Technoeconomic Analysis#

This document provides comprehensive documentation for the technoeconomic analysis (TEA) capabilities in Difflow, including capital costs, operating costs, utility costs, and profitability metrics.

Table of Contents#

  1. Overview

  2. Capital Costs

  3. Utility Costs

  4. Operating Costs

  5. Profitability Analysis

  6. Cost Indices

  7. Examples


Overview#

The Difflow economics module provides comprehensive technoeconomic analysis capabilities:

difflow/economics/
├── capital.py       # Equipment cost correlations
├── utilities.py     # Utility cost models
├── opex.py          # Operating cost calculations
├── profitability.py # Financial metrics (NPV, IRR, MSP)
├── indices.py       # Cost index escalation (CEPCI)
└── insulation.py    # Pipe insulation cost and economic thickness

Key Features:

  • All functions are JAX-differentiable for optimization

  • CEPCI cost escalation for 2000-2026

  • Comprehensive equipment cost database

  • Industry-standard financial metrics


Capital Costs#

Location: difflow/economics/capital.py

Equipment Cost Correlations#

Each table entry is a CostParams in one of two published forms. Turton et al. (Table A.1, 2001 dollars) use

\[\log_{10} C = K_1 + K_2 \log_{10} S + K_3 (\log_{10} S)^2\]

and Towler & Sinnott (Table 7.2, 2010 dollars) and Seider et al. (2013 dollars) use

\[C = a + b \cdot S^n\]

Here \(C\) is the purchased cost of carbon-steel, near-ambient-pressure equipment in the entry’s base_year dollars, and \(S\) is its size. equipment_cost evaluates whichever form the entry names and escalates by CEPCI. Both forms are differentiable in \(S\). Every entry stores the size range its source fitted (S_min..S_max, in S_units). For a concrete size outside that range, equipment_cost (and so compressor_cost, vessel_cost, …) raises difflow.economics.CostRangeWarning and still returns the extrapolated cost. Sizes traced under jit/grad are not checked.

from typing import NamedTuple
from difflow.economics.capital import CostParams

class CostParams(NamedTuple):
    a: float       # Fixed cost ($), or K1
    b: float       # Scaling coefficient, or K2
    n: float       # Scaling exponent, or K3
    S_min: float   # Minimum valid size
    S_max: float   # Maximum valid size
    S_units: str   # Size units
    base_year: int # Cost basis year
    form: str = "power_law"   # or "log_quadratic"
    source: str = ""          # citation

CostParams.base_cost(S) gives the cost in base-year dollars. CostParams.exponent(S) gives the local scaling exponent \(d\ln C/d\ln S\).

Equipment Cost Databases#

The coefficients below are generated from difflow.economics.capital. Some entries borrow the nearest published row, and their source says so. For example, pfr_tube uses Turton’s jacketed non-agitated reactor and centrifugal_multistage uses the single centrifugal-pump row. packing_structured is Seider’s installed cost.

Turton’s floating-head exchanger has \(K_2 < 0\). Its cost therefore has a minimum at \(S = 10^{-K_2/2K_3} \approx 21.6\) m², inside the 10–1000 m² range, and falls as area grows below that point. The coefficients are kept as published. An area optimizer working below about 22 m² will move toward the minimum, and validate_cost_params flags this region through its negative local exponent.

Table

Entry

Form

a/K1, b/K2, n/K3

Valid size

Base year

Source

REACTOR_COSTS

cstr_jacketed

log_quadratic

4.1052, 0.532, -0.0005

0.1–35 m³

2001

Turton et al. 5e Table A.1: jacketed agitated reactor

REACTOR_COSTS

cstr_coil

log_quadratic

4.1052, 0.532, -0.0005

0.1–35 m³

2001

Turton et al. 5e Table A.1: jacketed agitated reactor (no separate coil row)

REACTOR_COSTS

pfr_tube

log_quadratic

3.3496, 0.7235, 0.0025

5–45 m³

2001

Turton et al. 5e Table A.1: jacketed non-agitated reactor (proxy for a tubular reactor)

REACTOR_COSTS

batch_reactor

log_quadratic

4.5587, 0.2986, 0.002

1–15 m³

2001

Turton et al. 5e Table A.1: autoclave

VESSEL_COSTS

pressure_vessel_vertical

log_quadratic

3.4974, 0.4485, 0.1074

0.3–520 m³

2001

Turton et al. 5e Table A.1: vertical process vessel

VESSEL_COSTS

pressure_vessel_horizontal

log_quadratic

3.5565, 0.3776, 0.0905

0.1–628 m³

2001

Turton et al. 5e Table A.1: horizontal process vessel

VESSEL_COSTS

storage_tank_atmospheric

log_quadratic

4.8509, -0.3973, 0.1445

90–30000 m³

2001

Turton et al. 5e Table A.1: API fixed-roof tank

VESSEL_COSTS

flash_drum

log_quadratic

3.4974, 0.4485, 0.1074

0.3–520 m³

2001

Turton et al. 5e Table A.1: vertical process vessel

HEAT_EXCHANGER_COSTS

shell_tube_floating

log_quadratic

4.8306, -0.8509, 0.3187

10–1000 m²

2001

Turton et al. 5e Table A.1: floating head

HEAT_EXCHANGER_COSTS

shell_tube_fixed

log_quadratic

4.3247, -0.303, 0.1634

10–1000 m²

2001

Turton et al. 5e Table A.1: fixed tube sheet

HEAT_EXCHANGER_COSTS

shell_tube_utube

log_quadratic

4.1884, -0.2503, 0.1974

10–1000 m²

2001

Turton et al. 5e Table A.1: U-tube

HEAT_EXCHANGER_COSTS

double_pipe

log_quadratic

3.3444, 0.2745, -0.0472

1–10 m²

2001

Turton et al. 5e Table A.1: double pipe

HEAT_EXCHANGER_COSTS

plate_frame

log_quadratic

4.6656, -0.1557, 0.1547

10–1000 m²

2001

Turton et al. 5e Table A.1: flat plate

HEAT_EXCHANGER_COSTS

air_cooler

log_quadratic

4.0336, 0.2341, 0.0497

10–10000 m²

2001

Turton et al. 5e Table A.1: air cooler

HEAT_EXCHANGER_COSTS

spiral_plate

log_quadratic

4.6561, -0.2947, 0.2207

1–100 m²

2001

Turton et al. 5e Table A.1: spiral plate

HEAT_EXCHANGER_COSTS

kettle_reboiler

power_law

29000, 400, 0.9

10–500 m²

2010

Towler & Sinnott 2e Table 7.2: U-tube kettle reboiler

COLUMN_COSTS

tray_column_shell

log_quadratic

3.4974, 0.4485, 0.1074

