Skip to main content
Docs

Search guides and API endpoints, for example “Idempotency-Key” or “submit job”.

    Tools · Fluids & Thermodynamics

    Reactor Network / PSR Simulator

    Solve networks of stirred reactors to steady state and map where a flame ignites and blows out as residence time changes.

    Updated November 10, 2026

    On this page

    Prices, workflows, and method papersOpen in the app

    Many combustion and process questions are about steady operation rather than a single ignition event: will a staged combustor stay lit, how much fuel converts at a given residence time, how close is a burner to blowout? Reactor Network / PSR Simulator answers them with zero-dimensional reactor networks in Cantera. It solves a network of perfectly stirred reactors (PSRs), mixers and heaters to steady state and, optionally, sweeps one reactor's residence time to find its ignition and extinction limits. It runs on CPU.

    How it works

    1. The network. Reservoirs hold fixed inlet states. Each PSR is an ideal-gas reactor that reacts its contents to steady state; mixers merge streams without reaction; heaters hold a set outlet temperature or lose heat to the environment through a U·A wall. Flow out of a reservoir is set by a mass-flow controller; every other node passes its flow on through one pressure-controlled outlet, and nodes without an outlet drain to the exhaust.
    2. Steady state. The network is integrated until it stops changing. If that fails, it is time-marched for many residence times and solved again; a point that still fails is marked not_converged rather than dropped.
    3. Residence-time sweep. One PSR's residence time τ steps across a range, each point starting from the previous solution so the reactor follows one branch of its S-curve. Shortening τ from a burning reactor finds the blowout (extinction) limit; lengthening it from a cold one finds ignition. Running both shows the hysteresis between them.
    4. Limits. A limit is a temperature jump between neighbouring sweep points larger than a set fraction of the burning-to-inlet temperature rise. It is reported as the pair of points around the jump, so it is only as precise as the grid: add points near a limit to tighten it.
    5. Conversion. For each reactor, conversion is 1 − Yout/Yin of one species, measured against the fresh feed that reaches it, so dilution by secondary air does not count as conversion.

    A plug-flow reactor can be approximated as a chain of equal PSRs in series. This is a tanks-in-series estimate, not a one-dimensional solution, and it gets closer to plug flow as segments are added.

    Inputs

    • The mechanism: built-in gri30 or h2o2 (mechanism_id), or your own Cantera YAML or single CHEMKIN file with thermodynamic data, uploaded or from an artifact. It can come from RMG Mechanism Generator, Mechanism Reduction, or Kinetic Parameter Fitter.
    • Nodes: each with an id and a type (reservoir, psr, mixer or heater). Reservoirs use the same temperature, pressure and composition fields as Mechanism Target Validator conditions. A PSR is set either by residence_time_s (only when reservoirs feed it) or by a fixed volume_m3 (when it sits downstream of another node), and starts from the burning (equilibrium) or cold (inlet) state.
    • Edges: from and to node ids. An edge out of a reservoir carries mdot_kg_s, or a fraction of the inflow when it feeds a residence-time PSR. Edges out of any other node carry no flow field. Unknown node types or keys, loops, and nodes with more than one outlet are rejected before anything runs.
    • Sweep (optional): the PSR to sweep, the τ range, the number of points, log or linear spacing, and the direction (down, up or both).

    Outputs

    FileContents
    steady_state.csvTemperature, pressure, residence time, mass flow, conversion and selected mole fractions for every reactor
    sweep.csvThe same columns for every sweep point and reactor, with the pass and status of each point
    limits.csvEach ignition or extinction limit as a τ bracket with the temperatures on either side
    plots/Temperature and conversion against residence time for the swept reactor
    summary.jsonMechanism size, inlet streams, solver settings, convergence counts and the limits

    In a workflow, every row of the steady-state and sweep tables carries its conversion, so a filter step can keep, for example, only the burning points.

    Not covered

    • Recycle loops, splitting one stream into several, and more than one environment temperature.
    • Surface chemistry, non-ideal gases, pressure drop, radiation, sprays and CFD.
    • Exact turning points: limits are brackets on the sweep grid, with no arc-length continuation.
    • Changes to the single-reactor and flame modes of Cantera.

    Build mechanisms with RMG Mechanism Generator, shrink them with Mechanism Reduction, check them against measured ignition delays and flame speeds with Mechanism Target Validator, and run single reactors or flames in Cantera.

    Run it from the API

    Submit with Submit a job and the job_type below. Price it first with Estimate job reservation cost: submitting reserves that amount from your wallet, and the charge settles at the actual runtime.

    Reactor Network / PSR Simulator reactor-network

    Job type
    reactor-network
    Hardware
    cpu (default)
    Typical runtime
    5 min on CPU

    Payload

    Provide exactly one of: mechanism, mechanism_id.

    Payload fields
    FieldTypeDescription
    mechanismfile_object
    mechanism.namestring

    Limits: min length 1

    mechanism.filenamestring

    Limits: min length 1

    mechanism.content_b64string

    Limits: min length 1

    mechanism.contentany
    mechanism.bytesany
    mechanism_idstring

    One of: "gri30", "h2o2"

    phase_namestring

    Limits: min length 1

    nodes[]requirednode[]

    Limits: min items 1, max items 8

    edges[]requirededge[]

    Limits: min items 1, max items 16

    edges.fromrequiredstring

    Limits: pattern ^[a-z][a-z0-9_]{0,31}$

    edges.torequiredstring

    Limits: pattern ^[a-z][a-z0-9_]{0,31}$

    edges.mdot_kg_snumber

    Limits: > 0, ≤ 1000

    edges.fractionnumber

    Limits: > 0, ≤ 1

    pressure_panumber

    Limits: > 0, ≤ 100000000

    environment_temperature_knumber

    Limits: > 0, ≤ 5000

    sweepsweep
    sweep.noderequiredstring

    Limits: pattern ^[a-z][a-z0-9_]{0,31}$

    sweep.tau_min_srequirednumber

    Limits: ≥ 0.000001, ≤ 10

    sweep.tau_max_srequirednumber

    Limits: ≥ 0.000001, ≤ 10

    sweep.pointsrequiredinteger

    Limits: ≥ 2, ≤ 200

    sweep.spacingstring

    Default: "log"One of: "log", "linear"

    sweep.directionstring

    Default: "down"One of: "down", "up", "both"

    conversion_speciesstring

    Limits: min length 1

    output_species[]string[]

    Limits: min items 1, max items 20

    limit_jump_fractionnumber

    Default: 0.5Limits: > 0, < 1

    steady_statesteady_state
    steady_state.max_stepsinteger

    Default: 10000Limits: ≥ 1, ≤ 100000

    steady_state.residual_thresholdnumber

    Default: 2e-9Limits: > 1e-9, ≤ 1

    steady_state.fallback_time_factornumber

    Default: 50Limits: ≥ 1, ≤ 1000

    File fields take {"name": "x.pdb", "content_b64": "…"} or a stored artifact, {"$artifact": {"id": "art-…", "port": "…"}}. Files are up to 25 MiB each.

    Example

    from cognichem_client import CogniChem
    
    client = CogniChem.from_env()  # reads COGNICHEM_API_KEY
    payload = {
        "mechanism_id": "gri30",
        "nodes": [
            {
                "id": "inlet",
                "type": "reservoir",
                "temperature_k": 300,
                "pressure_pa": 101325,
                "equivalence_ratio": 1,
                "fuel": {"CH4": 1},
                "oxidizer": {"O2": 1, "N2": 3.76},
            },
            {
                "id": "combustor",
                "type": "psr",
                "volume_m3": 0.001,
                "residence_time_s": 0.1,
                "initial": "equilibrium",
            },
        ],
        "edges": [{"from": "inlet", "to": "combustor", "fraction": 1}],
        "sweep": {
            "node": "combustor",
            "tau_min_s": 0.00001,
            "tau_max_s": 0.1,
            "points": 40,
            "spacing": "log",
            "direction": "down",
        },
        "conversion_species": "CH4",
        "output_species": ["CH4", "O2", "CO", "CO2", "H2O", "… 2 more"],
    }
    
    estimate = client.jobs.estimate(job_type="reactor-network", payload=payload, resource="cpu")
    print(f"Reserves ${estimate.cost:.2f}")
    
    job = client.jobs.submit(
        job_name="my-reactor-network-run",
        job_type="reactor-network",
        payload=payload,
        resource="cpu",
    )
    status = client.jobs.wait(job.process_id)
    if status.status == "completed":
        client.jobs.result(job.process_id, save_path=".")

    Sample data from the job catalog; long values are shortened here. Each job_name must be unique among your jobs.

    Workflow inputs

    • Kinetic mechanism · optionalYAML, CHEMKIN

    Workflow outputs

    • ArchiveZIP
    • TableCSV
    • TableCSV
    • TableCSV