Skip to content

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:

H + O -> OH
H2 + O -> OH + H
OH + H2 -> H2O + H

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 species
  • reactions: List of all reactions
  • species_dict: Fast lookup dictionary (name → index)
  • reactions_dict: Dictionary of reactions by type
  • Mass information and elemental composition
  • file_name: The network file name
  • label: 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 identifier
  • mass: Molecular mass in atomic mass units
  • charge: 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 reaction
  • products: Species created in the reaction
  • rate: Formula for calculating reaction speed as a sympy expresssion
  • rtype: Classification (e.g. photo)
  • tmin: Minimum temperature for reaction cutoff
  • tmax: Maximum temperature for reaction cutoff
  • verbatim: User friendly representation of reaction

Rate Expressions:

Most reactions use Arrhenius-type rate laws:

\[k(T) = \alpha \left(\frac{T}{300}\right)^\beta e^{-\gamma/T}\]

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:

  1. Write a template with the $JAFF commands
  2. Process the template with the network
  3. 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:

rate[0] = k0 * sqrt(T) * n[0];
rate[1] = k1 * sqrt(T) * n[1];
rate[2] = k2 * sqrt(T) * n[2];

With CSE:

double x0 = sqrt(T);
rate[0] = k0 * x0 * n[0];
rate[1] = k1 * x0 * n[1];
rate[2] = k2 * x0 * n[2];

Enable CSE with use_cse=True:

code = cg.get_rates(use_cse=True)  # More efficient

ODEs (Ordinary Differential Equations)

Chemical networks produce systems of ODEs describing concentration changes:

\[\frac{dy_i}{dt} = \sum_j \nu_{ij} R_j\]

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:

ode_code = cg.get_ode(ode_var="dydt", use_cse=True)

Jacobian Matrix

The Jacobian is the matrix of partial derivatives:

\[J_{ij} = \frac{\partial f_i}{\partial y_j}\]

Where \(f_i = dy_i/dt\) is the ODE for species i.

Jacobians are essential for implicit ODE solvers:

jac_code = cg.get_jacobian(jac_var="J", use_cse=True)

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:

net = Network("mynetwork.dat", errors=True)

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:

cg.get_rates(use_cse=True)  # Faster execution

3. Check Generated Code

Always review generated code before using:

code = cg.get_rates()
print(code)  # Inspect output

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:

  1. Loading Networks: Learn about network file formats
  2. Code Generation: Master code generation
  3. Template Syntax: Create custom templates
  4. 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