diff --git a/PhysicsPackages/eigenPhysicsPackage_class.f90 b/PhysicsPackages/eigenPhysicsPackage_class.f90 index aa89111c1..d67603e8a 100644 --- a/PhysicsPackages/eigenPhysicsPackage_class.f90 +++ b/PhysicsPackages/eigenPhysicsPackage_class.f90 @@ -323,7 +323,7 @@ subroutine collectResults(self) type(outputFile) :: out character(nameLen) :: name - call out % init(self % outputFormat) + call out % init(self % outputFormat, filename=self % outputFile) name = 'seed' call out % printValue(self % pRNG % getSeed(),name) @@ -359,8 +359,6 @@ subroutine collectResults(self) call self % activeTally % print(out) call out % endBlock() - call out % writeToFile(self % outputFile) - end subroutine collectResults diff --git a/PhysicsPackages/fixedSourcePhysicsPackage_class.f90 b/PhysicsPackages/fixedSourcePhysicsPackage_class.f90 index cd200cdb6..792d11d38 100644 --- a/PhysicsPackages/fixedSourcePhysicsPackage_class.f90 +++ b/PhysicsPackages/fixedSourcePhysicsPackage_class.f90 @@ -266,7 +266,7 @@ subroutine collectResults(self) type(outputFile) :: out character(nameLen) :: name - call out % init(self % outputFormat) + call out % init(self % outputFormat, filename=self % outputFile) name = 'seed' call out % printValue(self % pRNG % getSeed(),name) @@ -287,8 +287,6 @@ subroutine collectResults(self) ! Print tally call self % tally % print(out) - call out % writeToFile(self % outputFile) - end subroutine collectResults diff --git a/UserInterface/fileOutput/CMakeLists.txt b/UserInterface/fileOutput/CMakeLists.txt index bf913adf4..3ad697235 100644 --- a/UserInterface/fileOutput/CMakeLists.txt +++ b/UserInterface/fileOutput/CMakeLists.txt @@ -4,6 +4,7 @@ add_sources( ./outputFile_class.f90 ./asciiOutputFactory_func.f90 ./asciiMATLAB_class.f90 ./asciiJSON_class.f90 - ./dummyPrinter_class.f90) + ./dummyPrinter_class.f90 + ./delayedStream_class.f90) add_unit_tests (./Tests/outputFile_test.f90) diff --git a/UserInterface/fileOutput/asciiJSON_class.f90 b/UserInterface/fileOutput/asciiJSON_class.f90 index 18ddf36eb..4fd6fa66c 100644 --- a/UserInterface/fileOutput/asciiJSON_class.f90 +++ b/UserInterface/fileOutput/asciiJSON_class.f90 @@ -21,7 +21,7 @@ module asciiJSON_class !! There is indentation for each block and entry, but long multi-dimensional (N-D) arrays !! are printed on a single line. Also N-D arrays are represented as a JSON array of arrays. !! - !! JSON output is fairly standard so it can be easlily read into Python and other enviroments + !! JSON output is fairly standard so it can be easily read into Python and other environments !! !! TODO: !! Currently there is a dirty fix to remove commas at the end of a block, which requires to @@ -39,7 +39,6 @@ module asciiJSON_class !! type, public, extends(asciiOutput) :: asciiJSON private - type(charTape) :: output integer(shortInt) :: ind_lvl = 0 integer(shortInt), dimension(:), allocatable :: shapeBuffer @@ -49,8 +48,8 @@ module asciiJSON_class contains procedure :: init + procedure :: endFile procedure :: extension - procedure :: writeToFile procedure :: startBlock procedure :: endBlock @@ -71,51 +70,43 @@ subroutine init(self) class(asciiJSON), intent(inout) :: self ! Add the initial bracket - call self % output % append( "{" // NEWLINE) + call self % append( "{" // NEWLINE) self % ind_lvl = 1 end subroutine init !! - !! Return approperiate extension for the file + !! Finalise the printer !! !! See asciiOutput_inter for details !! - pure function extension(self) result(str) - class(asciiJSON), intent(in) :: self - character(:), allocatable :: str - - str = 'json' - - end function extension - - !! - !! Print the output to the given unit - !! - !! See asciiOutput_inter for details - !! - subroutine writeToFile(self, unit) + subroutine endFile(self) class(asciiJSON), intent(inout) :: self - integer(shortInt), intent(in) :: unit - character(:),allocatable :: form ! Dirty fix ! Remove comma from the last entry by rewind ! Need to check that the character is a comma to avoid removing { when there is an empty block ! TODO: Find better way. Will clash with any proper stream output - if (self % output % get(self % output % length() - 1) == ",") then - call self % output % cut(2) - call self % output % append(NEWLINE) + if (self % peek(2) == ",") then + call self % cut(2) + call self % append(NEWLINE) end if + call self % append("}" // NEWLINE) - ! Close the file - call self % output % append("}") + end subroutine endFile - form = '(A' // numToChar(self % output % length()) // ')' + !! + !! Return appropriate extension for the file + !! + !! See asciiOutput_inter for details + !! + pure function extension(self) result(str) + class(asciiJSON), intent(in) :: self + character(:), allocatable :: str - write(unit,form) self % output % expose() + str = 'json' - end subroutine writeToFile + end function extension !! !! Change state to writing new block with "name" @@ -127,10 +118,10 @@ subroutine startBlock(self, name) character(nameLen), intent(in) :: name ! Write indentation - call self % output % append(repeat(BLANK, self % ind_lvl * IND_SIZE)) + call self % append(repeat(BLANK, self % ind_lvl * IND_SIZE)) ! Open new block and increase indentation - call self % output % append(QUOTE // trim(name) // QUOTE // ":" // "{" // NEWLINE) + call self % append(QUOTE // trim(name) // QUOTE // ":" // "{" // NEWLINE) self % ind_lvl = self % ind_lvl + 1 end subroutine startBlock @@ -147,17 +138,17 @@ subroutine endBlock(self) ! Remove comma from the last entry by rewind ! Need to check that the character is a comma to avoid removing { when there is an empty block ! TODO: Find better way. Will clash with any proper stream output - if (self % output % get(self % output % length() - 1) == ",") then - call self % output % cut(2) - call self % output % append(NEWLINE) + if (self % peek(2) == ",") then + call self % cut(2) + call self % append(NEWLINE) end if ! Decrease and write indentation self % ind_lvl = self % ind_lvl - 1 - call self % output % append(repeat(BLANK, self % ind_lvl * IND_SIZE)) + call self % append(repeat(BLANK, self % ind_lvl * IND_SIZE)) ! Close the block - call self % output % append("}" // ","// NEWLINE) + call self % append("}" // ","// NEWLINE) end subroutine endBlock @@ -171,10 +162,10 @@ subroutine startEntry(self, name) character(*), intent(in) :: name ! Print indentation - call self % output % append(repeat(BLANK, self % ind_lvl * IND_SIZE)) + call self % append(repeat(BLANK, self % ind_lvl * IND_SIZE)) ! Write the name - call self % output % append(QUOTE // trim(name) // QUOTE // ":") + call self % append(QUOTE // trim(name) // QUOTE // ":") end subroutine startEntry @@ -188,7 +179,7 @@ subroutine endEntry(self) ! End with NEWLINE ! Comma will be written together with a number/char - call self % output % append(NEWLINE) + call self % append(NEWLINE) end subroutine endEntry @@ -235,12 +226,12 @@ subroutine printNum(self, val) mul = 1 do i = 1, size(self % shapeBuffer) mul = mul * self % shapeBuffer(i) - if (modulo(self % count, mul) == 0) call self % output % append("[") + if (modulo(self % count, mul) == 0) call self % append("[") end do end if ! Print the number - call self % output % append(val) + call self % append(val) if (self % in_array) then ! Append the count @@ -250,12 +241,12 @@ subroutine printNum(self, val) mul = 1 do i = 1, size(self % shapeBuffer) mul = mul * self % shapeBuffer(i) - if (modulo(self % count, mul) == 0) call self % output % append("]") + if (modulo(self % count, mul) == 0) call self % append("]") end do end if ! Finish entry with a comma - call self % output % append(",") + call self % append(",") end subroutine printNum @@ -274,12 +265,12 @@ subroutine printChar(self, val) mul = 1 do i = 1, size(self % shapeBuffer) mul = mul * self % shapeBuffer(i) - if (modulo(self % count, mul) == 0) call self % output % append("[") + if (modulo(self % count, mul) == 0) call self % append("[") end do end if ! Print the character - call self % output % append(QUOTE // val // QUOTE) + call self % append(QUOTE // val // QUOTE) if (self % in_array) then ! Append the count @@ -289,12 +280,12 @@ subroutine printChar(self, val) mul = 1 do i = 1, size(self % shapeBuffer) mul = mul * self % shapeBuffer(i) - if (modulo(self % count, mul) == 0) call self % output % append("]") + if (modulo(self % count, mul) == 0) call self % append("]") end do end if ! Finish entry with a comma - call self % output % append(",") + call self % append(",") end subroutine printChar diff --git a/UserInterface/fileOutput/asciiMATLAB_class.f90 b/UserInterface/fileOutput/asciiMATLAB_class.f90 index eeb232789..b844dc710 100644 --- a/UserInterface/fileOutput/asciiMATLAB_class.f90 +++ b/UserInterface/fileOutput/asciiMATLAB_class.f90 @@ -23,8 +23,8 @@ module asciiMATLAB_class !! !! Printer for ASCII MATLAB output file !! - !! Creates a computer-readable matalab ".m" file. - !! Despite beeing in ASCII it is not intended for human-readability: + !! Creates a computer-readable matlab ".m" file. + !! Despite being in ASCII it is not intended for human-readability: !! -> Each entry is a single line. !! -> Furthermore every array is printed as RANK 1 array and a reshape function. !! @@ -40,7 +40,7 @@ module asciiMATLAB_class !! blockLevel -> Current block level !! output -> Character that contain the output !! prefix -> Current prefix for the block - !! shapeBuffer -> Buffored shape of the current array + !! shapeBuffer -> Buffered shape of the current array !! !! Interface: !! asciiOutput Interface @@ -52,17 +52,14 @@ module asciiMATLAB_class type(stackChar) :: blockNameStack integer(shortInt) :: blockLevel = 0 - ! Outputfile - type(charTape) :: output - ! Buffers type(charTape) :: prefix integer(shortInt), dimension(:), allocatable :: shapeBuffer contains procedure :: init + procedure :: endFile procedure :: extension - procedure :: writeToFile procedure :: startBlock procedure :: endBlock @@ -90,33 +87,29 @@ subroutine init(self) end subroutine init !! - !! Return approperiate extension for the file + !! Finalise the printer !! !! See asciiOutput_inter for details !! - pure function extension(self) result(str) - class(asciiMATLAB), intent(in) :: self - character(:), allocatable :: str + subroutine endFile(self) + class(asciiMATLAB), intent(inout) :: self - str = 'm' + ! Nothing to do - end function extension + end subroutine endFile !! - !! Print the output to the given unit + !! Return appropriate extension for the file !! !! See asciiOutput_inter for details !! - subroutine writeToFile(self, unit) - class(asciiMATLAB), intent(inout) :: self - integer(shortInt), intent(in) :: unit - character(:),allocatable :: form - - form = '(A' // numToChar(self % output % length()) // ')' + pure function extension(self) result(str) + class(asciiMATLAB), intent(in) :: self + character(:), allocatable :: str - write(unit,form) self % output % expose() + str = 'm' - end subroutine writeToFile + end function extension !! !! Change state to writing new block with "name" @@ -128,11 +121,6 @@ subroutine startBlock(self, name) character(nameLen), intent(in) :: name character(100), parameter :: Here ='startBlock (asciiMATLAB_class.f90)' - ! Check if state support acction - ! if (self % state == IN_ENTRY .or. self % state == IN_ARRAY) then - ! call fatalError(Here,'Cannot start writing new block inside entry or array') - ! end if - ! Update state - change current prefix call self % blockNameStack % push(name) call self % prefix % append(trim(name) // '_') @@ -151,15 +139,6 @@ subroutine endBlock(self) integer(shortInt) :: N character(100), parameter :: Here ='endBlock (asciiMATLAB_class.f90)' - ! Check if state support acction - ! if (self % state == IN_ENTRY .or. self % state == IN_ARRAY) then - ! call fatalError(Here,'Cannot end writing new block inside entry or array') - ! end if - ! - ! if ( self % blockLevel == 0) then - ! call fatalError(Here,'Cannot exit from root block') - ! end if - ! Update state - change current prefix call self % blockNameStack % pop(temp) @@ -180,17 +159,12 @@ subroutine startEntry(self, name) character(*), intent(in) :: name character(100), parameter :: Here ='startEntry (asciiMATLAB_class.f90)' - ! Check if state support acction - ! if (self % state == IN_ENTRY .or. self % state == IN_ARRAY) then - ! call fatalError(Here,'Cannot star writing new entry inside entry or array') - ! end if - ! Update state self % state = IN_ENTRY ! Write variable name with prefix - call self % output % append( trim(self % prefix % expose()) // trim(name)) - call self % output % append( ' = ') + call self % append( trim(self % prefix % expose()) // trim(name)) + call self % append( ' = ') end subroutine startEntry @@ -204,16 +178,11 @@ subroutine endEntry(self) class(asciiMATLAB), intent(inout) :: self character(100), parameter :: Here ='endEntry (asciiMATLAB_class.f90)' - ! Check if state support acction - ! if (self % state == IN_BLOCK .or. self % state == IN_ARRAY) then - ! call fatalError(Here,'Cannot finish entry in block or array') - ! end if - ! Update state self % state = IN_BLOCK ! Write semicolon and newline - call self % output % append( ';' // NEWLINE) + call self % append( ';' // NEWLINE) end subroutine endEntry @@ -227,11 +196,6 @@ subroutine startArray(self, shape) integer(shortInt),dimension(:),intent(in) :: shape character(100), parameter :: Here ='startArray (asciiMATLAB_class.f90)' - ! Check if state support acction - ! if (self % state /= IN_ENTRY) then - ! call fatalError(Here,'Cannot finish entry in block or array') - ! end if - ! Update state self % state = IN_ARRAY @@ -240,10 +204,10 @@ subroutine startArray(self, shape) ! Write start of array if( size(self % shapeBuffer) == 1) then - call self % output % append('[ ') + call self % append('[ ') else - call self % output % append('reshape([ ') + call self % append('reshape([ ') end if @@ -259,24 +223,19 @@ subroutine endArray(self) integer(shortInt) :: i character(100), parameter :: Here ='endArray (asciiMATLAB_class.f90)' - ! Check if state support acction - ! if (self % state /= IN_ARRAY) then - ! call fatalError(Here,'Cannot finish array inside an entry') - ! end if - ! Update state self % state = IN_ENTRY ! Write end of array - call self % output % cut(1) - call self % output % append(']') + call self % cut(1) + call self % append(']') ! Finish reshape function for higher rank arrays if( size(self % shapeBuffer) > 1) then do i=1,size(self % shapeBuffer) - call self % output % append(',' // numToChar(self % shapeBuffer(i))) + call self % append(',' // numToChar(self % shapeBuffer(i))) end do - call self % output % append(')') + call self % append(')') end if end subroutine endArray @@ -292,13 +251,11 @@ subroutine printNum(self, val) character(100), parameter :: Here ='printNum (asciiMATLAB_class.f90)' if(self % state == IN_ARRAY) then - call self % output % append(val//',') + call self % append(val//',') else if( self % state == IN_ENTRY) then - call self % output % append(val) + call self % append(val) - ! else - ! call fatalError(Here,'Cannot print number directly into block') end if end subroutine printNum @@ -314,13 +271,11 @@ subroutine printChar(self, val) character(100), parameter :: Here ='printChar (asciiMATLAB_class.f90)' if(self % state == IN_ARRAY) then - call self % output % append(BRAKET_L // APOS // val // APOS // BRAKET_R // ",") + call self % append(BRAKET_L // APOS // val // APOS // BRAKET_R // ",") else if( self % state == IN_ENTRY) then - call self % output % append(BRAKET_L // APOS // val // APOS // BRAKET_R ) + call self % append(BRAKET_L // APOS // val // APOS // BRAKET_R ) - ! else - ! call fatalError(Here,'Cannot print number directly into block') end if end subroutine printChar diff --git a/UserInterface/fileOutput/asciiOutput_inter.f90 b/UserInterface/fileOutput/asciiOutput_inter.f90 index 0b491d921..e60a7b8b2 100644 --- a/UserInterface/fileOutput/asciiOutput_inter.f90 +++ b/UserInterface/fileOutput/asciiOutput_inter.f90 @@ -1,6 +1,7 @@ module asciiOutput_inter use numPrecision + use delayedStream_class, only : delayedStream implicit none private @@ -10,7 +11,7 @@ module asciiOutput_inter !! !! Allows stream-like output to multiple formats (e.g. MATLAB, CSV, JSON) !! - !! They recive the sequence of calls from the `outputFile` and conver them to the output file. + !! They receive the sequence of calls from the `outputFile` and convert them to the output file. !! !! Printers do no error checking. It is responsibility of the `outputFile` to ensure that !! the sequence is correct. @@ -21,7 +22,7 @@ module asciiOutput_inter !! !! Interface: !! init -> Initialise - !! extension -> Return file extension approperiate for the format + !! extension -> Return file extension appropriate for the format !! writeToFile -> Print the output to the provided unit !! startBlock -> Start new block !! endBlock -> End a block @@ -32,10 +33,16 @@ module asciiOutput_inter !! type, public,abstract :: asciiOutput private + type(delayedStream) :: stream contains + procedure :: append + procedure :: cut + procedure :: peek + procedure :: close + procedure :: setUnit procedure(init), deferred :: init + procedure(init), deferred :: endFile procedure(extension), deferred :: extension - procedure(writeToFile),deferred :: writeToFile procedure(startBlock),deferred :: startBlock procedure(endBlock),deferred :: endBlock procedure(startEntry),deferred :: startEntry @@ -61,7 +68,21 @@ subroutine init(self) end subroutine init !! - !! Return approperiate extension for the file + !! End the file + !! + !! Format may require to print some characters at the end, so the printer + !! needs to be notified that no more data will be provided. + !! + !! Args: + !! None + !! + subroutine endFile(self) + import :: asciiOutput + class(asciiOutput), intent(inout) :: self + end subroutine endFile + + !! + !! Return appropriate extension for the file !! !! Must be without any "." Thus "exe" instead of ".exe"! !! @@ -74,19 +95,6 @@ pure function extension(self) result(str) character(:), allocatable :: str end function extension - !! - !! Print the output to the given unit - !! - !! Args: - !! unit [in] -> Unit number of the output - !! - subroutine writeToFile(self, unit) - import :: asciiOutput, & - shortInt - class(asciiOutput), intent(inout) :: self - integer(shortInt), intent(in) :: unit - end subroutine writeToFile - !! !! Change state to writing new block with "name" !! @@ -111,7 +119,7 @@ end subroutine endBlock !! !! Change state to writing a new entry !! - !! Can recive single value or array next + !! Can receive single value or array next !! !! Args: !! name [in] -> name of the entry @@ -134,7 +142,7 @@ end subroutine endEntry !! !! Start writing array with the given shape !! - !! Name should alrady be provided by "startEntry" + !! Name should already be provided by "startEntry" !! !! Args: !! shape [in] -> Shape of the array (in column-major order) @@ -183,4 +191,77 @@ subroutine printChar(self, val) end subroutine printChar end interface +contains + + + !! + !! Write data to the output + !! + !! Args: + !! text [in] -> Text to be written + !! + subroutine append(self, text) + class(asciiOutput), intent(inout) :: self + character(*), intent(in) :: text + + call self % stream % write(text) + + end subroutine append + + !! + !! Remove n characters from the output + !! + !! Args: + !! n [in] -> Number of characters to be removed + !! + subroutine cut(self, n) + class(asciiOutput), intent(inout) :: self + integer, intent(in) :: n + + call self % stream % cut(n) + + end subroutine cut + + !! + !! Get the nth last character from the output + !! + !! Args: + !! n [in] -> How many characters back to peak + !! + function peek(self, n) result(c) + class(asciiOutput), intent(inout) :: self + integer, intent(in) :: n + character(1) :: c + + c = self % stream % peek(n) + + end function peek + + !! + !! Close the stream + !! + !! Signals to the output that no more data will be provided, so the + !! closing characters can be printed. In addition flushes remaining characters + !! in the buffer to the output. + !! + subroutine close(self) + class(asciiOutput), intent(inout) :: self + + call self % endFile() + call self % stream % close() + + end subroutine close + + !! + !! Set the unit to write to + !! + subroutine setUnit(self, unit) + class(asciiOutput), intent(inout) :: self + integer(shortInt), intent(in) :: unit + + call self % stream % setUnit(unit) + + end subroutine setUnit + + end module asciiOutput_inter diff --git a/UserInterface/fileOutput/delayedStream_class.f90 b/UserInterface/fileOutput/delayedStream_class.f90 new file mode 100644 index 000000000..86869fec0 --- /dev/null +++ b/UserInterface/fileOutput/delayedStream_class.f90 @@ -0,0 +1,185 @@ +module delayedStream_class + + use numPrecision + use errors_mod, only : fatalError + + implicit none + private + + integer(shortInt), parameter :: MAX_DELAY = 1024 + integer(shortInt), parameter :: MIN_DELAY = 16 + + !! + !! Writes characters to a file with a delay to allow for backtracking. + !! + !! It is required to make writing of output format in stream-like fashion + !! easier to implement. For example, in JSON, it allows to backtrack and + !! remove trailing comma when closing a JSON dictionary. + !! + !! NOTE: We need to be careful to check if the file is open before writing + !! to it. Otherwise, we will get an error. + !! + !! Private Member: + !! unit -> Unit to which the output file is connected + !! bufferPos -> Position in the buffer + !! buffer -> Buffer of characters + !! + !! Interface: + !! write -> Write some characters to the buffer/file + !! cut -> Remove last N characters from the buffer + !! peek -> Inspect the Nth last character in the buffer + !! close -> Write all characters remaining in the buffer to the file + !! setUnit -> Set the unit to which the output file is connected + !! + type, public :: delayedStream + private + integer(shortInt) :: unit = -8 + integer(shortInt) :: bufferPos = 0 + character(MAX_DELAY) :: buffer + contains + procedure :: write + procedure :: cut + procedure :: peek + procedure :: close + procedure :: setUnit + + procedure, private :: flush + end type delayedStream + +contains + + !! + !! Write to a delayed stream + !! + !! Args: + !! text [in] -> text to write + !! + subroutine write(self, text) + class(delayedStream), intent(inout) :: self + character(*), intent(in) :: text + integer(shortInt) :: i + + ! Write to buffer + do i = 1, len(text) + self % bufferPos = self % bufferPos + 1 + self % buffer(self % bufferPos:self % bufferPos) = text(i:i) + if (self % bufferPos == MAX_DELAY) then + call flush(self) + end if + end do + + end subroutine write + + !! + !! Remove last characters from a delayed stream + !! + !! Args: + !! n [in] -> number of characters to remove. Must be less than the MIN_DELAY. + !! If n is larger than the number of characters in buffer, everything is removed. + !! + subroutine cut(self, n) + class(delayedStream), intent(inout) :: self + integer(shortInt), intent(in) :: n + character(100), parameter :: HERE = "cut (delayedStream_class.f90)" + + if (n > MIN_DELAY) then + call fatalError(HERE, "n must be less than MIN_DELAY") + end if + + self % bufferPos = max(0, self % bufferPos - n) + + end subroutine cut + + !! + !! Inspect the nth last character in the buffer + !! + !! Args: + !! n [in] -> The number of characters to peek back. + !! + function peek(self, n) result(c) + class(delayedStream), intent(inout) :: self + integer(shortInt), intent(in) :: n + character(n) :: c + character(100), parameter :: HERE = "peek (delayedStream_class.f90)" + + if (n > self % bufferPos) then + call fatalError(HERE, "n is larger than the buffer") + else + c = self % buffer(self % bufferPos - n + 1: self % bufferPos - n + 1) + end if + + end function peek + + !! + !! Flush contents of the buffer + !! + subroutine flush(self) + class(delayedStream), intent(inout) :: self + integer(shortInt) :: to_file + logical(defBool) :: isOpen + + ! Do not write anything if the file is not opened + inquire(unit=self % unit, opened=isOpen) + if (.not. isOpen) then + return + end if + + ! Do nothing if the buffer is below minimum length + if (self % bufferPos <= MIN_DELAY) then + return + end if + to_file = self % bufferPos - MIN_DELAY + + ! Write to file only if it is open + if (isOpen) then + write(self % unit, "(A)", advance="no") self % buffer(1 : to_file) + end if + + self % buffer(1:MIN_DELAY) = self % buffer(to_file + 1 : self % bufferPos) + + self % bufferPos = MIN_DELAY + + end subroutine flush + + !! + !! Close the delayed stream + !! + subroutine close(self) + class(delayedStream), intent(inout) :: self + logical(defBool) :: isOpen + + ! Do not write anything if the file is not opened + inquire(unit=self % unit, opened=isOpen) + if (.not. isOpen) then + return + end if + + ! Does nothing if empty + if (self % bufferPos == 0) then + return + end if + + ! Write the remaining buffer + ! Write to file only if it is open + if (isOpen) then + write(self % unit, "(A)", advance="no") self % buffer(1:self % bufferPos) + end if + self % bufferPos = 0 + + end subroutine close + + !! + !! Set the unit to which the output file is connected + !! + !! Args: + !! unit [in] -> Unit to which the output file is connected. May be opened or not. + !! Both are fine. If it is not opened, nothing will be written to it. + subroutine setUnit(self, unit) + class(delayedStream), intent(inout) :: self + integer(shortInt), intent(in) :: unit + + self % unit = unit + + end subroutine setUnit + +end module delayedStream_class diff --git a/UserInterface/fileOutput/dummyPrinter_class.f90 b/UserInterface/fileOutput/dummyPrinter_class.f90 index 890883d4d..de2f84442 100644 --- a/UserInterface/fileOutput/dummyPrinter_class.f90 +++ b/UserInterface/fileOutput/dummyPrinter_class.f90 @@ -23,8 +23,8 @@ module dummyPrinter_class contains procedure :: init + procedure :: endFile procedure :: extension - procedure :: writeToFile procedure :: startBlock procedure :: endBlock @@ -52,29 +52,29 @@ subroutine init(self) end subroutine init !! - !! Return approperiate extension for the file + !! End the file !! !! See asciiOutput_inter for details !! - pure function extension(self) result(str) - class(dummyPrinter), intent(in) :: self - character(:), allocatable :: str + subroutine endFile(self) + class(dummyPrinter), intent(inout) :: self - str = '' + ! Nothing to do - end function extension + end subroutine endFile !! - !! Print the output to the given unit + !! Return approperiate extension for the file !! !! See asciiOutput_inter for details !! - subroutine writeToFile(self, unit) - class(dummyPrinter), intent(inout) :: self - integer(shortInt), intent(in) :: unit + pure function extension(self) result(str) + class(dummyPrinter), intent(in) :: self + character(:), allocatable :: str + str = '' - end subroutine writeToFile + end function extension !! !! Change state to writing new block with "name" diff --git a/UserInterface/fileOutput/outputFile_class.f90 b/UserInterface/fileOutput/outputFile_class.f90 index 6585580e0..48fb2011d 100644 --- a/UserInterface/fileOutput/outputFile_class.f90 +++ b/UserInterface/fileOutput/outputFile_class.f90 @@ -1,7 +1,8 @@ module outputFile_class use numPrecision - use genericProcedures, only : fatalError, numToChar + use errors_mod, only : fatalError + use genericProcedures, only : numToChar use stack_class, only : stackChar use charTape_class, only : charTape use charMap_class, only : charMap @@ -28,10 +29,10 @@ module outputFile_class !! !! Stack to store the dictionaries with occupied entries !! - !! Note that blocks in outputFile are enumarated from 0 + !! Note that blocks in outputFile are enumerated from 0 !! So index in stack is idx = blockLevel + 1 !! - !! Imlementation must be rebust to avoid segmentation errors if outut is used + !! Implementation must be robust to avoid segmentation errors if output is used !! in incorrect sequence (e.g. closing more blocks then were open) !! !! Interface @@ -69,7 +70,7 @@ module outputFile_class !! is called root !! -> Each entry in a block (including sub-blocks) must have a unique name !! -> Each entry is either a raw "Name - Value" pair or an array - !! -> Arrays can have unlimited rank (dimension), but values of the enteries must be given in a + !! -> Arrays can have unlimited rank (dimension), but values of the entries must be given in a !! sequence in a COLUMN-MAJOR ORDER !! -> Arrays can store RESULTS, REALS, INTS or CHARACTERS. !! -> RESULT is a pair of reals. MEAN and STANDARD DEVIATION @@ -81,14 +82,14 @@ module outputFile_class !! !! Handling errors: !! If `fatalError` is true (default) then on wrong sequence of calls fatalError will be raised. - !! Otherwise the flag `noErrors` will be set to false and error massege saved in the `errorLog` + !! Otherwise the flag `noErrors` will be set to false and error massage saved in the `errorLog` !! !! Repeated Names: - !! If name in a block is repeted bahviour is governed by `logNameReuse` function. If `fatalError` - !! is true, the nwarning message is produced. Otherwise error is logged in `errorLog` and + !! If name in a block is repeated behaviour is governed by `logNameReuse` function. If `fatalError` + !! is true, the warning message is produced. Otherwise error is logged in `errorLog` and !! `noErrors` flag set to FALSE. !! - !! Private Memebers: + !! Private Members: !! output -> Output printer that determines the format !! type -> Type of the output printer !! fatalErrors -> True (default) makes all errors fatal @@ -107,8 +108,7 @@ module outputFile_class !! !! Interface: !! init -> Initialise the output file with format - !! writeToFile -> Write the output to a file - !! writeToConsole -> Write the output to the console + !! finalise -> Finalise the output file. Ensures all data is written. !! reset -> Discard already written output. !! isValid -> Return true if no errors were raised since initialisation !! getErrorLog -> Get the error log @@ -124,8 +124,10 @@ module outputFile_class !! type, public :: outputFile private - class(asciiOutput),allocatable :: output - character(nameLen) :: type + class(asciiOutput), allocatable :: output + character(nameLen) :: type + character(:), allocatable :: outputFileName + integer(shortInt) :: outputUnit = -97875674 ! Hopefully this does not exist for any internal units ! Error handling settings logical(defBool) :: fatalErrors = .true. ! Enable fatalErrors on wrong logic @@ -141,7 +143,7 @@ module outputFile_class character(nameLen) :: current_array_name = '' type(stackChar) :: block_name_stack - ! Buffors + ! Buffers integer(shortInt),dimension(:), allocatable :: shapeBuffer type(charMapStack) :: usedNames @@ -151,9 +153,8 @@ module outputFile_class contains procedure :: init - procedure :: writeToFile - procedure :: writeToConsole procedure :: reset + final :: finalisation ! Error Handling procedure, private :: logError @@ -210,17 +211,56 @@ module outputFile_class !! !! Args: !! type [in] -> Character to specify the format of the output file - !! fatalErrors [in] -> Optional. Set on/off fatalErrors oninvalid use of output file. + !! fatalErrors [in] -> Optional. Set on/off fatalErrors on invalid use of output file. !! Default is ON. - !! - subroutine init(self, type, fatalErrors) - class(outputFile), intent(inout) :: self - character(*),intent(in) :: type - logical(defBool),optional,intent(in) :: fatalErrors + !! filename [in] -> Optional. Set the name of the output file. If none is given no output is + !! written. Must be given without extension. It will be added automatically + !! based on the format of the output file. + !! + subroutine init(self, type, fatalErrors, filename) + class(outputFile), intent(inout) :: self + character(*), intent(in) :: type + logical(defBool), optional,intent(in) :: fatalErrors + character(*), optional, intent(in) :: filename + integer(shortInt) :: error + character(99) :: errorMsg + logical(defBool) :: isOpen + character(100), parameter :: Here = "init (outputFile_class.f90)" self % type = type allocate( self % output, source = new_asciiOutput(self % type)) + ! Open file if given + ! Otherwise make sure that the unit is not opened + if (present(filename)) then + ! Because GFotran hates implicit allocation + ! It causes buggy warnings to appear in compilation + allocate(self % outputFileName, source = trim(filename) // "." // self % output % extension()) + + open(newunit = self % outputUnit, & + file = self % outputFileName, & + status = "replace", & + action = "write", & + access = "stream", & + form = "formatted",& + iostat = error, & + iomsg = errorMsg) + + ! Catch error + if (error /= 0 ) call fatalError(Here, errorMsg) + else + ! Set unit to some unused, unopened unit + ! delayedStream will not write anything if given an unopened file + do + inquire(self % outputUnit, opened = isOpen) + if (.not. isOpen) exit + self % outputUnit = self % outputUnit + 1 + end do + end if + + ! Set output unit + call self % output % setUnit(self % outputUnit) + ! Initialise name stack call self % usedNames % init() call self % usedNames % push() @@ -233,58 +273,36 @@ subroutine init(self, type, fatalErrors) end subroutine init !! - !! Dump collected results to file - !! - !! Writes the output stored in memory to a file - !! - !! Args: - !! file [in] -> Path to the output file. Must be given WITHOUT extension - !! Approperiate extension will recived from the printer + !! Upon the destruction of the object, write the output to file !! - !! Errors: - !! fatalError if there is a problem opening the file. - !! - subroutine writeToFile(self, file) - class(outPutFile), intent(inout) :: self - character(*), intent(in) :: file - integer(shortInt) :: unitNum - integer(shortInt) :: error - character(:), allocatable :: file_loc - character(99) :: errorMsg - character(100), parameter :: Here = 'writeToFile (outputFile_class.f90)' - - file_loc = trim(file) // "." // self % output % extension() - - ! Open file to write - open ( newunit = unitNum, & - file = file_loc, & - action = 'write', & - iostat = error , & - iomsg = errorMsg ) - - ! Catch error - if (error /= 0 ) call fatalError(Here, errorMsg) - - ! Write to file - call self % output % writeToFile(unitNum) - - ! Close file - close(unitNum) + !! NOTE: + !! Being marked `final`(a deconstructor) this subroutine will be normally implicitly called by + !! by the compiler whenever a variable of `type(outputFile)` goes out of scope (e.g. when program + !! execution reaches `end subroutine`, `end function` or `end block` statement. However, the rule in + !! Fortran is that the decostructors are NOT called at the end of the program `end program`. In that case + !! if one uses the output file as a module variable or defines it inside the program block, the `finalisation` + !! must be called explicitly to ensure or data from the buffer is written to the disk + !! + !! + subroutine finalisation(self) + type(outputFile), intent(inout) :: self + logical(defBool) :: isOpen + + !! Deallocate the printer + !! Needs to be done before closing the file + !! So remaining contents of the buffer are flushed + if (allocated(self % output)) then + call self % output % close() + deallocate(self % output) + end if - end subroutine writeToFile + inquire(unit = self % outputUnit, opened = isOpen) - !! - !! Print output file to the console (default output) - !! - !! Args: - !! None - !! - subroutine writeToConsole(self) - class(outputFile), intent(inout) :: self - - call self % output % writeToFile(OUTPUT_UNIT) + if (isOpen) then + close(self % outputUnit) + end if - end subroutine writeToConsole + end subroutine finalisation !! !! Discards all results and sets output file to an initialised state @@ -296,6 +314,8 @@ subroutine reset(self) deallocate(self % output) allocate( self % output, source = new_asciiOutput(self % type)) + if (allocated(self % outputFileName)) deallocate(self % outputFileName) + ! Clean error log an reset flag call self % errorLog % clean() self % noErrors = .true. @@ -341,10 +361,10 @@ subroutine logError(self, where, what) end subroutine logError !! - !! Deal with an event when entry name is resued in a block + !! Deal with an event when entry name is reused in a block !! !! Currently is fatalError is TRUE -> Prints warning to the screen - !! If fatalError is False markes error flagto true and appends a log + !! If fatalError is False marks error flag to true and appends the log !! !! Args: !! name [in] -> Name that has been reused @@ -403,7 +423,7 @@ end function getErrorLog !! !! Errors: !! Calls logError if: - !! -Called when writting an array + !! -Called when writing an array !! Calls logNameReuse if name is not unique !! subroutine startBlock(self, name) @@ -422,7 +442,7 @@ subroutine startBlock(self, name) if ( self % arrayType /= NOT_ARRAY) then call self % logError(Here,'In block: '// self % current_block_name // & ' Cannot begin new block: '//name// & - ' Becouse is writing array: '// self % current_array_name) + ' Because is writing array: '// self % current_array_name) end if ! Update state @@ -443,7 +463,7 @@ end subroutine startBlock !! !! Errors: !! Calls logError if: - !! -Called when writting an array + !! -Called when writing an array !! -Tries to end the root block. !! subroutine endBlock(self) @@ -454,7 +474,7 @@ subroutine endBlock(self) if ( self % arrayType /= NOT_ARRAY) then call self % logError(Here,'In block: '// self % current_block_name // & ' Cannot exit from block: '// & - ' Becouse is writing array: '// self % current_array_name) + ' Because is writing array: '// self % current_array_name) end if ! Check that is not in root block @@ -485,7 +505,7 @@ end subroutine endBlock !! !! Errors: !! Calls logError if: - !! -Called when writting another array + !! -Called when writing another array !! -Shape is invalid !! Calls logNameReuse if name is not unique !! @@ -505,14 +525,14 @@ subroutine startArray(self, name, shape) if ( self % arrayType /= NOT_ARRAY) then call self % logError(Here,'In block: '// self % current_block_name // & ' Cannot start new array: '//name// & - ' Becouse is writing array: '// self % current_array_name) + ' Because is writing array: '// self % current_array_name) end if ! Check whether shape is degenerate if (size(shape) == 0 .or. product(shape) == 0) then call self % logError(Here,'In block: '// self % current_block_name // & ' Cannot start new array: '//name// & - ' Becouse the shape is degenerate') + ' Because the shape is degenerate') end if ! Load shape information into buffer @@ -524,8 +544,8 @@ subroutine startArray(self, name, shape) ! Update State -> Array is present but has undefined type self % arrayType = UNDEF_ARRAY - ! Print begining of new entry - ! We don't know the shape of array becouse if has results its rank will increase + ! Print beginning of new entry + ! We don't know the shape of array because if has results its rank will increase call self % output % startEntry(name) end subroutine startArray @@ -548,7 +568,7 @@ subroutine endArray(self) if(self % arrayTop /= self % arrayLimit) then call self % logError(Here, 'Cannot close array: ' // trim(self % current_array_name) // & ' in block: ' // trim(self % current_block_name) // & - ' Becouse only: '// numToChar(self % arrayTop) // & + ' Because only: '// numToChar(self % arrayTop) // & ' of '// numToChar(self % arrayLimit)//' were provided') end if @@ -615,10 +635,10 @@ end subroutine addResult_scalar !! Add results to the array !! !! Args: - !! val [in] -> Array of mean valuse + !! val [in] -> Array of mean values !! std [in] -> Associated standard deviations !! - !! Erorrs: + !! Errors: !! Calls logError if: !! -Called before an array is opened !! -Currently opened array has different type @@ -632,7 +652,7 @@ subroutine addResult_rank1(self, val, std) character(100),parameter :: Here ='addResult_rank1 (outputFile_class.f90)' N = size(val) - if (N /= size(std)) call self % logError(Here,'val and std have diffrent size.') + if (N /= size(std)) call self % logError(Here, 'val and std have different size.') ! Add all individual entries do i = 1, N @@ -1203,7 +1223,7 @@ end subroutine push_charMapStack !! None !! !! Errors: - !! Poping empty stack does nothing! + !! Popping empty stack does nothing! !! subroutine pop_charMapStack(self) class(charMapStack), intent(inout) :: self @@ -1218,7 +1238,7 @@ subroutine pop_charMapStack(self) end subroutine pop_charMapStack !! - !! Add new entery at the given block level + !! Add new entry at the given block level !! !! Args: !! name [in] -> Entry name @@ -1246,7 +1266,7 @@ end subroutine add_charMapStack !! b_lvl [in] -> Block level (idx = b_lvl + 1) !! !! Result: - !! True if it is. False otherwsie + !! True if it is. False otherwise !! !! Errors: !! Returns FALSE is b_lvl is out of range diff --git a/docs/Output.rst b/docs/Output.rst index 7349deb8d..9d3108e3b 100644 --- a/docs/Output.rst +++ b/docs/Output.rst @@ -38,8 +38,9 @@ Writing to an output file in SCONE is done through a series of calls to appropri character(nameLen) :: name type(outputFile) :: out - !! Initialise output by choosing the format - call out % init('asciiMatlab') + !! Initialise output by choosing the format and output file + !! If no file name is provided, the output will not be printed + call out % init('asciiMatlab', filename="./path/to/output/without_any_extension") !! Write an integer of 7 name = 'int' @@ -76,9 +77,10 @@ Writing to an output file in SCONE is done through a series of calls to appropri !! Same goes for blocks call out % endBlock() - !! When done, we can write output to a file - !! Extension will be determined by the format printer - call out % writeToFile("./path/to/output/without_any_extension") + !! We need to make sure that output file is properly finalised. We can skip + !! this line if we are not in a `program` block e.g. in `subroutine` or `function` + call out % finalise() + The need to write name to a `character(nameLen)` variable before calling the procedure is a quirk caused by the fact that the output file expects character of `nameLen` as a variable. If one were to