Skip to main content

Processforge

processforge-logo

A Python-based process simulation framework for coupling different simulation engines.

Table of Contents

Features

Plan / Apply Workflow

  • pf init: Initialises the .processforge/ project directory and outputs/ folder. Run once per project.
  • pf plan: Validates the flowsheet (schema, DOF, Pint unit consistency), performs a structural diff against the last saved state (+ added, ~ modified, - removed units), and generates a Mermaid diagram — all without running the solver.
  • pf apply: Solves the flowsheet using the last converged state as a warm start. Falls back automatically to a step-wise homotopy/continuation solver if the direct Newton solve fails. Topology changes (added/removed units) trigger a cold start with a warning.
  • Snapshot Versioning: Every successful apply creates a new numbered snapshot in .pfstate/snapshots/. Previous snapshots are never deleted, enabling rollback to any prior converged design.
  • Convergence Guardrails: If both the direct solve and homotopy fail, the engine auto-reverts latest to the last good snapshot and writes a divergence debug report (*_divergence.json) with the final residual norm, drifted parameters, and solver statistics.
  • Dynamic t=0 from State: pf run (dynamic mode) automatically loads the latest .pfstate converged values as the initial conditions for time-integration, replacing arbitrary feed defaults with a physically meaningful starting point.

Core Capabilities

  • Steady-state EO (equation-oriented) and dynamic process simulations
  • Thermodynamic property calculations using CoolProp
  • Multiple unit operations for hydraulic and thermal systems
  • Flowsheet modeling with automatic recycle stream detection
  • Modelica/OMPython bridge: transpile flowsheets to native .mo source and compile to Model Exchange FMU via OpenModelica

Steady-State EO Solver

  • All unit equations assembled into a global F(x) = 0 system and solved simultaneously
  • Newton-Raphson with Armijo backtracking line search (SciPy backend)
  • Pluggable backends: SciPy (built-in), Pyomo + IPOPT (optional), CasADi (optional)
  • Recycle loops handled natively — no tear stream initialisation or Wegstein iteration needed
  • Recycle streams are auto-detected from the flowsheet graph topology

Dynamic Simulation

  • ODE time-marching for Tank-based dynamic flowsheets
  • Sequential-modular propagation with Wegstein convergence for recycle loops
  • Timeseries results for transient analysis

Results & Visualization

  • Export formats: Zarr stores containing both scalar and timeseries results for downstream analysis
  • Optional plotting (use --export-images) for temperature profiles and composition charts
  • Graphviz flowsheet diagrams (PNG and SVG)
  • Timeseries visualization for dynamic simulations

Validation & Quality

  • JSON schema validation for flowsheet configurations
  • Connectivity checks (inlet sources, unused outlets, unreachable units)
  • Comprehensive logging for debugging

Available Unit Operations

Unit Type Mode Description Key Parameters
Pump Steady-state (EO) Adds pressure rise with efficiency losses deltaP, efficiency
Valve Steady-state (EO) Isenthalpic pressure reduction pressure_ratio
Strainer Steady-state (EO) Fixed pressure drop element deltaP
Pipes Steady-state (EO) & Dynamic Laminar flow with friction losses delta_p, diameter
Tank Dynamic only Well-mixed molar tank (ODE) outlet_flow, initial_n, initial_T, P, duty
Flash Steady-state (EO, SciPy backend) Isothermal flash separator P
Heater Steady-state (EO, SciPy backend) Temperature control unit duty, flowrate

Note: Flash and Heater use CoolProp internally and are supported on the SciPy backend only. Pump, Valve, Strainer, and Pipes support all three backends (SciPy, Pyomo, CasADi).

Installation

From PyPI

# pip (core features)
pip install processforge

# uv (core features)
uv add processforge

# with CoolProp thermodynamics (required for Heater, Flash)
pip install "processforge[coolprop]"
uv add "processforge[coolprop]"

With EO solver backends (optional)

# Pyomo + IPOPT backend
pip install "processforge[eo]"
uv add "processforge[eo]"

# Pyomo + IPOPT + CasADi backend
pip install "processforge[eo-casadi]"
uv add "processforge[eo-casadi]"

