Changelog#

Versions follow Semantic Versioning (<major>.<minor>.<patch>).

Backward incompatible (breaking) changes will only be introduced in major versions with advance notice in the Deprecations section of releases.

fgen v0.4.1 (2024-04-10)#

Improved Documentation#

  • Fixed the generated changelog from the release of v0.4.0 (#109)

fgen v0.4.0 (2024-04-10)#

Breaking Changes#

  • Broke the initialisation for fgen.models.ValueDefinition.

    This was required to handle multi-return types. fgen.models.ValueDefinition must now be initialised with a fgen.models.UnitlessValueDefinition as part of its input. (#44)

  • Move black and jinja2 to optional dependencies.

    pip install fgen will now only install the runtime dependencies required for fgen_runtime. For users that are not building wrappers, this change will have no impact.

    For downstream packages that generate wrappers, you now need to specify fgen[templates] as a build dependency in order to use the fgen package or the CLI.

    The unused openscm-units dependency was also removed. (#49)

  • Changed the default module prefix to an empty string i.e. no prefix.

    This simplifies the reasoning related to prefixes and makes it much easier to use modules with a name that does not use the “mod_” prefix.

    It does break the previous behaviour. However, these changes are all in the auto-generated wrappers, so should require minimal (if any) changes for users. (#70)

  • Renamed fgen.data_models.unitless_value.UnitlessValue.expose_to_python to fgen.data_models.unitless_value.UnitlessValue.expose_getter_to_python.

    The flag does the same thing, but this renaming clarifies its behaviour. (#79)

  • Re-structured the templates within fgen.

    fgen.templator was removed and has been moved to fgen.wrapper_building. This is the only user-facing change, all other changes are internal. (#86)

  • Updated the fgen generate command-line interface so that it expects a series of yaml files rather than processing modules one at a time.

    This makes it much easier to handle dependencies between modules, as we have them all in memory at the same time. It also paves the way for improving our ability to wrap new types, for example enums (which require a Python definition of the enum to be written too, but only once at the package-level, as we can only pass integers across the Python-Fortran border). Such behaviour is not possible when we write the modules independently of each other.

    If you previously did e.g.

    fgen generate ... mod_1.yaml
    fgen generate ... mod_2.yaml
    

    Now you can just do

    fgen generate ... mod_1.yaml mod_2.yaml
    

    This MR also removed fgen.wrapper_building.process_module()}, replacing it with fgen.wrapper_building.process_package(). If you previously did, e.g.

    process_module(module, ...)
    

    As demonstrated in the tests, you can get the same behaviour with

    process_package(Package((module,)), ...)
    

    (#88)

  • Removed fgen.data_models.module.Module.short_name and fgen.data_models.module.Module.short_name.

    The same behaviour can be achieved by setting fgen.data_models.module.Module.truncated_name (and this option also provides much greater flexibility and control). (#96)

Features#

  • Pin fgen==v0.3.1 via the CMake module (#43)

  • Added the ability to handle fluxes as part of model output.

    This MR included a number of additional pieces of functionality to make this work:

    • libfgen.fgen_time.TimeAxis to support time axis handling including bounds

    • fgen.models.MultiReturnDefinition to handle the case where there are multiple return values from a function/method

    (#44)

  • Added libfgen.timeseries.Timeseries (#51)

  • Added module fgen_interp1d and module fgen_array_helpers (#52)

  • Added solve_until_end_time_bounds argument to libfgen.fgen_ivp.solve_ivp().

    This allows us to ensure that the model is solved up until the end of the last time step, rather than stopping at the start of the last time step, which is not helpful for creating timeseries objects etc. (#54)

  • Added libfgen.fgen_timeseries.Timeseries.integrate() and libfgen.fgen_timeseries.Timeseries.differentiate().

    Integration and differentiation are only implemented for key use cases. If you have a different use case, the skeleton is there but you will need to implement the details yourself.

    These are supported by libfgen.fgen_integrated1d and libfgen.fgen_differentiate1d and corresponding changes in the underlying 1D interpolation modules.

    This change also required splitting out libfgen.fgen_interp1d_options to avoid circular dependencies. (#55)

  • Added libfgen.fgen_values_bounded.ValuesBounded (#57)

  • Added libfgen.fgen_timeseries.Timeseries.get_value_at_time() to facilitate retrieving values at specific times.

    This also required adding libfgen.fgen_boundary1d, to support handling of the logic around which values to supply when on the boundary of our timesteps (which varies depending on the kind of interpolation which is being assumed). (#59)

  • Added libfgen.fgen_timeseries.Timeseries.interpolate(), libfgen.fgen_timeseries.Timeseries.interpolate_single() and libfgen.fgen_timeseries.Timeseries.extrapolat_single() to support interpolation and extrapolation operations with libfgen.fgen_timeseries.Timeseries. (#60)

  • Added support for wrapping derived type attributes which are character arrays in Fortran, str in Python. (#71)

  • Added setters for and the ability for methods to return the following types:

    • intrinsic

      • float

      • int

      • boolean

    • characters/strings

      • fixed-length

      • allocatable-length

    • arrays

      • fixed-length

      • allocatable-length

    • derived types

      • accessed via pointers

      • encapsulate i.e. allocatable attributes are not yet supported, that will happen in #42

    The setters are not added automatically, they must be opted into on a per-attribute basis by setting expose_setter_to_python: true.

    (#79)

  • Added support for wrapping derived types that have allocatable, other derived types as attributes (#80)

  • Added support for wrapping libfgen.fgen_values_bounded.ValuesBounded.

    The more general pattern is that we can now support things that should retrieve their units dynamically from Pint quantities, rather than forcing all unit specifications to be static. There are now quite a lot of moving parts in our wrapper generation. We aim to clean these up as part of #60. (#81)

  • Added fgen.data_models.Package for handling a collection of modules i.e. a package (#88)

  • Added auto-generation of _repr_pretty_ and _repr_html_ methods to the generated Python wrappers (#91)

  • Added fgen.data_models.package_shared_elements.PackageSharedElements to provide a common source for elements that are common across the wrappers in a package and added fgen.data_models.unitless_value.is_fortran_units_holder to support and clarify how dynamic units are handled.

    fgen.data_models.unitless_value.is_fortran_units_holder allows you to specify that this attribute is the attribute which holds the units in Fortran, i.e. it is where dynamic units should be passed into Fortran and where dynamic units should be retrieved from Fortran. (#93)

  • Added support for wrapping enums defined in Fortran.

    This was done by adding fgen.wrapping_strategies.enum.WrappingStrategyEnum, fgen.data_models.EnumDefiningModule, fgen.data_models.enum_defining_module and updating the fgen generate command to support taking in enum-defining yaml files. The changes to fgen generate are backwards-compatible hence require no interaction from users who do not wish to take advantage of this new feature. (#103)

Improvements#

  • Upgraded fgen so it can now wrap Fortran callables that return values in different units.

    For example, a callable that returns temperature in kelvin and ocean heat uptake in joules per year.

    This required adding fgen.models.CalculatorDefinition.units_str() and fgen.models.CalculatorDefinition.units_multi_return(). (#44)

  • Updated the codebase to handle the introduction of libfgen.fgen_values_bounded.ValuesBounded.

    Specifically, updated libfgen.fgen_time.TimeAxis so that it is a sub-class of libfgen.fgen_values_bounded.ValuesBounded. (There doesn’t seem to be a better way to do this in Fortran given the lack of attrs-style validators.)

    Also updated libfgen.fgen_timeseries.Timeseries and libfgen.fgen_ivp.solve_ivp() to use libfgen.fgen_values_bounded.ValuesBounded.

    Also updated libfgen.fgen_values_bounded.ValuesBounded.repr() so that it can be indented in libfgen.fgen_timeseries.Timeseries.repr(). (#56)

  • Added sane defaults to format strings in libfgen.fgen_char_conversions.

    As a result, you no longer need to pass format strings into the various formatting functions in libfgen.fgen_char_conversions.

    Also exposed constants in libfgen.fgen_char_conversions that can be used by other components if they wish. We expected this would be used rarely, but it avoids completely hard-coding the values with no explanation of what they are and provides a way to control them without exposing arguments to control these values in every single function that does any character handling. (#57)

  • Clarified the one-dimensional handling module.

    There is now an extensive docstring at the top of libfgen.fgen_1d_handling_options.

    This also included the following clean ups:

    • renaming src/libfgen/interp1d to src/libfgen/1d_handling

    • unification of the filenames in src/libfgen/1d_handling

    • unification of the module names in src/libfgen/1d_handling

    • unification of the option names in libfgen.fgen_1d_handling_options

    • using time rather than x consistently throughout libfgen.fgen_1d_handling_options, to reflect the fact that our x-axis is assumed to be a time axis (i.e. stricly monotonically increasing)

    As the affected features are unreleased, we do not provide a more detailed migration guide.

    (#61)

  • Updated libfgen.fgen_ivp.solve_ivp() so it only supports the use of t_eval.

    t_eval is now a required argument and support for simply solving until t_max has been removed. The t_tolerance argument was also removed because the results will match t_eval so there is no need to worry about tolerance anymore.

    As the affected features are unreleased, we do not provide a more detailed migration guide. The short story is this: any use of libfgen.fgen_ivp.solve_ivp() will now require t_eval. To get the same behaviour as previously, set the only value in t_eval % values equal to your previous t_max. Then just drop the first value in your result. (#62)

  • Re-named solver to stepper throughout the code, whenever the thing being referred to was an object used for numerically solving ODE’s.

    This is a breaking change. For example, we have renamed src/libfgen/solvers to src/libfgen/steppers and renamed libfgen.fgen_ivp to libfgen.fgen_solve_ivp. However, as the code it breaks has not yet been released, we do not label this as breaking nor do we provide a detailed migration guide. (#65)

  • Aligned the names in fgen with the domain model described in Overview.

    This is a breaking change. For example, we have renamed src/libfgen/models to src/libfgen/data_models and renamed calculator to fortran derived type throughout the code. However, as the code it breaks has not yet been released, we do not label this as breaking nor do we provide a detailed migration guide. (#67)

  • Renamed the classes in fgen.data_models for consistency.

    Changes:

    • fgen.data_models.MethodDefinition -> fgen.data_models.Method

    • fgen.data_models.ModuleDefinition -> fgen.data_models.Module

    • fgen.data_models.MultiReturnDefinition -> fgen.data_models.MultiReturn

    • fgen.data_models.ValueDefinition -> fgen.data_models.Value

    • fgen.data_models.UnitlessValueDefinition -> fgen.data_models.UnitlessValue

    As the code it breaks was not released at the time of merging, we do not label this as breaking nor do we provide a detailed migration guide.

    (#72)

  • Re-wrote fgen.templator and the associated template files

    Key changes:

    • wrote docs explaining how our templating works

    • made the Fortran wrapper module template and Python wrapper module template more symmetric, this makes it easier to see how they work

    • split the templates out into a number of smaller files. This means there is more to keep track of, but the logic is much easier to follow within the limited scopes.

    • cleaned up the templates

    • changed the naming in the templates to try to use most significant bit naming, e.g. wrapper modules are now module_name_w rather than w_module_name because the key thing is the module that is being wrapped, then the fact that it is a wrapper. This also makes the names of our modules more consistent (you end up with module_name, module_name_w and module_name_manager rather than having prefixes which makes it harder to line things up).

    As the code it breaks has not yet been released, we do not label this as breaking nor do we provide a detailed migration guide.

    (#76)

  • Increased the number of available Fortran instances in manager modules to 4096 and improved the validation and handling of dynamic unit values.

    In testing in the notebook, we found that you can hit 2048 easily once you start making lots of plots (which can trigger recursive calls to access units, with associated instance usage). (#93)

  • When processing a package, generate a __init__.py file in the python directory if one does not already exist. This ensures that the Python directory is always a valid Python package. (#101)

Bug Fixes#

  • Fixed handling of indentation when creating Python code.

    In yaml files, use “|” after multi-wrap lines and then things should just work from there. (#44)

  • Removed unused dependency “cmakelang” from the list of dependencies. It remains as a development dependency. (#50)

Improved Documentation#

  • Added documentation to clarify the domain model of fgen and the naming choices that follow.

    The code changes required to match this documentation will be done as part of solving the following issues:

    (#64)

  • Added documentation explaining how our templating works.

    This sets out the patterns and strategies we use.

    At the time of merging, this is an intended goal rather than actually being how it works. We will update the docs over time as we do the implementation. (#85)

  • Wrote docs describing how are wrapper builders and strategies will fit together. (#88)

Trivial/Internal Changes#

fgen v0.3.1 (2024-01-25)#

Improvements#

  • Add support for installing the fortran library via CMake (#41)

Improved Documentation#

  • Added notebooks about subtleties of solving models in terms of input interpolation and flux handling (#40)

Trivial/Internal Changes#

fgen v0.3.0 (2024-01-19)#

Features#

  • Add colour to logging and switch to loguru for internal logging handling (#23)

  • Add searchsorted and is_monotonic functions to the fgen_utils module (#30)

  • Add support for evaluating an initial-value problem IVP on specified timesteps via the t_eval parameter to fgen_ivp.solve_ivp (#34)

  • Added basic conversions to character types (see fgen_char_conversions) (#37)

  • Added a Euler forward solver (see fgen_euler_forward) (#38)

Improvements#

  • Adds Fortran-based unit tests using the test-drive framework (#21)

  • Added writing of __str__ method on generated classes to provide a quick way to get more information about the Fortran values from Python (#24)

  • Introduce fgen_runtime.exceptions.PointerArrayConversionError to provide more specific context when the error raised is related to conversion from a pointer to an array.

    This context is then used to provide more information when creating a generated object’s __str__ representation. (#26)

Bug Fixes#

  • Resolved the incorrect calculation of the initial step size for the rk4 solver alongside some refactoring that aimed to improve the readability of solve_ivp. (#29)

  • Fixed bugs leftover from !34

    Bugs relate to stopping conditions when solving and correctly handling t_eval steps. (#37)

Trivial/Internal Changes#

fgen v0.2.1 (2023-12-07)#

Bug Fixes#

  • Wrapping of a class with more than one method (previously the methods would not have a newline between them so the generated code was not syntactically correct) (#22)

fgen v0.2.0 (2023-12-06)#

Breaking Changes#

  • Refactor the fortran data type parsing module to extract additional information about a fortran type. While this change is breaking to the API of fgen, it doesn’t impact the generated wrappers. (#9)

  • Updates to names to better reflect the part of the Fortran specification that are being captured:

    • fgen.fortran_parsing.SUPPORTED_TYPE_DECLARATION –> fgen.fortran_parsing.SUPPORTED_TYPE_SPECIFICATIONS

    • fgen.fortran_parsing.SUPPORTED_TYPE_ATTRIBUTES –> fgen.fortran_parsing.SUPPORTED_ATTRIBUTE_SPECIFICATIONS

    • fgen.fortran_parsing.FortranDataType.type_declaration -> fgen.fortran_parsing.FortranDataType.type_specification

    • fgen.fortran_parsing.FortranDataType.attributes -> fgen.fortran_parsing.FortranDataType.attribute_specifications

    • fgen.fortran_parsing.FortranDataType.fortran_type -> fgen.fortran_parsing.FortranDataType.fortran_type_attribute_declaration

    • fgen.fortran_parsing.FortranDataType.DimensionAttribute -> fgen.fortran_parsing.FortranDataType.DimensionAttributeSpecification

    • In fgen.fortran_parsing.FortranDataType.from_str, keyword argument fortran_type_declaration -> fortran_type_attribute_declaration

    (#11)

  • Improve typing offered by fgen_runtime.verify_units

    fgen_runtime.verify_units will now correctly type the functions it decorates, for example making clear that a function which receives a float will now expect a pint.Quantity as a result of being decorated. This type hinting isn’t perfect and may break, please raise an issue if it does.

    As part of this change, we have also removed fgen_runtime.units.Quantity. Please get the class from Pint instead via e.g. pint.registry.UnitRegsitry.Quantity (or ur.Quantity if you already have a unit registry object instantiated, this is likely the better chance at runtime).

    The code generated by fgen has also been updated to match this new typing capability. This also caused changes to fgen’s API. FortranDataType.equivalent_python_type will now return float and int for the Fortran real and integer types rather than Quantity as was previously the case. (#20)

Features#

  • Adds the option to reference another calculator in a wrapped function.

    f2py cannot directly wrap a Fortran derived type, preventing the direct passing or returning of calculators by value in any wrapped functions. Instead, fgen can pass the model index of a calculator, which the Python module can then convert into a concrete instance. A working example can be found in the derived_type example.

    This also adds the concept of links to module configuration. These links describe other wrapped modules that are dependencies. (#12)

  • Support deferred shape arrays being used as calculator attributes (#15)

Improvements#

  • Added get_instance subroutine to the manager modules. This allows for calculators to be referenced by pointers that can be retrieved using the model_index of the calculator. (#6)

  • Add support for type hints (#7)

  • Add a check to see if a valid model index was found (#8)

  • Generated code now also passes ruff’s TRY003 rule (#17)

  • Fix up order of imports in generated Python and add type hints to _UNITS (#18)

Improved Documentation#

  • Updated documentation throughout fgen.fortran_parsing (#11)

Trivial/Internal Changes#

fgen v0.1.2 (2023-07-17)#

Features#

  • Add fgen.f2py which wraps numpy.f2py, but applies some additional error handling (#5)

fgen v0.1.1 (2023-07-14)#

Improvements#

  • Add support for Python v3.9 (#3)

Improved Documentation#

  • Migrate to using towncrier for managing the changelog (#4)

fgen v0.1.0 (2023-07-03)#

Feature#

  • Initial release