0.3–520 m³

2001

Turton et al. 5e Table A.1: tower (tray and packed)

COLUMN_COSTS

packed_column_shell

log_quadratic

3.4974, 0.4485, 0.1074

0.3–520 m³

2001

Turton et al. 5e Table A.1: tower (tray and packed)

COLUMN_COSTS

sieve_tray

log_quadratic

2.9949, 0.4465, 0.3961

0.07–12.3 m² (per tray)

2001

Turton et al. 5e Table A.1: sieve trays

COLUMN_COSTS

valve_tray

log_quadratic

3.3322, 0.4838, 0.3434

0.7–10.5 m² (per tray)

2001

Turton et al. 5e Table A.1: valve trays

COLUMN_COSTS

packing_random

log_quadratic

2.4493, 0.9744, 0.0055

0.03–628 m³

2001

Turton et al. 5e Table A.1: loose packing

COLUMN_COSTS

packing_structured

power_law

0, 10065, 1

0.1–100 m³

2013

Seider et al. 4e Sec. 16.5: corrugated-sheet stainless, $285/ft³ INSTALLED, very approximate

PUMP_COSTS

centrifugal_single

log_quadratic

3.3892, 0.0536, 0.1538

1–300 kW

2001

Turton et al. 5e Table A.1: centrifugal pump

PUMP_COSTS

centrifugal_multistage

log_quadratic

3.3892, 0.0536, 0.1538

1–300 kW

2001

Turton et al. 5e Table A.1: centrifugal pump (one row covers all centrifugal pumps)

PUMP_COSTS

reciprocating

log_quadratic

3.8696, 0.3161, 0.122

0.1–200 kW

2001

Turton et al. 5e Table A.1: reciprocating pump

PUMP_COSTS

gear

log_quadratic

3.4771, 0.135, 0.1438

1–100 kW

2001

Turton et al. 5e Table A.1: positive displacement pump

COMPRESSOR_COSTS

centrifugal

power_law

580000, 20000, 0.6

75–30000 kW

2010

Towler & Sinnott 2e Table 7.2: centrifugal compressor, driver power

COMPRESSOR_COSTS

reciprocating

power_law

260000, 2700, 0.75

93–16800 kW

2010

Towler & Sinnott 2e Table 7.2: reciprocating compressor, driver power

COMPRESSOR_COSTS

screw

log_quadratic

5.0355, -1.8002, 0.8253

18–950 kW

2001

Turton et al. 5e Table A.1: rotary compressor, fluid power

SEPARATOR_COSTS

mixer_settler

log_quadratic

4.7116, 0.4479, 0.0004

0.04–60 m³

2001

Turton et al. 5e Table A.1: mixer/settler

SEPARATOR_COSTS

centrifuge_disk

power_law

57000, 480000, 0.7

0.26–0.49 m (bowl diameter)

2010

Towler & Sinnott 2e Table 7.2: high-speed disk centrifuge

SEPARATOR_COSTS

filter_rotary

log_quadratic

4.8123, 0.2858, 0.042

0.9–300 m²

2001

Turton et al. 5e Table A.1: disc and drum filter

SEPARATOR_COSTS

extraction_column

log_quadratic

3.4974, 0.4485, 0.1074

0.3–520 m³

2001

Turton et al. 5e Table A.1: tower shell (column internals not included)

EVAPORATOR_COSTS

forced_circulation

log_quadratic

5.0238, 0.3475, 0.0703

5–1000 m²

2001

Turton et al. 5e Table A.1: evaporator, forced circulation (pumped)

EVAPORATOR_COSTS

falling_film

log_quadratic

3.9119, 0.8627, -0.0088

50–500 m²

2001

Turton et al. 5e Table A.1: evaporator, falling film

EVAPORATOR_COSTS

agitated_film

log_quadratic

5.0, 0.149, -0.0134

0.5–5 m²

2001

Turton et al. 5e Table A.1: evaporator, agitated film (scraped wall)

EVAPORATOR_COSTS

short_tube

log_quadratic

5.2366, -0.6572, 0.35

10–100 m²

2001

Turton et al. 5e Table A.1: evaporator, short tube

EVAPORATOR_COSTS

long_tube

log_quadratic

4.642, 0.3698, 0.0025

100–10000 m²

2001

Turton et al. 5e Table A.1: evaporator, long tube

SEPARATOR_COSTS

filter_press

log_quadratic

4.2756, 0.352, 0.0714

0.5–80 m²

2001

Turton et al. 5e Table A.1: plate and frame filter

SEPARATOR_COSTS

filter_leaf

log_quadratic

3.8187, 0.6235, 0.0176

0.6–235 m²

2001

Turton et al. 5e Table A.1: leaf filter

SEPARATOR_COSTS

filter_cartridge

log_quadratic

3.2107, 0.7597, 0.0027

15–200 m²

2001

Turton et al. 5e Table A.1: cartridge filter

SEPARATOR_COSTS

filter_gravity

log_quadratic

4.2756, 0.352, 0.0714

0.5–80 m²

2001

Turton et al. 5e Table A.1: gravity filter

SEPARATOR_COSTS

filter_pan

log_quadratic

4.8123, 0.2858, 0.042

0.9–300 m²

2001

Turton et al. 5e Table A.1: pan filter

SEPARATOR_COSTS

filter_table

log_quadratic

5.1055, 0.4999, 0.0001

0.9–115 m²

2001

Turton et al. 5e Table A.1: table filter

SEPARATOR_COSTS

filter_tube

log_quadratic

5.1055, 0.4999, 0.0001

0.9–115 m²

2001

Turton et al. 5e Table A.1: tube filter

DRYER_COSTS

rotary_direct

log_quadratic

3.5645, 1.1118, -0.0777

5–100 m²

2001

Turton et al. 5e Table A.1: rotary dryer, gas fired (direct heat)

DRYER_COSTS

drum

log_quadratic

4.5472, 0.2731, 0.134

0.5–50 m²

2001

Turton et al. 5e Table A.1: drum dryer

DRYER_COSTS

tray

log_quadratic

3.6951, 0.5442, -0.1248

1.8–20 m²

2001

Turton et al. 5e Table A.1: tray dryer

DRYER_COSTS

spray

power_law

410000, 2200, 0.7

400–4000 kg/h (water evaporated)

2010

Towler & Sinnott 2e Table 7.2: spray dryer, evaporation rate

CRYSTALLIZER_COSTS

forced_circulation

power_law

0, 41793.7, 0.56

9.07185–907.185 t/day (crystals)

2013

Seider et al. 4e Table 16.32: forced-circulation continuous evaporative crystallizer

CRYSTALLIZER_COSTS

draft_tube_baffled

power_law

0, 34004, 0.63

