Closed wyphan closed 2 years ago
The specific use case I'm aiming for is a testing framework. I want to define a macro that assigns the "golden" value into the Python dictionary key-value pair, and another macro that checks the test result against the "golden" value. I want to use Python dictionaries because the value can be a Python list (which can then be mapped into a Fortran 1-D array).
You can use the .update()
in an Python expression to update values in a dictionary:
#:set values = {"energy": 12.0, "force": [1.0, 0.0, -1.0]}
$:values["energy"]
$:values.update([("energy", 100.0)])
$:values["energy"]
Thanks for the quick response! I think the next problem is the scoping of the Python dictionary. How can I declare it as a global variable so it spans multiple macros in the same fypp
input file?
For instance, here's what I've put up so far as mod_testing.fypp
:
#! Macro to initialize testing infrastructure
#:def test_init()
#! Number of tests and errors
#:global ntst
#:set ntst = 0
#:global nerr
#:set nerr = 0
#! Dictionaries to hold golden values and test labels
#:global chkval
#:set chkval = {}
#:global tstlbl
#:set tstlbl = {}
#:enddef
#! Macro to register golden value
#:def save_gold( lbl, val )
$:tstlbl.update([(ntst, lbl)])
$:chkval.update([(ntst, val)])
#:enddef save_gold
#! Macro to check scalar test result exactly against golden value
#:def check_exact( val )
$:lbl = tstlbl[ntst]
$:gold = chkval[ntst]
BLOCK
USE ISO_FORTRAN_ENV, ONLY: o => output_unit, e => error_unit
WRITE(o,'(A)') ${lbl}$
#:if val == gold
WRITE(e,'("OK: result = ${val}$")')
#:else
$:nerr += 1
WRITE(e,'("Error[${_FILE_}$]: result = ${val}$, should be ${gold}")')
#:endif
END BLOCK
$:ntst += 1
#:enddef check_exact
And then here's where it's used, in test_gamma.fypp
:
#:include "mod_testing.fypp"
!===============================================================================
! Unit test for "Gamma" offset functions
! Last edited: Oct 26, 2021 (WYP)
!===============================================================================
PROGRAM testgamma
USE ISO_FORTRAN_ENV, ONLY: u => output_unit
! USE moa
IMPLICIT NONE
!-----------------------------------------------------------------------------
! Test for empty shape vector
!-----------------------------------------------------------------------------
WRITE(u,'("Test for empty shape")')
@:save_gold( "Gamma( <>, <> )", 0 )
! r = moa_gamma( [], [] )
@:check_exact( r )
WRITE(u,*)
STOP
END PROGRAM testgamma
The error message that I got from fypp
when preprocessing test_gamma.fypp
is:
test_gamma.fypp:13: error: exception occurred when calling 'save_gold' [FyppFatalError]
mod_testing.fypp:17: error: exception occurred when evaluating 'tstlbl.update([(ntst, lbl)])' [FyppFatalError]
error: name 'tstlbl' is not defined [NameError]
Never mind, I figured it out! Here's my current mod_testing_macros.fypp
:
#! -*- mode: f90; -*-
#:mute
!===============================================================================
! Testing infrastructure fypp macros
! Last edited: Nov 10, 2021 (WYP)
!===============================================================================
#:endmute
#! Initialize testing infrastructure
#! Number of tests
#:global ntst
#:set ntst = 0
#! Dictionaries to hold golden values and test labels
#:global chkval
#:set chkval = {}
#:global tstlbl
#:set tstlbl = {}
#! Macro to register golden value
#:def save_gold( lbl, val )
$:tstlbl.update([(ntst, lbl)])
$:chkval.update([(ntst, val)])
#:enddef save_gold
#! Macro to check scalar test result exactly against golden value
#:def check_exact( val )
#:set lbl = tstlbl[ntst]
#:set gold = chkval[ntst]
BLOCK
USE ISO_FORTRAN_ENV, ONLY: o => output_unit, e => error_unit
WRITE(o,'(A)') ${lbl}$
IF( ${val}$ == ${gold}$ ) THEN
WRITE(e,*) 'OK: result = ', ${val}$
ELSE
nerr = nerr + 1
WRITE(e,*) 'Error[${_FILE_}$]: result = ', ${val}$, ', should be ${gold}$'
END IF
END BLOCK
#:set ntst = ntst + 1
#:enddef check_exact
#:def test_summary()
BLOCK
USE ISO_FORTRAN_ENV, ONLY: o => output_unit
WRITE(o,'("Summary for ${_FILE_}$:")')
IF( nerr > 0 ) THEN
WRITE(o,'(" ",I0," out of ${ntst}$ tests failed")') nerr
ELSE
WRITE(o,'(" All ${ntst}$ tests succeeded")')
END IF
END BLOCK
#:enddef test_summary
I am happy you managed. Just a comment: Variables in the global scope (chkval
, tslb1
, etc.) do not need a #:global
assignment, they are global by default...
Otherwise, you may eventually be interested in the FyTest unit testing framework, which offers unit testing and uses the aforementioned dictionary manipulation quite heavily.
Is it possible to assign and retrieve Python dictionary key-value pairs from within
fypp
without writing a dedicated Python class? If yes, what is the correct syntax to do so?