# Modelica transpiler + OMPython bridge (requires OpenModelica installed separately)
pip install "processforge[modelica]"
uv add "processforge[modelica]"

From source (development)

  1. Clone the repository:

    git clone https://github.com/urjanova/processforge.git
    cd processforge
    
  2. Install the package:

    pip install -e ".[dev]"
    

    Or using uv:

    uv sync
    

Usage

Command Line Interface

Processforge provides a CLI with the following subcommands. Both processforge and the shorter alias pf are interchangeable.

Recommended workflow: init → plan → apply

# 1. Initialise project (run once per project)
pf init

# 2. Preview changes: structural diff, DOF analysis, unit consistency, Mermaid diagram
pf plan flowsheets/my-flowsheet.json

# 3. Apply changes: warm-start from last state, homotopy fallback, snapshot versioned
pf apply flowsheets/my-flowsheet.json

All commands

# Initialise .processforge/ directory and outputs/ folder
pf init [--path PATH]

# Preview changes against saved state (no solver run)
pf plan flowsheets/my-flowsheet.json [--no-diagram] [--output-dir diagrams/]

# Apply flowsheet using state-based warm start and homotopy fallback
pf apply flowsheets/my-flowsheet.json

# Run a simulation directly (steady-state or dynamic)
pf run flowsheets/my-flowsheet.json [--export-images]

# Generate a standalone flowsheet diagram
pf diagram flowsheets/my-flowsheet.json [--format svg] [--output-dir diagrams/]

# Export flowsheet as Modelica .mo and optionally compile to FMU via OMPython
pf export-modelica flowsheets/my-flowsheet.json [--output-dir modelica/] [--no-compile]

# Export flowsheet as FMI 2.0 co-simulation FMU
pf export-fmu flowsheets/my-flowsheet.json [--output-dir outputs/] [--backend scipy]

Running pf apply or pf run generates output files in the outputs/ directory:

  • *_results.zarr — Simulation results stored as a Zarr directory
  • *_validation.xlsx — Validation report derived directly from the Zarr store
  • *.pfstate/ — Versioned state store: snapshots/ directory with one Zarr group per successful apply, plus a latest pointer for rollback
  • *_divergence.json — Written when both the direct solve and homotopy fail; contains drifted parameters, final residual norm, and solver statistics

As a Python Module

from processforge import EOFlowsheet, validate_flowsheet

# Load and validate a flowsheet
config = validate_flowsheet("flowsheets/my-flowsheet.json")

# Run steady-state EO simulation (default SciPy backend)
fs = EOFlowsheet(config, backend="scipy")
results = fs.run()
# results: {stream_name: {"T": ..., "P": ..., "flowrate": ..., "z": {...}}}

# Use Pyomo + IPOPT backend (requires: pip install "processforge[eo]")
fs = EOFlowsheet(config, backend="pyomo")
results = fs.run()

# Use CasADi backend (requires: pip install "processforge[eo-casadi]")
fs = EOFlowsheet(config, backend="casadi")
results = fs.run()

Flowsheet Configuration

Flowsheets are defined as JSON files. The simulation.mode field controls which solver is used:

mode Solver Use case
"steady" (default) EO — global Newton-Raphson Steady-state without Tank units
"dynamic" SM — ODE time-marching Flowsheets containing Tank units
{
  "metadata": { "name": "My Flowsheet", "version": "2.0" },
  "materials": {
    "Water":   { "id": 1 },
    "Toluene": { "id": 2 },
    "Steel":   { "id": 3 }
  },
  "material_mixes": {
    "Water_Toluene_Mix": {
      "friendly_material_mix_id": 1,
      "percent_type": "ao",
      "components": [
        { "name": "Water",   "fraction": 0.8 },
        { "name": "Toluene", "fraction": 0.2 }
      ]
    }
  },
  "streams": {
    "feed": { "T": 298.15, "P": 101325, "flowrate": 1.0, "material_mix": 1 }
  },
  "units": {
    "pump_1":  { "type": "Pump",  "in": "feed",       "out": "after_pump", "deltaP": 200000, "efficiency": 0.75, "material": 3 },
    "valve_1": { "type": "Valve", "in": "after_pump",  "out": "product",   "pressure_ratio": 0.5,                "material": 3 }
  },
  "simulation": {
    "mode": "steady",
    "backend": "scipy"
  }
}