9.07185–226.796 t/day (crystals)

2013

Seider et al. 4e Table 16.32: draft-tube-baffled continuous evaporative crystallizer

CRYSTALLIZER_COSTS

batch

log_quadratic

4.5097, 0.1731, 0.1344

1.5–30 m³

2001

Turton et al. 5e Table A.1: batch crystallizer

COOLING_TOWER_COSTS

field_assembled

power_law

170000, 1500, 0.9

100–10000 L/s (circulating water)

2010

Towler & Sinnott 2e Table 7.2: cooling tower and pumps, field assembly

Outside the valid size range a correlation is an extrapolation. validate_cost_params(params, size) flags that case. It also flags a local exponent outside 0.3–1.2.

from difflow.economics.capital import reactor_cost, heat_exchanger_cost

cost = reactor_cost(volume=10.0, reactor_type='cstr_jacketed')
# ~$90,000 for a 10 m³ jacketed agitated reactor (escalated to the default target year)

cost = heat_exchanger_cost(area=50.0, hx_type='shell_tube_floating')
# ~$42,000 for a 50 m² floating-head exchanger

Pressure Adjustment (vessels):

\[F_P = \frac{(P - 1)(D + 2t)}{2SE - 1.2(P-1)} + 1\]

Where:

  • \(P\): Design pressure (barg)

  • \(D\): Diameter

  • \(t\): Wall thickness

  • \(S\): Allowable stress

  • \(E\): Weld efficiency

from difflow.economics.capital import pressure_factor_vessel, vessel_cost

F_p = pressure_factor_vessel(pressure=20.0, diameter=2.0)
base_cost = vessel_cost(volume=5.0, vessel_type='pressure_vessel_vertical')
cost_adj = base_cost * F_p

Column shells and their internals are costed separately and added:

import math
from difflow.economics.capital import column_cost

# Column shell + internals: 2 m diameter x 20 m tall, 30 sieve trays
D, H, n_trays = 2.0, 20.0, 30
shell_volume = math.pi * D**2 / 4 * H
tray_area = math.pi * D**2 / 4
cost = (column_cost(volume=shell_volume, column_type='tray_column_shell')
        + n_trays * column_cost(volume=tray_area, column_type='sieve_tray'))
from difflow.economics.capital import pump_cost, compressor_cost

pump = pump_cost(power=10.0, pump_type='centrifugal_single')
comp = compressor_cost(power=500.0, compressor_type='centrifugal')

