CodyFortran is a Modern Fortran framework for simulating interacting quantum many-body systems. Designed to be flexible and versatile, it enables advanced simulations of fermionic, bosonic, and mixed quantum systems with customizable potential and interaction functions. The framework supports time propagation and ground-state calculations, suitable for lattice and continuous systems across one, two, and three dimensions.
- Installation
- Usage
- Example Programs
- Capabilities
- The CodyFortran Architecture: A Deep Dive into Modularity
- Setting Up a New Simulation: A Step-by-Step Guide
- Extending CodyFortran
- Contributing
- Coding Conventions
- Abbreviations
- License
CodyFortran depends on several specialized libraries:
- EXPOKIT: Matrix exponential routines
- FFTW3: Fast Fourier Transform library
- SHTNS: Spherical Harmonic Transform library
- Fortran stdlib: Fortran standard library
- Fortran test-drive: Testing framework for Fortran
- BLAS: Basic Linear Algebra Subprograms
- LAPACK: Linear Algebra Package
- JSON-Fortran: JSON parsing capabilities for Fortran
- gsl: GNU Scientific Library (optional, for additional numerical methods)
- arpack: ARPACK library for large-scale eigenvalue problems
These dependencies can be provided in two ways:
- System-provided libraries: Using your system's package manager (if available)
- Locally compiled libraries: Built in the project's
thirdPartyfolder
Important: Fortran libraries that use modules must be compiled with the same Fortran compiler you plan to use for CodyFortran. Different compilers may produce incompatible binary interfaces, which can cause linking errors.
To build the required libraries locally:
- Configure which libraries to download by editing
thirdParty/getSources.sh:
# Set to "true" to download the library, "false" to skip
DOWNLOAD_EXPOKIT="true"
DOWNLOAD_SHTNS="true"
DOWNLOAD_STDLIB="true"
DOWNLOAD_TEST_DRIVE="true"
DOWNLOAD_JSON_FORTRAN="true"
DOWNLOAD_GSL="true"
DOWNLOAD_ARPACK="true"
# these are not needed when building with intel compilers and MKL
DOWNLOAD_FFTW3="true"
DOWNLOAD_OPENBLAS="true"- Build the libraries:
# Clone repository
git clone https://github.com/flackner/codyfortran
cd CodyFortran
cd thirdParty
# source the setBuildVars.sh file (the one in thirdParty not the top-level one)
# flags:
# -h help: display help message
# -c compiler family: gnu or intel
# -t build type: debug or release
# For third-party libraries, release build type is recommended unless
# you need to debug those libraries
source setBuildVars.sh -c gnu -t release
# Download the selected libraries (delete the old folders fetching updates)
bash getSources.sh
# Build and install the libraries
cmake -B build
cmake --build build
cmake --install buildThe libraries will be installed in the thirdParty/installation/ directory. When building CodyFortran, it will first check for libraries in the thirdParty/installation/lib/ and thirdParty/installation/lib64/ directories. If a required library isn't found there, it will search in the standard system paths.
After building the third-party libraries, it is recommended to start a new terminal session before sourcing setBuildVars.sh in the main project directory.
# Clone repository (can be skipped if already done)
git clone https://github.com/flackner/codyfortran
cd CodyFortran
# source the setBuildVars.sh file
# flags:
# -h help: display help message
# -c compiler family: gnu or intel
# -t build type: debug or release
source setBuildVars.sh -c gnu -t release
# Build and install CodyFortran
cmake -B build
cmake --build build
cmake --install build
# Run the test suite
cd build
ctest -R T_CodyFortran's console output uses ANSI color escape codes and a few non-ASCII characters. If you prefer plain ASCII (for example when redirecting output to a file) or you don't like the styled output, disable the fancy printing by setting
export CODY_PRETTY_PRINT="OFF"Per default, CODY_PRETTY_PRINT is set to "ON" in setBuildVars.sh. You can change this default behavior by editing that script.
This section shows how to run provided executables and example programs, and how their JSON inputs are paired.
All .f90 files in the app folder are automatically compiled into executable files. When running an executable, you can provide the JSON configuration input file in two ways:
- Explicit JSON file specification:
# Set the dynamic library paths (needs to be done once per terminal session)
source setBuildVars.sh -c gnu -t release
# Run the executable with the specified input file
./myProg.exe myInput.json
# Example: Run the executable with input file myProg.json in current directory
./myProg.exeThe `test/` folder contains several runnable programs together with a matching JSON configuration that shares the same base name. For example:
T_He1d_03_ImagTimePropagationTdhx.f90↔T_He1d_03_ImagTimePropagationTdhx.jsonT_FermiHubbard_04_ImagTimePropagationTdci.f90↔T_FermiHubbard_04_ImagTimePropagationTdci.json
These examples serve as excellent starting points for understanding how to set up different types of simulations using CodyFortran.
- Grids
- Lattice grids in 1D/2D/3D with periodic/hard boundaries.
- Continuous grids in 1D: equidistant and FEDVR.
- Continuous grids in 2D: polar and cartesian.
- Continuous grids in 3D: spherical (Ylm) and cartesian with radial FEDVR.
- Physics Operators
- Kinetic: different hopping schemes for lattices; laplacian for continuous.
- External potentials: Many presets (Coulomb, harmonic) and easy to extend.
- Interactions: Many presets (lattice on-site, Coulomb) and easy to extend.
- Time Evolution and Solvers
- Real-time and imaginary-time propagation.
- Various Runge–Kutta/Crank-Nicholson integrators and interface to gsl odeiv2.
- Split-step propagation schemes using several integrators for different Hamiltonian parts.
- Ground-state search via ED, imaginary time propagation or iterative scf methods.
- I/O, Analysis, and Utilities
- JSON-driven configuration mirroring the code architecture.
- Data output for densities, observables, spectra, and RDMs
- Many test programs under `test/` serve as runnable examples with matching JSON.
CodyFortran is built to support a wide array of simulation methods, each has a different definition of the state representation of the physical system. The implementation of each method is located as a subdirectory inside the `src/Method/` folder. The choice of method is configured in your JSON input file. The table below details the primary methods available.
| Category | Method | State Representation | JSON Configuration Key |
|---|---|---|---|
| Single-Body | Single Particle | Single-particle wavefunction on the grid. | "sb": {} |
| Grid-Based Many-Body | Full Expansion on Grid | Full wavefunction on a direct-product grid basis. | "gridBased": {"full": {}} |
| Geminal-Based Many-Body | Geminal Expansion | Wavefunction expanded in geminals (two-particle functions). | "gemBased": {} |
| Orbital-Based Many-Body | TDCI (Time-Dependent CI) | CI Coefficients (Dynamic) + Orbitals (Fixed) |
"orbBased": {"tdci": {}} |
| MCTDHX | CI Coefficients (Dynamic) + Orbitals (Dynamic) |
"orbBased": {"mctdhx": {}} |
|
| TDHX (Time-Dependent Hartree-Fock) | Single Slater determinant (`Orbitals are Dynamic`). | `"orbBased": {"tdhx": {}}` |
CodyFortran is a modular, extensible framework for performing quantum dynamics simulations. Its architecture is designed for maximum flexibility and clarity, allowing users to easily swap physical models, numerical methods, and computational backends by configuring a simple JSON file.
The power and extensibility of CodyFortran stem from three core architectural principles:
- Mirrored Abstraction Hierarchies: The abstraction hierarchy of the code is perfectly mirrored in both the source folder structure and the JSON configuration file structure. This creates an intuitive and predictable system where the configuration is a direct map of the code's design.
- Strict Separation of Interface and Implementation: Each module's public API (the interface) is defined in a single
M_*.f90file, completely separate from its various concrete implementations, which reside inS_*.f90files. - API via Procedure Pointers and Fabrication: A module's API functions are defined as
deferredprocedure pointers. These pointers are assigned to concrete implementations during a "Fabrication" step, where a chain of*_Fabricatecalls traverses the abstraction hierarchy to wire the final routines.
CodyFortran is organized around a clear abstraction hierarchy. The most general concepts form the "base modules" at the top level of the src/ directory (e.g., Method), which are made concrete by "child modules" in subdirectories. This hierarchy is strictly mirrored in two places:
- The Folder Structure: The nesting of folders within
src/represents specialization. - The JSON Configuration: The nesting of objects within the JSON file represents the same specialization.
This creates a one-to-one mapping that makes the system easy to navigate. The relationship between a base module and a child module is always an "is-a" relationship (like inheritance), not a "has-a" relationship (like composition).
Abstraction Hierarchy Path (src/) |
Mirrored JSON Structure | Interpretation |
|---|---|---|
Method/Mb/OrbBased/Mctdhx/ |
{"method": {"mb": {"orbBased": {"mctdhx": {}}}}} |
The method is a Mb method, which is an OrbBased method, which is an Mctdhx method. |
Because of this "is-a" logic, each base module object in the JSON file must contain exactly one child module sub-object to define its concrete type.
Every module consists of two distinct parts:
- The Interface (
M_*.f90): This is the stable, public face of the module. It defines the "contract" for what the module can do by declaring public data, types, and a set ofdeferredprocedure pointers for its API functions (e.g.,Apply,Setup). User code should only ever interact with thisM_interface. - The Implementation (
S_*.f90): These files contain the concrete algorithms that fulfill the interface's contract. There can be many different implementations for a single interface, and the choice of which one to use is made at runtime based on the JSON configuration.
This separation ensures that you can change or add new implementations without ever modifying the code that uses the module.
CodyFortran's API is not built on static function calls but on dynamically assigned procedure pointers. This dynamic binding happens during the Fabrication phase.
The process works as a chain of command down the abstraction hierarchy:
- Your program calls the
*_Fabricateroutine of a top-level base module (e.g.,Method_Fabricate). - This routine reads the JSON configuration to identify which child module was selected (e.g.,
mb). - It then calls the
*_Fabricateroutine of that child module. - This process repeats, traversing deeper into the nested folders and corresponding JSON objects until the most concrete implementation is reached.
- The final, most concrete
*_Fabricateroutine assigns its specific functions to the procedure pointers defined in the top-levelM_interface file.
This cascading fabrication process is how the framework wires the entire simulation together, connecting the abstract API calls in your program to the concrete scientific code chosen in your JSON file.
Modules whose names end in List (e.g., M_IntegratorList, M_DiagonalizerList) are containers for multiple objects. They are used for orchestration. For example, a split-step propagator needs two distinct integrators.
In the JSON, you configure multiple child objects by appending a unique key to each.
{
"integratorList": {
"rk1": {
"o2Impl": { }
},
"rk2": {
"o4Expl": { }
}
}
}The program then uses these named instances as required, for instance, by wiring rk1 to the first propagator sub-step and rk2 to the second.
This guide walks through the creation of a new simulation program, keeping the architectural principles in mind.
Start with a new Fortran program (.f90) in the app/ directory. The first step is to load the JSON configuration file, which will dictate the entire simulation's structure.
program P_MySimulation
use M_Utils_Json
! ... other modules
call Json_LoadJsonFile
end programImport the M_* interface modules needed for your simulation. You will only ever interact with these public interfaces.
use M_Grid
use M_SysKinetic
use M_Method
use M_IntegratorList
use M_PropagatorCall the *_Fabricate routine for each module. This phase reads the JSON config file, navigates the configuration hierarchy, and wires the procedure pointers to the selected S_* implementations.
call Grid_Fabricate
call SysKinetic_Fabricate
call Method_Fabricate
! For a list module, the fabricate call might take programmatic input
! to specify how the list items are used.
type(T_IntegratorList_FabricateInput) :: IntegratorListInput(2)
IntegratorListInput(1) % TimeDerivative => Method_Mb_OrbBased_TimeDerivativeOrbsLin
IntegratorListInput(2) % TimeDerivative => Method_Mb_OrbBased_TimeDerivativeCoeffsPlusOrbsNonLin
call IntegratorList_Fabricate(IntegratorListInput)
call Propagator_Fabricate()Call the *_Setup routines. This is the phase where memory is allocated and operators are constructed.
call Grid_Setup
call SysKinetic_Setup
call Method_Setup
call Propagator_SetupWith all modules fabricated and set up, you can now call the interface procedures (e.g., Propagator_Propagate) to run the simulation and generate output.
! The call to Propagator_Propagate is simple, but behind the scenes it uses the
! integrators from the IntegratorList, which in turn call the time derivatives
! provided by the Method module—all wired up during Fabricate.
call Propagator_Propagate(Method_state, t, t + timeStep)This architecture makes adding new functionality straightforward. To add a new 1D potential:
- Create a new Subfolder: Create a new folder
src/SysPotential/Linear/YourPotentialwith two files inside:M_SysPotential_Linear_YourPotential.f90andS_SysPotential_Linear_YourPotential.f90. Most of the time, you can copy an existing potential implementation and modify it. - Create an Implementation Submodule: Inside
S_SysPotential_Linear_YourPotential.f90, implement your logic and a*_Fabricateroutine that reads JSON keys (e.g.,sysPotential.linear.yourPotential.strength) and assigns theSysPotential_Applyprocedure pointer. - Register the New Type: In
src/SysPotential/Linear/S_SysPotential_Linear.f90, add anifblock to its*_Fabricateroutine to detect your new JSON object key and call your submodule's*_Fabricate. - Use it in JSON: You can now select your new potential with the structure:
"sysPotential": { "linear": { "yourPotential": { "strength": 1.0 } } }
This pattern of extending CodyFortran by creating a new folder, creating the implementation in the S_*.f90 file, registering it in the parent dispatcher, and configuring it via JSON applies universally across the framework.
This is a small project, and there is no established community. Contributions are welcome, whether they are bug fixes, improvements, or extensions. If you are interested in contributing, please feel free to open an issue or a pull request. All contributions will be considered.
To maintain consistency and readability, CodyFortran follows specific coding conventions.
Since Fortran lacks namespaces, the underscore _ is used as a namespace separator.
- Functions, Subroutines, and Modules: Use UpperCamelCase.
- Example:
Method_Fabricate,Grid_InnerProduct. - Namespaced:
Namespace_SubNamespace_FunctionName.
- Example:
- Variables: Use lowerCamelCase.
- Example:
timeStep,methodState,gridPoints. - Namespaced:
Namespace_SubNamespace_variableName(e.g.,Grid_Linear_xmin).
- Example:
Argument order in procedures follows a strict pattern:
- Output arguments (
intent(out)) - Input/Output arguments (
intent(inout)) - Input arguments (
intent(in)) - Optional Output arguments
- Optional Input/Output arguments
- Optional Input arguments
Optional Arguments: All optional arguments must have a trailing underscore _ and must be called using keyword arguments.
subroutine ExampleRoutine(fOut, fInOut, fIn, optOut_, optInOut_, optIn_)
real(R64), intent(out) :: fOut
real(R64), intent(inout) :: fInOut
real(R64), intent(in) :: fIn
real(R64), intent(out), optional :: optOut_
real(R64), intent(inout), optional :: optInOut_
real(R64), intent(in), optional :: optIn_
! ...
end subroutinePrefer the use of symbolic comparison operators for inequalities and dot-operators for equality:
- Use
<,<=,>,>= - Use
.eq.,.ne.
The codebase and JSON configuration use a number of recurring abbreviations. This list summarizes the most common ones for quick reference.
| Abbreviation | Explanation |
|---|---|
sb |
Single-Body. Method for a single particle where the wavefunction is represented directly on a grid. |
mb |
Many-Body. General category for methods simulating interacting particles (includes orbital-, geminal-, and grid-based approaches). |
gridBased |
Grid-Based Expansion. Many-body method where the full wavefunction is expanded on a direct-product grid basis. |
gemBased |
Geminal-Based Expansion. Many-body method where the wavefunction is expanded in geminals (two-particle functions). |
orbBased |
Orbital-Based Expansion. Many-body method where the wavefunction is expanded using single-particle orbitals. |
tdci |
Time-Dependent Configuration Interaction. Orbital-based method where CI coefficients evolve in time while orbitals remain fixed. |
mctdhx |
Multi-Configuration Time-Dependent Hartree-Fock for Bosons, Fermions, and mixtures.. Orbital-based method where both CI coefficients and orbitals evolve in time. |
tdhx |
Time-Dependent Hartree-Fock for Bosons, Fermions, and mixtures. Mean-field approximation where the wavefunction is a single Slater determinant (or permanent) with time-evolving orbitals. |
ci |
Configuration Interaction. Expansion of the many-body wavefunction in a basis of Slater determinants or permanents. |
rdm |
Reduced Density Matrix. Matrices representing the quantum state of a subsystem (1-body, 2-body, etc.) by tracing out other degrees of freedom. |
fedvr |
Finite-Element Discrete Variable Representation. A high-order grid discretization method combining finite elements with local DVR bases. |
ecs |
Exterior Complex Scaling. A method using a complex-valued coordinate contour to absorb outgoing wavepackets at boundaries. |
ylm |
Spherical Harmonics. Angular basis functions |
rk |
Runge–Kutta. A family of iterative methods for the approximate numerical solution of ordinary differential equations (ODEs). |
cn |
Crank–Nicolson. An implicit, unitary time-stepping scheme often used for the Schrödinger equation. |
sil |
Short Iterative Lanczos. A Krylov subspace time-propagation method based on the Lanczos algorithm. |
json |
JavaScript Object Notation. The human-readable data format used for CodyFortran configuration files. |
o4Expl |
Explicit 4th order. Explicit integrator of order 4. |
o2Impl |
Implicit 2nd order. Implicit integrator of order 2. |
fabricate |
Fabrication. The initialization phase where abstract procedure pointers are bound to concrete implementations based on JSON input. |
setup |
Setup. The initialization phase following fabrication where memory is allocated and operators are constructed. |
M_ |
Module Interface. Prefix for files defining public interfaces and deferred procedure pointers (e.g., M_Grid.f90). |
S_ |
Submodule Implementation. Prefix for files containing concrete implementations of interfaces (e.g., S_Grid_Linear_Fedvr.f90). |
ctx |
Context. A derived type holding the configuration and state for a specific subsystem or module. |
e |
Element. Generic name for an element within a container or list structure (e.g., in *List modules). |
evecs / evals
|
Eigenvectors / Eigenvalues. Common variable names for the results of a diagonalization procedure. |
bt |
Body Type. Index identifying a distinguishable particle species. |
*Q |
Query / Question. Suffix convention for logical (boolean) variables (e.g., toScreenQ means "write to screen?"). |
Distributed under the BSD 3-Clause License. See LICENSE for more information.
