Basic Concepts
What is JAFF?
JAFF (Just Another Fancy Format) is a Python library for working with chemical reaction networks. It provides tools to:
- Load chemical reaction networks from various formats
- Analyze network properties (species, reactions, elements)
- Generate optimized code for rate calculations, ODEs, and Jacobians
- Export networks to multiple programming languages (C, C++, Fortran, Python and others)
The main aim of jaff is to provide a commone interface for all common chemical network formats and it doubles down as a code generator.
Core Components
1. Chemical Networks
A chemical network describes a system of chemical species and the reactions between them.
Key Elements:
- Species: Individual chemical entities (atoms, molecules, ions)
- Reactions: Transformations between species
- Rate Coefficients: Functions that determine reaction speeds
- Stoichiometry: The ratios of reactants and products
Example Network:
This network has:
- 4 species: H, O, H2, OH, H2O
- 3 reactions
- Temperature-dependent rate coefficients
2. Network Object
The Network class represents a loaded chemical network in memory.
from jaff import Network
# Load a network file
net = Network("networks/react_COthin")
# Access properties
print(f"Species: {len(net.species)}") # Number of species
print(f"Reactions: {len(net.reactions)}") # Number of reactions
print(f"Label: {net.label}") # Network identifier
It contains:
species: List of all chemical speciesreactions: List of all reactionsspecies_dict: Fast lookup dictionary (name → index)reactions_dict: Dictionary of reactions by type- Mass information and elemental composition
file_name: The network file namelabel: A label for the network
3. Species
A species represents a single chemical entity with properties.
# Get a species
species = net.species[0]
print(f"Name: {species.name}") # e.g., "CO"
print(f"Mass: {species.mass}") # Atomic mass in amu
print(f"Charge: {species.charge}") # Electric charge
print(f"Index: {species.index}") # Position in array
Species Properties:
name: Chemical formula or identifiermass: Molecular mass in atomic mass unitscharge: Electric charge (0 for neutral, +1/-1 for ions)index: Position in the species array (for indexing)latex: Latex representation of the species
4. Reactions
A reaction describes a chemical transformation.
# Get a reaction
reaction = net.reactions[0]
print(f"Reaction: {reaction.verbatim}") # e.g., "H + O -> OH"
print(f"Type: {reaction.rtype}") # Reaction type
# Get reaction rate
k = reaction.rate
print(f"Rate: {k}")
Reaction Components:
reactants: Species consumed in the reactionproducts: Species created in the reactionrate: Formula for calculating reaction speed as a sympy expresssionrtype: Classification (e.g. photo)tmin: Minimum temperature for reaction cutofftmax: Maximum temperature for reaction cutoffverbatim: User friendly representation of reaction
Rate Expressions:
Most reactions use Arrhenius-type rate laws:
Where:
- \(\alpha\): Pre-exponential factor
- \(\beta\): Temperature exponent
- \(\gamma\): Activation energy parameter
- \(T\): Temperature in Kelvin
5. Code Generation
JAFF uses templates to generate code in multiple languages.
Template Workflow:
- Write a template with the
$JAFFcommands - Process the template with the network
- Generate code in your target language
Example Template:
// Template: rates.cpp
// $JAFF SUB nreact
const int NREACT = $nreact$;
// $JAFF END
void compute_rates(double* k, double T) {
$JAFF REPEAT idx, rate IN rates
k[$idx$] = $rate$;
$JAFF END
}
Generated Code:
const int NREACT = 127;
void compute_rates(double* k, double T) {
k[0] = 1.2e-10 * pow(T/300, 0.5);
k[1] = 3.4e-11 * exp(-500/T);
// ... more rates
}
Key Concepts
Network Files
JAFF supports multiple network file formats:
- KIDA format: From the KInetic Database for Astrochemistry
- UDFA format: From the UMIST Database for Astrochemistry
- PRIZMO format: From the PRIZMO astrochemical code
- KROME format: From the KROME package for astrochemistry
- UCLCHEM format: From the UCL Chemistry and Dust code
Array Indexing
Different languages use different indexing conventions and bracket formats:
| Language | Starting Index | Example |
|---|---|---|
| C/C++ | 0 | arr[0] |
| Python | 0 | arr[0] |
| Fortran | 1 | arr(1) |
JAFF handles these differences automatically when generating code.
Index Offsets
You can customize array indexing:
from jaff import Codegen
cg = Codegen(network=net, lang="c++")
# Use default offset (0 for C++)
code1 = cg.get_rates(idx_offset=0) # arr[0], arr[1], ...
# Use custom offset (e.g., start at 1)
code2 = cg.get_rates(idx_offset=1) # arr[1], arr[2], ...
Common Subexpression Elimination (CSE)
CSE is an optimization that reduces redundant calculations:
Without CSE:
With CSE:
Enable CSE with use_cse=True:
ODEs (Ordinary Differential Equations)
Chemical networks produce systems of ODEs describing concentration changes:
Where:
- \(y_i\): Concentration of species i
- \(R_j\): Rate of reaction j
- \(\nu_{ij}\): Stoichiometric coefficient (\(y_{i}^{c_{j}}\))
JAFF generates these ODEs automatically:
Jacobian Matrix
The Jacobian is the matrix of partial derivatives:
Where \(f_i = dy_i/dt\) is the ODE for species i.
Jacobians are essential for implicit ODE solvers:
Element Conservation
Chemical reactions conserve elements. JAFF can check conservation:
from jaff.elements import Elements
elem = Elements(net)
# Get element truth matrix (which species contain which elements)
truth_matrix = elem.get_element_truth_matrix()
# Get element density matrix (how many of each element per species)
density_matrix = elem.get_element_density_matrix()
Workflow Examples
Basic Analysis Workflow
from jaff import Network
# 1. Load network
net = Network("networks/react_COthin")
# 2. Explore species
for species in net.species:
print(f"{species.name}: {species.mass} amu")
# 3. Explore reactions
for reaction in net.reactions:
print(f"{reaction.verbatim}: {reaction.rtype}")
# 4. Get rates for further calculation and plotting
for reaction in net.reactions[:5]:
k = reaction.rate
print(f"{reaction.verbatim}: k = {k}")
Code Generation Workflow with Preprocessor
The recommended workflow uses the Preprocessor class to replace pragmas in template files:
from jaff import Network, Codegen, Preprocessor
# 1. Load network
net = Network("networks/react_COthin")
# 2. Create code generator and preprocessor
cg = Codegen(network=net, lang="cxx")
p = Preprocessor()
# 3. Generate code components
commons = cg.get_commons(idx_offset=0, definition_prefix="static constexpr int ")
rates = cg.get_rates_str(idx_offset=0, use_cse=True)
odes = cg.get_ode_str(idx_offset=0, use_cse=True)
jacobian = cg.get_jacobian_str(idx_offset=0, use_cse=True)
# 4. Define pragma replacements
replacements = {
"COMMONS": commons,
"RATES": rates,
"ODE": odes,
"JACOBIAN": jacobian,
"NUM_SPECIES": f"static constexpr int neqs = {net.get_number_of_species()};"
}
# 5. Process template file with pragma replacement
# Template file should contain pragmas like: // PREPROCESS_RATES
p.preprocess(
path="path/to/templates",
fnames=["chemistry.hpp"],
dictionaries=replacements,
comment="//", # or use "auto" for auto-detection
path_build="output/"
)
In your template file (chemistry.hpp), use pragma markers:
// PREPROCESS_COMMONS
// PREPROCESS_END
void compute_rates() {
// PREPROCESS_RATES
// PREPROCESS_END
}
void compute_ode(double* y, double* dydt) {
// PREPROCESS_ODE
// PREPROCESS_END
}
The preprocessor will replace content between // PREPROCESS_<KEY> and // PREPROCESS_END with the corresponding values from the dictionary.
### Template-Based Workflow
```python
from jaff import Network
from jaff.file_parser import Fileparser
from pathlib import Path
# 1. Load network
net = Network("networks/react_COthin")
# 2. Process template file
parser = Fileparser(net, Path("template.cpp"))
output = parser.parse_file()
# 3. Save generated code
with open("output.cpp", "w") as f:
f.write(output)
Best Practices
1. Validate Networks
Always check for errors when loading:
This enables warnings for:
- Missing sink/source species
- Duplicate reactions
- Isomer issues
- Element conservation violations
2. Use CSE for Performance
Enable CSE for production code:
3. Check Generated Code
Always review generated code before using:
4. Choose Appropriate Index Offsets
Match your target framework:
# For C/C++/Python: start at 0
code = cg.get_rates(idx_offset=0)
# For Fortran: start at 1
code = cg.get_rates(idx_offset=1)
# For custom arrays: use any offset
code = cg.get_rates(idx_offset=5)
5. Organize Generated Code
Structure your output logically:
# Generate all components
commons = cg.get_commons()
rates = cg.get_rates(use_cse=True)
odes = cg.get_ode(use_cse=True)
jac = cg.get_jacobian(use_cse=True)
# Combine in logical order
full_code = f"""
// Common definitions
{commons}
// Rate calculations
{rates}
// ODE system
{odes}
// Jacobian matrix
{jac}
"""
Next Steps
Now that you understand the basic concepts:
- Loading Networks: Learn about network file formats
- Code Generation: Master code generation
- Template Syntax: Create custom templates
- API Reference: Explore the complete API
Common Terms
| Term | Definition |
|---|---|
| Species | A chemical entity (atom, molecule, ion) |
| Reaction | A chemical transformation between species |
| Rate Coefficient | Function determining reaction speed |
| Stoichiometry | Ratio of reactants to products |
| ODE | Ordinary Differential Equation describing concentration changes |
| Jacobian | Matrix of partial derivatives of ODEs |
| CSE | Common Subexpression Elimination (optimization) |
| Template | File with JAFF commands for code generation |
| Network | Collection of species and reactions |
| Index Offset | Starting index for arrays (0 or 1) |
Further Reading
- Chemistry Background: Understanding chemical kinetics helps interpret results
- ODE Solvers: Learn about numerical integration methods
- Programming Languages: Familiarity with target languages (C++, Fortran, etc.)
- SymPy: JAFF uses SymPy for symbolic mathematics