Dryers, crystallizers, filter presses and cooling towers (issue #451):

from difflow.economics import (cooling_tower_cost, crystallizer_cost,
                               dryer_cost, separator_cost)

dryer = dryer_cost(20.0, dryer_type='drum')                    # m² of drum area
spray = dryer_cost(1000.0, dryer_type='spray')                 # kg/h water evaporated
fc = crystallizer_cost(100.0, crystallizer_type='forced_circulation')  # t crystals/day
batch = crystallizer_cost(10.0, crystallizer_type='batch')     # m³
press = separator_cost(20.0, separator_type='filter_press')    # m² filter area
tower = cooling_tower_cost(1000.0)                             # L/s circulating water
  • dryer_cost(size, dryer_type="rotary_direct", target_year): rotary_direct (gas fired, direct heat, 5–100 m²), drum (0.5–50 m²) and tray (1.8–20 m²) are Turton et al. 5e Table A.1 (2001, CEPCI 397); spray is Towler & Sinnott 2e Table 7.2 (January 2010), \(C = 410{,}000 + 2{,}200\,S^{0.7}\) with \(S\) the water evaporation rate, 400–4000 kg/h.

  • crystallizer_cost(size, crystallizer_type="forced_circulation", target_year): forced_circulation and draft_tube_baffled (continuous evaporative) are Seider et al. 4e Table 16.32 (2013, CE 567), \(C = 39{,}575\,S^{0.56}\) (10–1000) and \(C = 31{,}980\,S^{0.63}\) (10–250) with \(S\) in short tons of crystals per day; difflow takes \(S\) in metric t/day and converts the coefficient. batch is Turton Table A.1, volume 1.5–30 m³.

  • separator_cost(area, "filter_press"): Turton Table A.1 plate-and-frame filter, 0.5–80 m² of filter area. The same table gives filter_leaf, filter_cartridge, filter_gravity, filter_pan, filter_table and filter_tube. Turton prints the same constants for plate-and-frame and gravity filters, for disc/drum and pan filters, and for table and tube filters.

  • cooling_tower_cost(water_flow, tower_type="field_assembled", target_year): Towler & Sinnott 2e Table 7.2 “cooling tower & pumps”, field assembly (January 2010), \(C = 170{,}000 + 1{,}500\,S^{0.9}\) with \(S\) the circulating-water flow, 100–10,000 L/s. The cost includes the pumps.

All of these were read from secondary transcriptions of the textbook tables: the openpytea 3.2.0 correlation database (openpytea/data/cost_correlations.csv, https://pypi.org/project/openpytea/) and, for the Turton rows and the Seider crystallizer exponents and ranges, ecoana 0.0.1 (https://pypi.org/project/ecoana/). The Towler spray-dryer and cooling-tower rows have only the openpytea transcription. Each function raises ValueError for an unknown type and CostRangeWarning for a concrete size outside the valid range.

Installation Factors#

Equipment purchase cost must be multiplied by installation factors to get installed cost. InstallationFactors is a dataclass whose fields are fractions of the purchased cost; the installed cost is purchased * (1 + sum of fractions) (total_factor).

from difflow.economics.capital import InstallationFactors

# Defaults shown; every field can be overridden.
InstallationFactors(
    piping=0.35,
    instrumentation=0.20,
    electrical=0.12,
    buildings=0.15,
    yard_improvements=0.05,
    service_facilities=0.15,
    engineering=0.10,
    construction=0.10,
    contingency=0.15,
)

Typical Installation Factors by Equipment Type:

Equipment

Bare Module Factor

Heat exchangers

3.0-3.5

Pumps

3.0-4.0

Vessels

3.5-4.5

Columns

4.0-5.0

Reactors

3.5-4.5

Compressors

2.5-3.5

from difflow.economics.capital import installed_cost, installed_cost_detailed

# Quick calculation with typical factor
installed = installed_cost(purchased_cost=100000, lang_factor=3.5)  # $350,000

# Detailed breakdown
detailed = installed_cost_detailed(
    purchased_cost=100000,
    factors=InstallationFactors(
        piping=0.45,
        instrumentation=0.20,
        electrical=0.15,
        buildings=0.10,
        yard_improvements=0.05,
        service_facilities=0.10,
    )
)

Piping Cost per Metre#

For the economic-pipe-diameter problem (pipe capital against pumping energy), pipe_cost_per_length(D, C_ref, D_ref, base_year, target_year) scales an installed carbon-steel pipe cost per metre with the inside diameter as a piecewise power law,

\[ C(D) = C_\mathrm{ref}\,\frac{g(D)}{g(D_\mathrm{ref})}\, \frac{\mathrm{CEPCI}_\mathrm{target}}{\mathrm{CEPCI}_\mathrm{base}},\qquad g(D) = (D/1\,\mathrm{in})^{n},\quad n = 1.0\ (D<1\,\mathrm{in}),\ 1.5\ (D>1\,\mathrm{in}) \]

the exponents behind the classical optimum-diameter formulas (Genereaux, 1937, Ind. Eng. Chem. 29, 385; Peters, Timmerhaus and West, Plant Design and Economics for Chemical Engineers, 5e, Ch. 12, whose turbulent optimum \(D_\mathrm{opt}\propto q^{0.45}\rho^{0.13}\) follows from \(n = 1.5\); PIPE_COST_EXPONENTS). pipe_cost_nps(nps, schedule, C_ref=..., nps_ref=..., base_year=...) does the same for ASME B36.10M sizes and adds the schedule by metal mass: the cost of a heavier wall at the same NPS is the reference schedule’s cost times pipe_weight_per_length(nps, schedule) / pipe_weight_per_length(nps, schedule_ref) (B36.10M plain-end weight, \(W = 0.0246615\,(OD-t)\,t\) kg/m with OD and t in mm). That treats all of the installed cost as proportional to the steel; erection labour grows more slowly with wall thickness, so heavy schedules come out somewhat high.

The absolute cost level is an input. C_ref is the installed \(/m of one reference size in `base_year` dollars, from a quote, a company database or a textbook table; difflow does not ship a \)/m figure because none could be checked against its source. Only the size scaling and the CEPCI escalation come from the library.

import jax, math
import difflow.economics as econ
from difflow import Pipe, PipeParams

def annual_cost(D, Q=0.02, L=1000.0):
    capital = 0.20 * L * econ.pipe_cost_per_length(D, C_ref=150.0, D_ref=0.0525,
                                                   base_year=2019)   # your quote
    pipe = Pipe(PipeParams(L=L, D=D, rho=998.2, mu=1.002e-3))
    dP = pipe.hydraulics_at_flow(Q)["dP"]
    return capital + dP * Q / 0.7 / 1000.0 * 8000.0 * 0.10           # $0.10/kWh, 8000 h

jax.grad(annual_cost)(0.15)        # d(annual cost)/dD: zero at the economic diameter
econ.pipe_cost_nps(4, "80", C_ref=150.0, nps_ref=2, base_year=2019, length=120.0)

When the model was written, the weights were checked against B36.10M (2-in Sch 40 5.44 kg/m, 4-in Sch 80 22.32 kg/m), along with the exponents, the escalation, and that the optimum of this cost scales as \(Q^{0.40\text{-}0.48}\).

Total Capital Investment#

Total Capital Investment (TCI) includes all costs to build a functioning plant.

from difflow.economics.capital import (
    total_capital_investment,
    total_capital_investment_detailed,
    CapitalInvestment
)

Capital Investment Breakdown:

Direct Costs (DC):
├── Equipment Purchase (ISBL)
├── Installation
├── Piping
├── Instrumentation
├── Electrical
├── Buildings
├── Site Development
└── Auxiliary Facilities

Indirect Costs (IC):
├── Engineering & Supervision (10-15% DC)
├── Construction & Contractor's Fee (5-15% DC)
└── Contingency (10-20% DC)

Fixed Capital Investment (FCI) = DC + IC

Working Capital (WC) = 10-20% FCI

Total Capital Investment (TCI) = FCI + WC
# Quick estimate from equipment costs
equipment_costs = {
    'reactor': 500000,
    'column': 800000,
    'heat_exchangers': 300000,
    'pumps': 100000
}

tci = total_capital_investment(sum(equipment_costs.values()))
# Lang factor approach: FCI = 4.74 x equipment cost, TCI = FCI + 15% working capital

# Detailed breakdown
result = total_capital_investment_detailed(
    equipment_costs=equipment_costs,
    factors=InstallationFactors(),   # piping, instrumentation, ... as fractions
    working_capital_fraction=0.15
)

print(f"Direct costs: ${result.total_direct_costs:,.0f}")
print(f"Indirect costs: ${result.total_indirect_costs:,.0f}")
print(f"Fixed capital: ${result.fixed_capital_investment:,.0f}")
print(f"Working capital: ${result.working_capital:,.0f}")
print(f"Total capital: ${result.total_capital_investment:,.0f}")

Pipe Insulation#

Location: difflow/economics/insulation.py

insulation_installed_cost(D_o, thickness, cost_per_m3, cost_per_m2=0, cost_per_m=0, base_year=None, target_year=None) is the installed cost per metre of pipe, linear in the insulated volume \(\pi t(D_o+t)\) with optional cladding-area and per-metre terms, escalated by CEPCI when both years are given. economic_insulation_thickness(...) minimises the annualised insulation cost (capital recovery factor) plus the cost of the heat lost (heat_transfer.insulated_pipe_heat_loss), and is differentiable in every input. The unit cost is a required input with no default: no published coefficient could be checked (see Economic insulation thickness).


Utility Costs#

Location: difflow/economics/utilities.py

Utility Prices#

from difflow.economics.utilities import UtilityPrices, DEFAULT_PRICES

# UtilityPrices is a dataclass; heat utilities are priced in $/GJ.
UtilityPrices(
    steam_high_pressure=14.05,    # $/GJ, 4.1 MPa
    steam_medium_pressure=11.80,  # $/GJ, 1.1 MPa
    steam_low_pressure=9.50,      # $/GJ, 0.34 MPa
    cooling_water=0.35,           # $/GJ
    chilled_water=4.50,           # $/GJ
    refrigeration_moderate=8.00,  # $/GJ, -20 C
    refrigeration_low=13.50,      # $/GJ, -50 C
    cryogenic=35.00,              # $/GJ, below -100 C
    electricity=0.07,             # $/kWh
    natural_gas=4.50,             # $/GJ
    fuel_oil=6.00,                # $/GJ
    coal=2.50,                    # $/GJ
    process_water=0.50,           # $/m³
    boiler_feed_water=2.50,       # $/m³
    wastewater_treatment=0.80,    # $/m³
    compressed_air=0.03,          # $/Nm³
    nitrogen=0.08,                # $/Nm³
    oxygen=0.12,                  # $/Nm³
)

# Regional presets: 'us_gulf_coast', 'us_midwest', 'europe_west',
# 'asia_pacific', 'china'
prices_eu = UtilityPrices.from_region('europe_west')

DEFAULT_PRICES is the US Gulf Coast instance. All cost functions return rates in $/s (multiply by 3600 * hours_per_year for an annual figure).

Steam#

Steam is the primary heating utility in chemical processes.

Steam Properties:

Type

Pressure

Temperature

Latent Heat

LP

150 psig (10 barg)

185°C

~2200 kJ/kg

MP

400 psig (28 barg)

250°C

~1800 kJ/kg

HP

600 psig (41 barg)

280°C

~1500 kJ/kg

from difflow.economics.utilities import (
    steam_cost_from_duty,
    steam_flowrate_from_duty
)

# Calculate steam cost from heat duty
Q = 1e6  # 1 MW heating
cost_per_s = steam_cost_from_duty(Q, steam_level='low_pressure')   # $/s
annual_cost = cost_per_s * 8000 * 3600                             # $/year

# Calculate steam flowrate
steam_rate = steam_flowrate_from_duty(Q, steam_level='low_pressure')  # kg/s

Equations:

\[\dot{m}_{steam} = \frac{Q}{\Delta H_{vap}}\]
\[C_{steam} = Q \cdot P_{steam} \cdot t_{operation}\]

with the steam price \(P_{steam}\) in \(/GJ and the duty \)Q$ in GJ/s.

Cooling Water#

from difflow.economics.utilities import (
    cooling_water_cost,
    cooling_water_flowrate
)

# Cooling water for 500 kW duty with 10°C rise
Q = 500000  # W
cost_per_s = cooling_water_cost(Q, delta_T=10.0)       # $/s
annual_cost = cost_per_s * 8000 * 3600                 # $/year

# Required flowrate
cw_rate = cooling_water_flowrate(Q, delta_T=10.0)      # kg/s

Equations:

\[\dot{m}_{cw} = \frac{Q}{C_p \cdot \Delta T}\]

Assuming \(C_p = 4.18\) kJ/kg/K for water.

Electricity#

from difflow.economics.utilities import (
    electricity_cost,
    electricity_cost_per_second,
    pump_electricity_cost,
    compressor_electricity_cost
)

# Cost rate from power in kW ($/h)
cost_per_hour = electricity_cost(power=1000.0)  # $70/h at $0.07/kWh

# Cost rate from continuous power in W ($/s)
cost_rate = electricity_cost_per_second(power=100000)

# Pump electricity (includes efficiency), $/s
pump_cost_rate = pump_electricity_cost(
    flowrate=0.01,       # m³/s
    head=50.0,           # m
    efficiency=0.75,
)

# Compressor electricity (isentropic), $/s
comp_cost_rate = compressor_electricity_cost(
    flowrate=10.0,       # mol/s
    pressure_ratio=3.0,
    efficiency=0.80,
)

Pump Power:

\[P_{pump} = \frac{\rho g Q H}{\eta}\]

Compressor Power (isentropic):

\[P_{comp} = \frac{n}{n-1} P_1 Q_1 \left[\left(\frac{P_2}{P_1}\right)^{(n-1)/n} - 1\right] / \eta\]

Refrigeration#

For sub-ambient cooling:

from difflow.economics.utilities import (
    refrigeration_cost,
    refrigeration_cost_continuous
)

# Cost depends on temperature level ($/s)
cost = refrigeration_cost(
    100000,                    # 100 kW cooling
    temperature_level=-20.0,   # °C
)

# Smooth, differentiable version of the temperature dependence
cost_c = refrigeration_cost_continuous(100000, temperature=-20.0)

Refrigeration Cost Factor:

Lower temperatures require more compression work, increasing cost. The price per GJ of duty steps up with the temperature level:

Temperature level

Price field

Default ($/GJ)

5°C and above

chilled_water

4.50

-20°C to 5°C

refrigeration_moderate

8.00

-50°C to -20°C

refrigeration_low

13.50

below -50°C

cryogenic

35.00

Combined Utility Costs#

from difflow.economics.utilities import (
    utility_cost_from_heat_duties,
    total_utility_cost,
    UtilityConsumption,
)

# Total utility cost from the summed heating and cooling duties
heating = 500000 + 300000       # W, sum of the heaters
cooling = 400000 + 600000       # W, sum of the coolers (positive)

cost_per_s = utility_cost_from_heat_duties(
    heating_duty=heating,
    cooling_duty=cooling,
    steam_level='medium_pressure',
    prices=DEFAULT_PRICES,
)
print(f"Utilities: ${float(cost_per_s) * 8000 * 3600:,.0f}/year")

# Or describe all consumption in one container
consumption = UtilityConsumption(
    heating_duty=heating, cooling_duty=cooling, electricity=200.0,  # kW
)
total = total_utility_cost(consumption, DEFAULT_PRICES)   # $/s
print(f"Total utilities: ${total * 8000 * 3600:,.0f}/year")

Operating Costs#

Location: difflow/economics/opex.py

Raw Materials#

from difflow.economics.opex import (
    RawMaterial,
    raw_material_cost,
    total_raw_material_cost,
    annual_raw_material_cost,
)

# RawMaterial is a dataclass:
#   RawMaterial(name, price [$/kg], consumption_rate [kg/s], molecular_weight [g/mol])
methanol = RawMaterial('methanol', price=0.40, consumption_rate=0.3, molecular_weight=32.04)
# Define raw materials as name -> (flowrate [kg/s], price [$/kg])
materials = {
    'methanol': (0.30, 0.40),
    'oxygen': (0.15, 0.05),
    'catalyst': (3e-5, 50.0),
}

rm_rate = total_raw_material_cost(materials)          # $/s
total_rm_cost = annual_raw_material_cost(materials)   # $/year (default schedule)
print(f"Annual raw material cost: ${total_rm_cost:,.0f}")

From Molar Quantities:

from difflow.economics.opex import raw_material_cost_molar

# Cost from molar flow rates
cost_per_s = raw_material_cost_molar(
    molar_flowrate=100.0,    # mol/s
    price=0.40,              # $/kg
    molecular_weight=32.04,  # g/mol
)
annual = cost_per_s * 8000 * 3600   # $/year

Labor#

from difflow.economics.opex import (
    LaborRates,
    DEFAULT_LABOR_RATES,
    operating_labor_cost,
    labor_cost_from_equipment
)

# LaborRates is a dataclass (defaults shown)
LaborRates(
    operator_salary=65000.0,     # $/year per operator
    supervisor_salary=90000.0,   # $/year per supervisor
    overhead_factor=1.4,         # benefits, taxes, etc.
    supervisor_ratio=0.2,        # supervisors per operator
)

Labor Estimation Methods:

  1. Direct calculation:

labor_cost = operating_labor_cost(
    n_operators_per_shift=6,
    n_shifts=3,              # 3 shifts/day; uses a 4.5x coverage factor for 24/7
    rates=DEFAULT_LABOR_RATES,
)
  1. From equipment count (correlation):

# Rough estimate: 1 operator per 2-4 major equipment items
labor_cost = labor_cost_from_equipment(
    n_major_equipment=20,
    process_complexity='medium',  # 'simple', 'medium' or 'complex'
    rates=DEFAULT_LABOR_RATES,
)

Overhead and Maintenance#

from difflow.economics.opex import (
    OverheadFactors,
    maintenance_cost,
    insurance_taxes_cost,
    plant_overhead_cost
)

# OverheadFactors is a dataclass of fractions (defaults shown):
#   maintenance=0.04, property_taxes=0.02, insurance=0.01     (of FCI)
#   supervision=0.25, laboratory=0.10, plant_overhead=0.60    (of operating labor)
#   general_admin=0.05, distribution_selling=0.05, research_dev=0.03
#                                                            (of manufacturing cost)
OverheadFactors()

Typical Factors:

Item

Basis

Default

Maintenance

FCI

4%

Insurance

FCI

1%

Property taxes

FCI

2%

Supervision + laboratory + plant overhead

operating labor

95%

FCI = 50e6  # $50M fixed capital

maintenance = maintenance_cost(FCI, OverheadFactors(maintenance=0.03))  # 3% of FCI = $1.5M
taxes_ins = insurance_taxes_cost(FCI)   # (2% property tax + 1% insurance) of FCI
overhead = plant_overhead_cost(500000)  # supervision + lab + overhead on $500k labor

Total Operating Cost#

from difflow.economics.opex import (
    calculate_opex,
    simple_opex,
    OperatingCostBreakdown,
    com_from_correlations
)

# Detailed OPEX calculation; maintenance, taxes/insurance and overhead
# are derived from the fixed capital and labor cost
opex = calculate_opex(
    raw_material_cost=4e6,   # $/year
    utility_cost=2e6,        # $/year
    fixed_capital=FCI,       # $
    operating_labor=1e6,     # $/year (omit to estimate from operators/shift)
)

print(f"Total manufacturing cost: ${opex.manufacturing_cost:,.0f}/year")
print(f"Total production cost: ${opex.total_production_cost:,.0f}/year")

# Quick estimate: variable costs + fractions of FCI
simple = simple_opex(raw_materials=4e6, utilities=2e6, fixed_capital=FCI)
print(f"Estimated OPEX: ${simple:,.0f}/year")

Cost of Manufacturing (COM) correlation:

\[COM = 0.18 \cdot FCI + 2.73 \cdot C_{OL} + 1.23 \cdot (C_{UT} + C_{RM})\]

Where:

  • \(FCI\): Fixed capital investment

  • \(C_{OL}\): Operating labor cost

  • \(C_{UT}\): Utility cost

  • \(C_{RM}\): Raw material cost

com = com_from_correlations(
    fixed_capital=50e6,
    utility_cost=2e6,
    raw_material_cost=4e6,
    n_operators_per_shift=4,   # sets the operating labor term
)

Operating Schedule#

from difflow.economics.opex import (
    OperatingSchedule, DEFAULT_SCHEDULE, HOURS_PER_YEAR, SECONDS_PER_YEAR
)

# OperatingSchedule is a dataclass (defaults shown)
OperatingSchedule(
    hours_per_year=8000.0,   # ~91% availability
    shifts_per_day=3,
    days_per_week=7.0,
    weeks_per_year=50.0,
    stream_factor=0.91,
)

# Constants: HOURS_PER_YEAR = 8000, SECONDS_PER_YEAR = 28_800_000

Profitability Analysis#

Location: difflow/economics/profitability.py

Financial Parameters#

from difflow.economics.profitability import FinancialParams

# FinancialParams is a dataclass (defaults shown)
FinancialParams(
    discount_rate=0.10,             # 10% WACC
    tax_rate=0.21,                  # 21% corporate tax
    depreciation_years=10,          # MACRS 10-year
    plant_life=20,                  # 20-year project life
    construction_years=2,           # 2-year construction
    salvage_fraction=0.05,          # 5% salvage value
    working_capital_fraction=0.15,  # 15% of FCI
    inflation_rate=0.02,            # 2% annual inflation
)

Time Value of Money#

from difflow.economics.profitability import (
    present_value,
    future_value,
    discount_factor,
    capital_recovery_factor,
    present_value_factor
)

# Present value of future cash
PV = present_value(future_value=1e6, rate=0.10, years=10)  # $385,543

# Future value of present cash
FV = future_value(present_value=1e6, rate=0.10, years=10)  # $2,593,742

# Discount factor
df = discount_factor(rate=0.10, year=10)  # 0.3855

# Capital recovery factor (annuity payment per $ of principal)
crf = capital_recovery_factor(rate=0.10, years=20)  # 0.1175

# Present value factor (sum of discount factors)
pvf = present_value_factor(rate=0.10, years=20)  # 8.514

Equations:

\[PV = \frac{FV}{(1+r)^n}\]
\[FV = PV \cdot (1+r)^n\]
\[CRF = \frac{r(1+r)^n}{(1+r)^n - 1}\]
\[PVF = \frac{1 - (1+r)^{-n}}{r}\]

Depreciation#

from difflow.economics.profitability import MACRS_SCHEDULES

# Modified Accelerated Cost Recovery System (US tax code)
# (jnp arrays keyed by recovery period: 5, 7, 10 and 15 years)
print(MACRS_SCHEDULES[5])   # [0.2, 0.32, 0.192, 0.1152, 0.1152, 0.0576]

Annual Depreciation:

\[D_t = FCI \cdot MACRS_t\]

Tax Savings from Depreciation:

\[\text{Tax Savings} = D_t \cdot \tau\]

Where \(\tau\) is the tax rate.

Net Present Value (NPV)#

from difflow.economics.profitability import (
    npv,
    npv_with_construction
)

import jax.numpy as jnp

# Simple NPV: cash flows for years 1..N, investment at year 0
cash_flows = jnp.array([10e6, 12e6, 14e6, 14e6, 14e6])
npv_value = npv(cash_flows, discount_rate=0.10, initial_investment=50e6)

# NPV with construction period
npv_value = npv_with_construction(
    operating_cash_flows=jnp.full(20, 10e6),   # 20 operating years
    capital_investment=50e6,
    discount_rate=0.10,
    construction_years=2,
)

Equation:

\[NPV = \sum_{t=0}^{n} \frac{CF_t}{(1+r)^t}\]

Decision Criteria:

  • NPV > 0: Accept project

  • NPV < 0: Reject project

  • NPV = 0: Indifferent (earns exactly the required return)

Internal Rate of Return (IRR)#

from difflow.economics.profitability import irr, irr_approx

# IRR calculation (Newton solve via optimistix)
cash_flows = jnp.array([10e6, 12e6, 14e6, 14e6, 14e6])
irr_value = irr(cash_flows, initial_investment=50e6)

# Quick approximation (linear interpolation between two trial rates)
irr_approx_value = irr_approx(jnp.full(20, 10e6), initial_investment=50e6)

Definition: IRR is the discount rate where NPV = 0:

\[0 = \sum_{t=0}^{n} \frac{CF_t}{(1+IRR)^t}\]

Decision Criteria:

  • IRR > WACC: Accept project

  • IRR < WACC: Reject project

Typical IRR Targets:

Risk Level

Target IRR

Low (expansion)

15-20%

Medium (new product)

20-30%

High (new technology)

30-50%

Payback Period#

from difflow.economics.profitability import (
    simple_payback,
    discounted_payback
)

# Simple payback (no discounting)
payback = simple_payback(annual_cash_flow=10e6, initial_investment=50e6)  # 5 years

# Discounted payback (accounts for time value)
d_payback = discounted_payback(
    cash_flows=jnp.full(20, 10e6),
    initial_investment=50e6,
    discount_rate=0.10,
)  # ~7.3 years

Equations:

\[\text{Simple Payback} = \frac{I_0}{CF_{annual}}\]
\[\text{Discounted Payback}: \text{Find } n \text{ where } \sum_{t=1}^{n} \frac{CF_t}{(1+r)^t} = I_0\]

Return on Investment (ROI)#

from difflow.economics.profitability import roi, average_roi

# Simple ROI
roi_value = roi(annual_profit=8e6, total_investment=50e6)  # 16%

# Average ROI over project life
avg_roi = average_roi(
    total_profit=160e6,     # Sum over 20 years
    total_investment=50e6,
    years=20,
)

Equation:

\[ROI = \frac{\text{Annual Net Profit}}{\text{Total Investment}} \times 100\%\]

Minimum Selling Price (MSP)#

MSP is the product price required to achieve a target financial return.

from difflow.economics.profitability import (
    minimum_selling_price,
    msp_with_target_roi,
    msp_with_npv_zero
)

# Simple MSP (break-even)
msp = minimum_selling_price(
    total_annual_cost=40e6,  # $/year
    annual_production=1e7     # kg/year
)  # $4.00/kg

# MSP for target ROI
msp_roi = msp_with_target_roi(
    total_annual_cost=40e6,
    annual_production=1e7,
    total_investment=50e6,
    target_roi=0.20,
)  # $5.00/kg

# MSP for NPV = 0 (most rigorous)
msp_npv = msp_with_npv_zero(
    opex=35e6,
    capex=50e6,
    annual_production=1e7,
    discount_rate=0.10,
    plant_life=20,
)

Equation (NPV = 0):

\[MSP = \frac{OPEX + \left(CAPEX + WC - PV(WC)\right) / PVF}{Q}\]

where \(WC\) is the working capital (a fraction of CAPEX), recovered at the end of the plant life, and \(PVF\) is the present value factor of the annuity. This is a pre-tax breakeven price.

Cash Flow Analysis#

from difflow.economics.profitability import (
    FinancialParams,
    generate_cash_flows,
    full_cash_flow_analysis,
    CashFlowResult
)

# Generate year-by-year cash flows
params = FinancialParams(
    discount_rate=0.10, tax_rate=0.21, depreciation_years=10, plant_life=20,
)
cash_flows = generate_cash_flows(
    capital_investment=50e6,
    annual_revenue=45e6,
    annual_opex=30e6,
    params=params,
)

# Full analysis (adds working capital recovery and salvage value)
result = full_cash_flow_analysis(
    capital_investment=50e6,
    annual_revenue=45e6,
    annual_opex=30e6,
    params=params,
)

print(f"NPV: ${result.npv:,.0f}")
print(f"IRR: {result.irr:.1%}")
print(f"Payback: {result.payback:.1f} years")

Cash Flow Components:

Revenue
- Operating Costs
- Depreciation
= Taxable Income
- Taxes (21%)
= Net Income
+ Depreciation (add back)
= Operating Cash Flow

Year 0: -Capital Investment
Years 1-n: Operating Cash Flow
Year n: + Working Capital + Salvage Value

Sensitivity Analysis#

from difflow.economics.profitability import npv_sensitivity

# NPV sensitivity to parameter changes
sensitivities = npv_sensitivity(
    base_case={'capital': 50e6, 'revenue': 45e6, 'opex': 30e6, 'discount_rate': 0.10},
    parameter_ranges={
        'capital': (40e6, 60e6),     # +/- 20%
        'revenue': (38e6, 52e6),     # +/- 15%
        'opex': (27e6, 33e6),        # +/- 10%
    },
    n_points=10,
)

# Identify most critical parameters
for param, (values, npvs) in sensitivities.items():
    print(f"{param}: NPV ranges from ${npvs.min()/1e6:.1f}M to ${npvs.max()/1e6:.1f}M")

Cost Indices#

Location: difflow/economics/indices.py

CEPCI (Chemical Engineering Plant Cost Index)#

The CEPCI allows escalation of historical equipment costs to current year.

from difflow.economics.indices import (
    get_cepci,
    escalate_cost,
    CEPCI_HISTORICAL
)

# CEPCI_HISTORICAL maps year -> CEPCIData(year, index, ...), 2000-2026
# (2024-2026 are estimates). Selected overall index values:
#   2000: 394.1, 2010: 550.8, 2019: 607.5, 2020: 596.2,
#   2021: 708.0, 2022: 816.0, 2023: 797.9

# Get CEPCI for specific year
cepci_2019 = get_cepci(2019)  # 607.5
cepci_2023 = get_cepci(2023)  # 797.9

Cost Escalation:

\[C_{new} = C_{old} \times \frac{CEPCI_{new}}{CEPCI_{old}}\]
# Escalate 2019 cost to 2023
cost_2019 = 1e6
cost_2023 = escalate_cost(cost_2019, base_year=2019, target_year=2023)
# $1,000,000 × (797.9/607.5) = $1,313,415

Equipment-Specific Escalation#

Different equipment types may have different cost trends:

from difflow.economics.indices import CEPCI_RATIOS

# Pre-computed ratios for common escalations
ratio_2019_to_2024 = CEPCI_RATIOS[(2019, 2024)]

# Apply to equipment cost
old_cost = 1e6
current_cost = old_cost * ratio_2019_to_2024

Inflation Factors#

For general inflation (not CEPCI):

from difflow.economics.indices import (
    inflation_factor,
    inflation_factor_continuous
)

# Discrete compounding
factor = inflation_factor(base_year=2020, target_year=2025, annual_rate=0.02)  # 1.104

# Differentiable (array-valued) form
factor_cont = inflation_factor_continuous(years=5.0, annual_rate=0.02)  # 1.104

Examples#

Complete TEA for a Methanol Plant#

import jax.numpy as jnp
from difflow.economics.capital import (
    reactor_cost, heat_exchanger_cost, column_cost, compressor_cost,
    total_capital_investment
)
from difflow.economics.utilities import utility_cost_from_heat_duties
from difflow.economics.opex import calculate_opex, raw_material_cost_molar
from difflow.economics.profitability import (
    FinancialParams, full_cash_flow_analysis, msp_with_npv_zero
)

# Plant specifications
production = 100000  # tonnes/year methanol
hours_per_year = 8000

# Equipment costs
equipment = {
    'reactor': reactor_cost(50.0, 'cstr_jacketed'),
    'distillation': column_cost(3.14 * 1.5**2 * 30.0, 'tray_column_shell'),  # 3 m dia x 30 m
    'heat_exchangers': 3 * heat_exchanger_cost(200.0, 'shell_tube_floating'),
    'compressor': compressor_cost(2000.0, 'centrifugal'),
}
total_equipment = sum(equipment.values())

# Capital investment
TCI = total_capital_investment(total_equipment)
FCI = TCI / 1.15  # fixed capital: TCI less the 15% working capital

# Utilities (heating and cooling duties summed, W)
heating = 8e6 + 2e6       # reboiler + feed heater
cooling = 5e6 + 6e6       # reactor cooling + condenser
utility_rate = utility_cost_from_heat_duties(heating, cooling)   # $/s
utilities = float(utility_rate) * hours_per_year * 3600          # $/year

# Raw materials
syngas_rate = raw_material_cost_molar(
    molar_flowrate=production * 1e6 / 32.04 / (hours_per_year * 3600) * 3,  # mol/s, 3 mol syngas per mol MeOH
    price=0.15,              # $/kg
    molecular_weight=10.0,   # approximate syngas MW, g/mol
)
syngas_cost = float(syngas_rate) * hours_per_year * 3600         # $/year

# Total OPEX (maintenance, taxes, insurance and overhead follow from FCI and labor)
labor = 1.5e6  # $1.5M/year
opex = calculate_opex(
    raw_material_cost=syngas_cost,
    utility_cost=utilities,
    fixed_capital=float(FCI),
    operating_labor=labor,
)

# Revenue (at $400/tonne methanol)
revenue = production * 400

# Profitability analysis
result = full_cash_flow_analysis(
    capital_investment=float(FCI),
    annual_revenue=revenue,
    annual_opex=opex.total_production_cost,
    params=FinancialParams(discount_rate=0.10, tax_rate=0.21, plant_life=20),
)

print(f"\n=== Methanol Plant TEA ===")
print(f"Production: {production:,} tonnes/year")
print(f"Total Capital Investment: ${TCI/1e6:.1f} M")
print(f"Fixed Capital: ${FCI/1e6:.1f} M")
print(f"Annual OPEX: ${opex.total_production_cost/1e6:.1f} M")
print(f"Annual Revenue: ${revenue/1e6:.1f} M")
print(f"\nProfitability Metrics:")
print(f"  NPV (10%): ${result.npv/1e6:.1f} M")
print(f"  IRR: {result.irr:.1%}")
print(f"  Payback: {result.payback:.1f} years")

# Minimum selling price
msp = msp_with_npv_zero(
    opex=opex.total_production_cost,
    capex=FCI,
    annual_production=production * 1000,  # kg/year
    discount_rate=0.10,
    plant_life=20,
)
print(f"  MSP: ${msp:.2f}/kg = ${msp*1000:.0f}/tonne")

Optimization with Gradient-Based Methods#

All TEA functions are JAX-differentiable, enabling gradient-based optimization:

import jax
import jax.numpy as jnp
from difflow.economics.capital import reactor_cost, column_cost, total_capital_investment
from difflow.economics.profitability import npv_objective

def plant_npv(design_params):
    """NPV as function of design parameters."""
    reactor_volume, column_volume = design_params

    # Equipment costs depend on design
    equip_cost = reactor_cost(reactor_volume, 'cstr_jacketed')
    equip_cost += column_cost(column_volume, 'tray_column_shell')

    FCI = total_capital_investment(equip_cost) / 1.15

    # Operating costs depend on design (simplified)
    OPEX = 0.15 * FCI  # Rough correlation

    # Revenue depends on conversion (which depends on reactor size)
    conversion = 1 - jnp.exp(-0.1 * reactor_volume)  # Simplified kinetics
    revenue = conversion * 1e8  # Base revenue

    # npv_objective returns the NEGATIVE NPV (for minimization)
    return -npv_objective(revenue, OPEX, FCI, discount_rate=0.10, plant_life=20)

# Gradient of NPV w.r.t. design parameters
grad_npv = jax.grad(plant_npv)

# Optimization loop
design = jnp.array([10.0, 20.0])  # Initial guess
step = 0.5                        # step length in design units

for i in range(100):
    grad = grad_npv(design)
    # Normalized gradient ascent (maximize NPV); NPV is in $, so scale the step
    design = design + step * grad / (jnp.linalg.norm(grad) + 1e-12)
    design = jnp.maximum(design, 0.5)   # stay inside the correlation range

    if i % 20 == 0:
        print(f"Iteration {i}: NPV = ${plant_npv(design)/1e6:.2f}M")

Summary Tables#

Typical Equipment Costs (purchased, default target year)#

Equipment

Size

Approximate Cost

CSTR (jacketed, agitated)

10 m³

$90,000

PFR (jacketed vessel proxy)

5 m³

$15,000

Shell-tube HX (floating head)

50 m²

$42,000

Distillation column, 30 sieve trays

2 m dia × 20 m

$220,000

Centrifugal pump

10 kW

$8,000

Centrifugal compressor

500 kW driver

$2,100,000

Utility Cost Summary (2019 basis)#

Utility

Unit Cost

Typical Usage

LP Steam

$0.015/kg

Heating to 185°C

HP Steam

$0.030/kg

Heating to 280°C

Cooling Water

$0.05/m³

Cooling above 30°C

Electricity

$0.07/kWh

Pumps, compressors

Refrigeration (-20°C)

2× CW cost

Sub-ambient cooling

Financial Parameters Summary#

Parameter

Typical Value

Discount rate (WACC)

8-12%

Corporate tax rate

21% (US)

MACRS depreciation

7-10 years

Plant life

15-25 years

Construction period

2-3 years

Working capital

10-20% of FCI

Contingency

10-20% of DC