The optional backend key selects the EO solver backend ("scipy", "pyomo", or "casadi"). Defaults to "scipy".

Materials and composition

Stream composition can be defined in two ways:

Explicit z dict — list component mole fractions directly on the stream:

"feed": { "T": 298.15, "P": 101325, "flowrate": 1.0, "z": { "Water": 0.8, "Toluene": 0.2 } }

material_mix reference — define a reusable mix in the top-level material_mixes section and reference it by friendly_material_mix_id. The validator automatically expands the reference into a z dict before simulation:

"material_mixes": {
  "Water_Toluene_Mix": {
    "friendly_material_mix_id": 1,
    "percent_type": "ao",
    "components": [
      { "name": "Water",   "fraction": 0.8 },
      { "name": "Toluene", "fraction": 0.2 }
    ]
  }
},
"streams": {
  "feed": { "T": 298.15, "P": 101325, "flowrate": 1.0, "material_mix": 1 }
}

Rules:

  • z and material_mix are mutually exclusive on a single stream.
  • friendly_material_mix_id values must be unique across all mixes.
  • Each component name in a mix must match a key in the top-level materials section.
  • When all component fractions are provided they must sum to 1.0.

Every unit also requires a material integer field pointing to an id in the materials section (this identifies the structural material the unit is made of, separate from the fluid composition).

Recycle streams

Recycle streams require no special configuration. Any stream produced as the out of one unit can be used as the in of any other unit — including upstream units. The EO solver resolves the full coupled system simultaneously.

"units": {
  "tank_1": { "type": "Tank", "in": ["feed", "recycle"], "out": "after_tank", ... },
  "pipe_1": { "in": "after_tank", "out": "recycle", ... }
}

Quick Start Examples

New project setup

pf init
pf plan flowsheets/hydraulic-chain.json   # validate + preview diff
pf apply flowsheets/hydraulic-chain.json  # solve + save snapshot

Iterating on a design

# Edit flowsheet.json, then:
pf plan flowsheets/hydraulic-chain.json   # see what changed (+/~/-)
pf apply flowsheets/hydraulic-chain.json  # warm-start from last converged state

Run a dynamic simulation (uses pfstate as t=0 if available)

pf run flowsheets/closed-loop-chain.json

Generate a flowsheet diagram

pf diagram flowsheets/closed-loop-chain.json

Plan / Apply Workflow Detail

The plan/apply workflow mirrors Terraform's preview-before-execute model:

Step Command What happens
1 pf init Creates .processforge/config.json and outputs/. Run once.
2 pf plan Schema + DOF validation, Pint unit checks, structural diff vs. saved state, Mermaid diagram. No solver runs.
3 pf apply Loads last snapshot as warm-start x₀, runs Newton solve. Falls back to 10-step homotopy continuation if needed. Saves a new snapshot on success.

Structural diff output (pf plan)

+ compressor_2   [Pump]    (added)
~ pump_1         [Pump]    deltaP: 1e5 → 2e5
- old_valve      [Valve]   (removed)

Snapshot versioning (.pfstate/)

outputs/my-flowsheet.pfstate/
  snapshots/
    0001_2026-04-06T12:00:00Z/   ← first apply
    0002_2026-04-06T13:30:00Z/   ← second apply
  latest                          ← plain text: "0002_2026-04-06T13:30:00Z"

Each snapshot stores the converged variable vector (x), variable names, and the flowsheet config at that point in time.

Divergence guardrails

If both the direct Newton solve and the homotopy fallback fail to converge:

  1. latest is automatically reverted to the previous good snapshot.
  2. A *_divergence.json report is written with the drifted parameters, final ||F||, homotopy step history, and the last x vector for debugging.

Running on the cloud

Processforge can run simulation jobs on any cloud VM with Docker installed. Two modes are available:

  • Batch mode — run a single flowsheet directly and store results locally or on S3.
  • API server mode — start pf-serve for programmatic flowsheet submission.

Prerequisites

