Polymerization reactor modeling package using the method of moments
Project description
PyPoly Reactor + RL
pypoly-reactor 0.1.3 packages a single-file Python module that combines:
- a polymerization reactor simulator (
PolyRxn: Batch / CSTR / tubular PFR), and - reinforcement-learning (RL) operating-condition optimisation for all three reactors (Batch, CSTR, and LDPE PFR), using the same REINFORCE method.
The implementation is packaged as:
src/pypoly/pypoly.py
| Part | Contents |
|---|---|
| 1 | PolyRxn simulator — Batch, CSTR, tubular PFR (method of moments). PFR has optional beta-scission. |
| 2 | LDPE PFR RL — the research workflow (run_pfr, compute_reward, GaussianMLP, REINFORCE, exploitation, N=100 validation) via run_pfr_rl(). |
| 2B | Batch / CSTR RL — the same REINFORCE method/policy applied to the lumped reactors via run_batch_rl() / run_cstr_rl(). |
Importing the module never starts training. The module needs only
NumPy + SciPy; matplotlib is optional (plot=True).
Installation
conda create -n poly python=3.10 numpy scipy
conda activate poly
pip install pypoly-reactor
For local source distribution testing, run this from the project root instead:
pip install .
Then import:
from pypoly import PolyRxn # simulation
from pypoly import run_batch_rl, run_cstr_rl, run_pfr_rl # optimisation
The package also includes the PyPolymer logo:
import pypoly
logo = pypoly.logo_path()
print(logo)
Part A — Reactor simulation (PolyRxn)
Batch / CSTR
from pypoly import PolyRxn
model = PolyRxn(kd=0.8, kp=15.0, ktc=0.02, ktd=0.02,
ktrm=15e-3, ktrp=10e-3, kca=5e-3, f=0.8,
dH_p=-100 * 1000, Mw_mono=28.05)
R_gas = 8.3145; Mw_mono = 28.05
P_assu = 3000 * 1e5; T_assu = 150 + 273.15
phys = dict(Tc_const=200 + 273.15,
rho=Mw_mono * (P_assu / R_gas / T_assu),
Cp_ass=42.9 / Mw_mono, U_heat=400, D=0.05)
# Batch
model.set_batch_params(**phys)
res = model.run_batch(mono_0=8000.0, ini_0=50.0, CTA_0=50.0,
T_0=200 + 273.15, t_end=20.0, dt=0.01)
print("Batch:", res["Mn_final"], res["Mw_final"], res["PDI_final"])
# CSTR (adds residence time tau)
model.set_cstr_params(**phys)
res = model.run_cstr(mono_0=8000.0, ini_0=50.0, CTA_0=50.0,
T_0=200 + 273.15, tau=5.0, t_end=20.0, dt=0.01)
print("CSTR: ", res["Mn_final"], res["Mw_final"], res["PDI_final"])
The kinetic constants in these examples are illustrative test values chosen so the code runs quickly, not a calibrated LDPE data set.
PFR (with optional beta-scission)
set_pfr_params gained two optional arguments for beta-scission
(Pn -> P_{n/2} + D_{n/2}):
A_kbs beta-scission pre-exponential (default 0.0 -> disabled)
Ea_kbs beta-scission activation energy (default 0.0)
With the defaults the PFR behaves exactly as before; passing the research values
(A_kbs=1e11, Ea_kbs=130000) activates scission.
import numpy as np
from scipy.integrate import solve_ivp
from pypoly import PolyRxn
model = PolyRxn(kd=0.8, kp=15.0, ktc=0.02, ktd=0.02,
ktrm=15e-3, ktrp=10e-3, kca=5e-3, f=0.8,
dH_p=-100 * 1000, Mw_mono=28.05)
model.set_pfr_params(
L=10.0, D=0.05, D_j=0.08, v=0.5, v_c=0.5,
rho_pfr=2.4e6, Cp_pfr=42.9 / 28.05, rho_c=1000.0, Cp_c=4180.0,
A_kd=0.8, Ea_kd=0.0, A_kp=15.0, Ea_kp=0.0, A_ktc=0.02, Ea_ktc=0.0,
A_ktd=0.02, Ea_ktd=0.0, A_ktrm=15e-3, Ea_ktrm=0.0,
A_ktrp=10e-3, Ea_ktrp=0.0, A_kca=5e-3, Ea_kca=0.0,
P_ref_bar=3000.0, DV_kp=0.0,
# A_kbs=1e11, Ea_kbs=130000.0, # uncomment to enable beta-scission
)
N, NV = 50, 11
y0 = np.zeros((N, NV))
y0[:, 6] = 50.0; y0[:, 7] = 8000.0; y0[:, 8] = 50.0
y0[:, 9] = 200 + 273.15; y0[:, 10] = 200 + 273.15
ode = model.pfr_odes(ini_0=50.0, mono_0=8000.0, CTA_0=50.0,
T_0=200 + 273.15, Tc_in=200 + 273.15,
h_r=0.5, h_j=0.5, N=N)
jac = model.build_jac_sparsity(N, NV)
sol = solve_ivp(ode, (0.0, 5.0), y0.ravel(), method="BDF", jac_sparsity=jac)
print("Success:", sol.success)
print("Final state shape:", sol.y[:, -1].reshape(N, NV).shape)
PolyRxn PFR state vector (NV = 11 per node):
0-2 : live-chain moments lambda0, lambda1, lambda2
3-5 : dead-chain moments mu0, mu1, mu2
6 : initiator 7 : monomer 8 : CTA
9 : reactor temperature 10 : coolant temperature
Part B — Reinforcement-learning optimisation
A Gaussian-MLP policy is trained with REINFORCE (warm-start, moving-average baseline, Adam) to choose operating conditions that hit product targets. Every run does training -> greedy exploitation -> validation. The Batch, CSTR, and PFR runs all use the same method and policy; only the simulator, levers, and targets differ.
Batch RL
from pypoly import run_batch_rl
out = run_batch_rl(n_eps=120) # optimise [ini_0, CTA_0, t_end]
print(out["best_oc"], out["valid_result"], out["valid_ok"])
CSTR RL
from pypoly import run_cstr_rl
out = run_cstr_rl(n_eps=120) # optimise [ini_0, CTA_0, tau]
LDPE PFR RL
from pypoly import run_pfr_rl
# Heavier than Batch/CSTR (a stiff ODE solve per step). Start small.
out = run_pfr_rl(n_eps=150) # optimise [T0, ini0, Tc_in, U_heat, cta0]
Each call prints a validation table. For example run_batch_rl(n_eps=120, seed=0) gives:
Results vs targets [validation: high-fidelity validated]
X = 42.22 % [25–60] OK
Tpk = 292.3 °C [200–300] OK
Mn = 21.65 kg/mol [12–22] OK
Mw = 44.62 kg/mol [22–50] OK
PDI = 2.06 [1.5–2.5] OK
Optional learning-curve plot (requires matplotlib):
from pypoly import run_batch_rl
out = run_batch_rl(n_eps=120, plot=True) # saves rl_batch_results.png
Levers and temperature
| Reactor | Levers (action) | Dim |
|---|---|---|
| Batch | ini_0, CTA_0, t_end |
3 |
| CSTR | ini_0, CTA_0, tau |
3 |
| LDPE PFR | T0, ini_0, Tc_in, U_heat, cta0 |
5 |
CTAis the primaryMnlever (more CTA -> lowerMn).- In Batch/CSTR the rate constants are temperature-independent (only the PFR uses Arrhenius), so feed temperature is not an action. It is still scored: the exotherm raises the temperature with conversion and the reward applies a runaway-safety penalty, so the agent trades conversion against temperature.
- In the PFR,
T0andU_heatare genuine levers.
Configuring targets
Targets are dictionaries you can edit before training. Each entry is
(low, high), except temperature which is (low, high, runaway) in degrees C:
import pypoly
pypoly.BATCH_TARGETS = {
"X": (0.30, 0.55),
"T": (200.0, 290.0, 320.0),
"Mn": (15.0, 20.0), # kg/mol
"Mw": (25.0, 45.0), # kg/mol
"PDI": (1.8, 2.4),
}
out = pypoly.run_batch_rl(n_eps=120)
Model assumptions (please read)
-
Empirical high-temperature suppression term. Both PFR formulations include a term of the form
RT -= max(0, T - T_safe) * k(T_safe = 573.15 K in the plainPolyRxnPFR, 593.15 K in the RL PFR). This is retained from the original research code for numerical/operational safety and is not a first-principles heat-transfer mechanism — do not read the PFR peak temperature as being limited by jacket heat transfer alone. -
Batch/CSTR rate constants are temperature-independent.
kd, kp, ktc, ktd, ktrm, ktrp, kcaare constants in the Batch/CSTR models; only the PFR uses Arrhenius kinetics. So in Batch/CSTR the temperature evolves but does not feed back into the reaction rates. -
Two independent PFR implementations with different state ordering. The general
PolyRxnPFR and the RL PFR (run_pfr) are separate; their per-node state vectors are ordered differently:PolyRxn.pfr_odes : [l0,l1,l2, m0,m1,m2, ini, mono, CTA, T, Tc ] (CTA=8, T=9) RL run_pfr : [l0,l1,l2, m0,m1,m2, ini, mono, T, Tc, CTA] (T=8, CTA=10)
Keep this in mind if you index the raw solver output directly.
Command-line demo
python -m pypoly # Batch + CSTR optimisation (fast)
python -m pypoly pfr # LDPE PFR optimisation (slower, minutes)
python -m pypoly all # all three
Notes
- If using the installed package, import from
pypoly. If using the raw single-file module, keeppypoly.pyin the same directory as your script or onPYTHONPATH. PolyRxntemperatures are in Kelvin; RL tables report peak temperature in degrees C.Mn/Mware g/mol fromPolyRxn, kg/mol in RL output.- Beta-scission is off by default in
PolyRxn(A_kbs=0), so existing simulations are unchanged. The PFR RL workflow uses its own simulator, which already includes beta-scission with the research parameters. - Reproducibility. Every RL entry point takes a
seedargument (run_batch_rl(seed=0),run_cstr_rl(seed=0),run_pfr_rl(seed=0)); the same seed and episode count reproduce the same result. - Importing the module never starts training; call the
run_*_rl()functions explicitly. Batch/CSTR finish in seconds; the PFR run takes minutes.
License
Use this file according to the license or distribution agreement provided by the project owner.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pypoly_reactor-0.1.3.tar.gz.
File metadata
- Download URL: pypoly_reactor-0.1.3.tar.gz
- Upload date:
- Size: 275.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
048945207441596183e53a9f278282785859fe49584033eae43c143010fc2eb8
|
|
| MD5 |
1a2a75703bf332c16215af2e2c9b6b22
|
|
| BLAKE2b-256 |
e5e423d0a28b84f353aebbf4b0477893d638219cbeee0f3afae02c45fb09093f
|
File details
Details for the file pypoly_reactor-0.1.3-py3-none-any.whl.
File metadata
- Download URL: pypoly_reactor-0.1.3-py3-none-any.whl
- Upload date:
- Size: 269.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
16501557635f1d3c2a9574e5f94b44f00684e361903e5c0f2d88a7c36a24d5b2
|
|
| MD5 |
1d0831edfb668d60ad0998feae73eff2
|
|
| BLAKE2b-256 |
402a42ea4d707ed2a8bf7052d349dbbec6fd6ee2a49be61e3145861e54bc730b
|