docker pull ghcr.io/urjanova/processforge:latest

Step-by-step: Run a flowsheet via Docker (S3 results)

This runs pf apply in the container and uploads results to an S3-compatible bucket.

  1. Prepare your flowsheet JSON — save it to a local file, e.g. ./flowsheets/my-flowsheet.json.

  2. Set S3 environment variables in your Docker run command.

  3. Run the container:

    docker run --rm \
      -v "$(pwd)/flowsheets:/app/flowsheets" \
      -e S3_ACCESS_KEY='XXXXXX' \
      -e S3_SECRET_KEY='YYYYYYYYY' \
      -e S3_ENDPOINT_URL='https://processforge-files.ams3.digitaloceanspaces.com' \
      -e S3_REGION_NAME='ams3' \
      -e S3_BUCKET_NAME='my-bucket' \
      ghcr.io/urjanova/processforge:latest \
      pf apply /app/flowsheets/my-flowsheet.json
    

    Results (Zarr store, pfstate snapshots) are uploaded to S3 under the configured bucket.

Step-by-step: Run the API server (pf-serve)

Start a persistent HTTP API that accepts flowsheet submissions:

  1. Start the server:

    docker run -d \
      --name pf-api \
      -p 9000:9000 \
      -e S3_ACCESS_KEY='XXXXXX' \
      -e S3_SECRET_KEY='YYYYYYYYY' \
      -e S3_ENDPOINT_URL='https://processforge-files.nyc .digitaloceanspaces.com' \
      -e S3_REGION_NAME='ams3' \
      -e S3_BUCKET_NAME='my-bucket' \
      ghcr.io/urjanova/processforge:latest \
      pf-serve
    
  2. Submit a flowsheet via the API:

    curl -X POST http://localhost:9000/run \
      -H "Content-Type: application/json" \
      -d @flowsheets/my-flowsheet.json
    
  3. Check job status (replace <job_id> with the ID returned by the submission response):

    curl http://localhost:9000/status/<job_id>
    
  4. Retrieve results — the results Zarr store and .pfstate snapshots are synced to your S3 bucket automatically on completion.

    (Ensure you provide your S3-compatible credentials and endpoint URL; output and results will be synced directly to the specified bucket.)

Step-by-step: Run OpenMC simulations in Docker

OpenMC simulations require nuclear cross-section data. The container's startup script (scripts/fetch_openmc_data.sh) downloads and caches these automatically.

Asset Typical size Managed via
Cross-section library (cross_sections.xml + HDF5 data) 100 MB – 5 GB OPENMC_DATA_URL

Step 1: Prepare a volume for cross-section data

You can either:

  • Use a named Docker volume (data persists across container restarts):
    docker volume create openmc_data
    
  • Use a host directory:
    mkdir -p /path/to/openmc_data
    

Step 2: Write your OpenMC flowsheet

Use environment variable expansion so the container can find the cross-sections at runtime:

{
  "providers": {
    "openmc": {
      "type": "openmc",
      "cross_sections": "${OPENMC_DATA_ROOT}/cross_sections/cross_sections.xml"
    }
  },
  "units": {
    "reactor": {
      "type": "SolverUnit",
      "provider": "openmc",
      "sim_type": "eigenvalue_csg",
      "solver_config": {
        "source_point": { "xyz": [0.0, 0.0, 0.0] },
        "point_source_material": "salt",
        "point_source_sphere_radius": 200.0,
        "mesh_tallies": [...]
      }
    }
  }
}

The startup script exports OPENMC_CROSS_SECTIONS pointing to the downloaded library, so ${OPENMC_DATA_ROOT} in your flowsheet is resolved automatically.

Step 3: Run the simulation

docker run --rm \
  -v openmc_data:/data \
  -e OPENMC_DATA_URL='https://your-host.com/endfb-viii.0-hdf5.tar.gz' \
  ghcr.io/urjanova/processforge:latest \
  pf run /app/flowsheets/openmc/msre_eigenvalue.json

The cross-sections are downloaded on first run and cached on the volume. Subsequent runs skip the download.

Step 4 (optional): Use pre-downloaded cross-sections

If you already have the library on your host machine, mount it directly and skip the download:

docker run --rm \
  -v /path/to/openmc_data:/data \
  ghcr.io/urjanova/processforge:latest \
  pf run /app/flowsheets/openmc/msre_eigenvalue.json

The container checks for cross_sections.xml at the expected location and uses it if present.

Step-by-step: pf-serve with OpenMC cross-sections (local)

Run the API server locally with OpenMC cross-section data so it can accept OpenMC flowsheet submissions.

  1. Create a named volume for cross-sections:

    docker volume create openmc_data
    
  2. Start pf-serve with the volume mounted and cross-section URL configured:

    docker run -d \
      --name pf-api \
      -p 9000:9000 \
      -v openmc_data:/data \
      -e OPENMC_DATA_URL='https://your-host.com/endfb-viii.0-hdf5.tar.gz' \
      ghcr.io/urjanova/processforge:latest \
      pf-serve
    

    The startup script downloads cross-sections to the volume on first start. The server is ready once the container logs show the download completed.

  3. Submit an OpenMC flowsheet to the API:

    curl -X POST http://localhost:9000/run \
      -H "Content-Type: application/json" \
      -d @flowsheets/openmc/msre_eigenvalue.json
    
  4. Check the job status:

    curl http://localhost:9000/status/<job_id>
    
  5. Stop the server when done:

    docker stop pf-api && docker rm pf-api
    

    Results from completed jobs are stored inside the container; mount an output directory with -v "$(pwd)/outputs:/app/outputs" if you need them on the host.

Logo credit

Google Gemini / Nano Banana

License

This project is licensed under the BSD 3-Clause License. See the LICENSE file for details.

For licensing inquiries, please contact the development team.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

processforge-0.2.30.tar.gz (141.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

processforge-0.2.30-py3-none-any.whl (165.9 kB view details)

Uploaded Python 3

File details

Details for the file processforge-0.2.30.tar.gz.

File metadata

  • Download URL: processforge-0.2.30.tar.gz
  • Upload date:
  • Size: 141.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for processforge-0.2.30.tar.gz
Algorithm Hash digest
SHA256 145e48f22363a6bab34a296f4205e2be01d95757bbc3d74b2a069b8ab6516080
MD5 91478f3faf65aeac050b88b29756df19
BLAKE2b-256 0850ccf28e9419f35f25f97b4f8f8651249206a79b154aa6eca4f8fe6705d556

See more details on using hashes here.

File details

Details for the file processforge-0.2.30-py3-none-any.whl.

File metadata

  • Download URL: processforge-0.2.30-py3-none-any.whl
  • Upload date:
  • Size: 165.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for processforge-0.2.30-py3-none-any.whl
Algorithm Hash digest
SHA256 6e2cb6f279c792788daf36ea48f432d0faf63450742e8961acadcb3ebe1b23d3
MD5 66edafafb3e18b2940506a9433957527
BLAKE2b-256 579874b047b48bd3795163094db9cc281ae25820740e686706849597afff1656

See more details on using hashes here.

Release history Release notifications | RSS feed

0.6.0

2 files

0.5.1

2 files

0.5.0

2 files

0.4.3

2 files

0.4.1

2 files

0.3.26

2 files

0.3.25

2 files

0.3.23

2 files

0.3.21

2 files

0.3.20

2 files

0.3.19

2 files

0.3.17

2 files

0.3.16

2 files

0.3.15

2 files

0.3.14

2 files

0.3.13

2 files

0.3.12

2 files

0.3.11

2 files

0.3.10

2 files

0.3.9

2 files

0.3.8

2 files

0.3.5

2 files

0.3.4

2 files

0.3.3

2 files

0.3.2

2 files

0.3.1

2 files

0.2.40

2 files

0.2.38

2 files

0.2.34

2 files

This release

0.2.30 This release

2 files

0.2.24

2 files

0.2.22

2 files

0.2.20

2 files

0.2.15

2 files

0.2.14

2 files

0.2.13

2 files

0.2.12

2 files

0.2.11

2 files

0.2.10

2 files

0.2.8

2 files

0.2.7

2 files

0.2.5

2 files

0.2.4

2 files

0.2.3

2 files

0.2.1

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page