Update:
Jo cannot reproduce the issue, I created a fresh env and still face the same issue.
I ran into a non-deterministic crash when using particle_reaction_model on a LumpedRateModelWithoutPores unit (bulk_reaction_model is not supported) for a unit without a solid phase. The exact same code, run repeatedly with no changes, sometimes succeeds and sometimes fails with:
CADETProcess.CADETProcessError.CADETProcessError: CADET Error: Simulation failed with Error (runSimulation:416) Simulation failed: Retrieving double parameter MAL_KFWD_SOLID failed
Root cause
Assigning a normal bulk MassActionLaw reaction to particle_reaction_model triggers CADET-Process auto-cast to a particle reaction. Since this unit has no solid phase, the solid-phase reaction parameters end up empty which is correctly represented as CADET-Process's own ProxyList container.
In CADET-python theparam_provider_num_elements (cadet/cadet_dll_utils.py) only recognizes list and numpy.ndarray as sized containers:
if isinstance(o, list):
return len(o)
elif isinstance(o, np.ndarray):
return o.size
return 1 # falls through here for ProxyList, even when empty
So it reports length 1 for an empty array, CADET-Core asks for index 0, and param_provider_get_double_array_item raises an unhandled IndexError on float(o[index]). That exception escapes the ctypes callback uncaught . Ctypes prints "Exception ignored..." and the output pointer (val[0]) is never written, so whatever uninitialized memory happens to be there gets read as if it were a valid value. Which probably explains the randomness?
Why this used to work: before CADET-Process's d8c6669 ("Add ProxyList/Array to allow indexed modification of aggregated properties", 2025-01-29, in every release since 0.10.1), an empty aggregated reaction array resolved to None and was dropped from the config entirely, so CADET-Core never asked for it at all. I confirmed this directly on CADET-Process 0.10.1 the identical reproducer succeeds 10/10 fresh CADET-Process runs; on every version since the ProxyList change, it fails 30-60% of the time, also reproducing identically with CADET-Process 0.11.0, 0.11.1, and 0.12.0(dev), independent of numpy version. So this is a CADET-python bug exposed by a CADET-Process feature change.
Suggested fix: in param_provider_get_double_array_item, catch IndexError/TypeError around o[index] and write a defined value instead of leaving val unset. Also worth making param_provider_num_elements recognize any object with __len__, not just list/np.ndarray.
Environment: CADET-Process 0.12.0 (also 0.11.0/0.11.1), CADET-python 1.2.0, CADET-Core (cadet conda package) 5.1.1.
MRE
This builds a 3-unit flow sheet, runs it in 10 fresh subprocesses, shows the failure rate, then shows a 3-line patch (catching the IndexError and writing a defined 0.0).
mre_particle_reaction_no_pores_uninitialized_value.py
import ctypes
import subprocess
import sys
import textwrap
import warnings
REPRODUCER_SCRIPT = textwrap.dedent("""
import warnings
from CADETProcess.processModel import (
ComponentSystem, Inlet, Outlet, LumpedRateModelWithoutPores,
MassActionLaw, FlowSheet, Process
)
from CADETProcess.simulator import Cadet
component_system = ComponentSystem(1)
inlet = Inlet(component_system, name='inlet')
column = LumpedRateModelWithoutPores(component_system, name='column')
outlet = Outlet(component_system, name='outlet')
reaction_system = MassActionLaw(component_system)
reaction_system.add_reaction(indices=[0], coefficients=[-1], k_fwd=1e-3, k_bwd=0)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
# column has no pores/bound phase, but supports_particle_reaction is True,
# so this is the documented way to attach a reaction to it -- and it is
# silently cast: bulk reaction -> particle reaction.
column.particle_reaction_model = reaction_system
flow_sheet = FlowSheet(component_system)
flow_sheet.add_unit(inlet)
flow_sheet.add_unit(column)
flow_sheet.add_unit(outlet)
flow_sheet.add_connection(inlet, column)
flow_sheet.add_connection(column, outlet)
process = Process(flow_sheet, 'mre')
column.length = 0.1
column.diameter = 0.01
column.axial_dispersion = 1e-7
column.total_porosity = 1
column.discretization.ncol = 20
inlet.flow_rate = [1e-6, 0, 0, 0]
process.cycle_time = 100
_ = process.add_event('start', 'flow_sheet.inlet.c', [1.0], 0)
_ = process.add_event('stop', 'flow_sheet.inlet.c', [0.0], 10)
simulator = Cadet()
simulator.use_dll = True
with warnings.catch_warnings():
warnings.simplefilter("ignore")
simulator.simulate(process)
print("SUCCESS")
""")
# The exact same script, with the 3-line fix injected before anything else runs:
# catch the IndexError on the empty solid-phase array and write a defined value
# (0.0 -- the physically correct rate for a reaction phase that does not exist)
# instead of leaving the output pointer uninitialized.
PATCH_PREAMBLE = textwrap.dedent("""
import ctypes
import cadet.cadet_dll_utils as cdu
def _patched_get_double_array_item(reader, name, index, val):
n = name.decode('utf-8')
c = reader.current()
if n not in c:
return -1
o = c[n]
try:
float_val = float(o)
except TypeError:
try:
float_val = float(o[index])
except IndexError:
val[0] = ctypes.c_double(0.0)
return 0
val[0] = ctypes.c_double(float_val)
return 0
cdu.param_provider_get_double_array_item = _patched_get_double_array_item
""")
def run_fresh_process(script: str) -> str:
"""Run `script` in a brand new Python process; return 'success' or the error."""
result = subprocess.run(
[sys.executable, "-c", script],
capture_output=True, text=True,
)
if "SUCCESS" in result.stdout:
return "success"
stderr_lines = [line for line in result.stderr.strip().splitlines() if line.strip()]
return f"FAILED: {stderr_lines[-1] if stderr_lines else '(no stderr)'}"
def main() -> None:
n_runs = 10
print(f"Running the unpatched reproducer in {n_runs} fresh processes...")
outcomes = [run_fresh_process(REPRODUCER_SCRIPT) for _ in range(n_runs)]
for i, outcome in enumerate(outcomes):
print(f" run {i}: {outcome}")
n_failed = sum(1 for o in outcomes if o != "success")
print(f"{n_failed}/{n_runs} fresh-process runs failed -- with the exact same "
f"code and environment every time.\n")
print(f"Running the patched reproducer in {n_runs} fresh processes...")
outcomes_patched = [
run_fresh_process(PATCH_PREAMBLE + REPRODUCER_SCRIPT) for _ in range(n_runs)
]
for i, outcome in enumerate(outcomes_patched):
print(f" run {i}: {outcome}")
n_failed_patched = sum(1 for o in outcomes_patched if o != "success")
print(f"{n_failed_patched}/{n_runs} fresh-process runs failed after the patch.")
if __name__ == "__main__":
main()
All packages
# Name Version Build Channel
_openmp_mutex 4.5 20_gnu conda-forge
_x86_64-microarch-level 3 3_x86_64_v3 conda-forge
about-time 4.2.1 pypi_0 pypi
addict 2.3.0 pypi_0 pypi
alive-progress 3.3.0 pypi_0 pypi
anyio 4.14.0 pypi_0 pypi
argon2-cffi 25.1.0 pypi_0 pypi
argon2-cffi-bindings 25.1.0 pypi_0 pypi
arrow 1.4.0 pypi_0 pypi
arviz 1.2.0 pypi_0 pypi
arviz-base 1.2.0 pypi_0 pypi
arviz-plots 1.2.0 pypi_0 pypi
arviz-stats 1.2.0 pypi_0 pypi
asttokens 3.0.1 pypi_0 pypi
async-lru 2.3.0 pypi_0 pypi
attrs 26.1.0 pypi_0 pypi
autograd 1.8.0 pypi_0 pypi
aws-c-auth 0.10.3 hea842a7_2 conda-forge
aws-c-cal 0.9.14 h78948cc_2 conda-forge
aws-c-common 0.14.0 hb03c661_0 conda-forge
aws-c-compression 0.3.2 haa0cbde_2 conda-forge
aws-c-http 0.11.0 h6488f85_2 conda-forge
aws-c-io 0.26.3 h3bf836e_5 conda-forge
aws-c-s3 0.12.5 hb916526_1 conda-forge
aws-c-sdkutils 0.2.4 haa0cbde_6 conda-forge
aws-checksums 0.2.10 haa0cbde_2 conda-forge
babel 2.18.0 pypi_0 pypi
beautifulsoup4 4.15.0 pypi_0 pypi
bleach 6.4.0 pypi_0 pypi
bzip2 1.0.8 hda65f42_9 conda-forge
c-ares 1.34.6 hb03c661_0 conda-forge
ca-certificates 2026.6.17 hbd8a1cb_0 conda-forge
cadet 5.1.1 h56c1e75_1 conda-forge
cadet-process 0.12.0 pypi_0 pypi
cadet-python 1.2.0 pypi_0 pypi
certifi 2026.5.20 pypi_0 pypi
cffi 2.0.0 pypi_0 pypi
charset-normalizer 3.4.7 pypi_0 pypi
cloudpickle 3.1.2 pyhcf101f3_1 conda-forge
cma 4.4.4 pypi_0 pypi
comm 0.2.3 pypi_0 pypi
contourpy 1.3.3 pypi_0 pypi
corner 2.2.3 pypi_0 pypi
cycler 0.12.1 pypi_0 pypi
debugpy 1.8.21 pypi_0 pypi
decorator 5.3.1 pypi_0 pypi
defusedxml 0.7.1 pypi_0 pypi
deprecated 1.3.1 pypi_0 pypi
dill 0.4.1 pypi_0 pypi
diskcache 5.6.3 pypi_0 pypi
et-xmlfile 2.0.0 pypi_0 pypi
executing 2.2.1 pypi_0 pypi
fastjsonschema 2.21.2 pypi_0 pypi
filelock 3.29.4 pypi_0 pypi
fonttools 4.63.0 pypi_0 pypi
fqdn 1.5.1 pypi_0 pypi
gmp 6.3.0 hac33072_2 conda-forge
graphemeu 0.7.2 pypi_0 pypi
h11 0.16.0 pypi_0 pypi
h5py 3.16.0 pypi_0 pypi
hdf5 2.1.0 nompi_h735b18d_107 conda-forge
hopsy 1.7.0 pypi_0 pypi
httpcore 1.0.9 pypi_0 pypi
httpx 0.28.1 pypi_0 pypi
icu 78.3 h33c6efd_0 conda-forge
idna 3.18 pypi_0 pypi
ipykernel 7.3.0 pypi_0 pypi
ipython 9.14.1 pypi_0 pypi
ipython-pygments-lexers 1.1.1 pypi_0 pypi
ipywidgets 8.1.8 pypi_0 pypi
isoduration 20.11.0 pypi_0 pypi
jedi 0.20.0 pypi_0 pypi
jinja2 3.1.6 pypi_0 pypi
joblib 1.5.3 pypi_0 pypi
json5 0.14.0 pypi_0 pypi
jsonpointer 3.1.1 pypi_0 pypi
jsonschema 4.26.0 pypi_0 pypi
jsonschema-specifications 2025.9.1 pypi_0 pypi
jupyter 1.1.1 pypi_0 pypi
jupyter-client 8.9.1 pypi_0 pypi
jupyter-console 6.6.3 pypi_0 pypi
jupyter-core 5.9.1 pypi_0 pypi
jupyter-events 0.12.1 pypi_0 pypi
jupyter-lsp 2.3.1 pypi_0 pypi
jupyter-server 2.19.0 pypi_0 pypi
jupyter-server-terminals 0.5.4 pypi_0 pypi
jupyterlab 4.5.8 pypi_0 pypi
jupyterlab-pygments 0.3.0 pypi_0 pypi
jupyterlab-server 2.28.0 pypi_0 pypi
jupyterlab-widgets 3.0.16 pypi_0 pypi
keyutils 1.6.3 hb9d3cd8_0 conda-forge
kiwisolver 1.5.0 pypi_0 pypi
krb5 1.22.2 ha1258a1_0 conda-forge
lark 1.3.1 pypi_0 pypi
lazy-loader 0.5 pypi_0 pypi
ld_impl_linux-64 2.45.1 default_hbd61a6d_102 conda-forge
libaec 1.1.5 h088129d_0 conda-forge
libamd 3.3.3 haaf9dc3_7100102 conda-forge
libblas 3.11.0 8_h4a7cf45_openblas conda-forge
libbtf 2.3.2 h32481e8_7100102 conda-forge
libcamd 3.3.3 h32481e8_7100102 conda-forge
libcblas 3.11.0 8_h0358290_openblas conda-forge
libccolamd 3.3.4 h32481e8_7100102 conda-forge
libcholmod 5.3.1 h59ddab4_7100102 conda-forge
libcolamd 3.3.4 h32481e8_7100102 conda-forge
libcurl 8.20.0 hcf29cc6_0 conda-forge
libcxsparse 4.4.1 h32481e8_7100102 conda-forge
libedit 3.1.20250104 pl5321h7949ede_0 conda-forge
libev 4.33 hd590300_2 conda-forge
libexpat 2.8.1 hecca717_1 conda-forge
libffi 3.5.2 h3435931_0 conda-forge
libgcc 15.2.0 he0feb66_19 conda-forge
libgcc-ng 15.2.0 h69a702a_19 conda-forge
libgfortran 15.2.0 h69a702a_19 conda-forge
libgfortran5 15.2.0 h68bc16d_19 conda-forge
libgomp 15.2.0 he0feb66_19 conda-forge
libhwloc 2.13.0 default_he001693_1000 conda-forge
libiconv 1.18 h3b78370_2 conda-forge
libklu 2.3.5 hf24d653_7100102 conda-forge
liblapack 3.11.0 8_h47877c9_openblas conda-forge
libldl 3.3.2 h32481e8_7100102 conda-forge
liblzma 5.8.3 hb03c661_0 conda-forge
libnghttp2 1.68.1 h877daf1_0 conda-forge
libnsl 2.0.1 hb9d3cd8_1 conda-forge
libopenblas 0.3.33 pthreads_h94d23a6_0 conda-forge
libparu 1.0.0 h17147ab_7100102 conda-forge
librbio 4.3.4 h32481e8_7100102 conda-forge
libspex 3.2.3 had10066_7100102 conda-forge
libspqr 4.3.4 h852d39f_7100102 conda-forge
libsqlite 3.53.2 h0c1763c_0 conda-forge
libssh2 1.11.1 hcf80075_0 conda-forge
libstdcxx 15.2.0 h934c35e_19 conda-forge
libstdcxx-ng 15.2.0 hdf11a46_19 conda-forge
libsuitesparseconfig 7.10.1 h92d6892_7100102 conda-forge
libumfpack 6.3.5 heb53515_7100102 conda-forge
libuuid 2.42.2 h5347b49_0 conda-forge
libxcrypt 4.4.36 hd590300_1 conda-forge
libxml2 2.15.3 h49c6c72_0 conda-forge
libxml2-16 2.15.3 hca6bf5a_0 conda-forge
libzlib 1.3.2 h25fd6f3_2 conda-forge
llvmlite 0.47.0 pypi_0 pypi
markupsafe 3.0.3 pypi_0 pypi
matplotlib 3.11.0 pypi_0 pypi
matplotlib-inline 0.2.2 pypi_0 pypi
metis 5.1.0 hd0bcaf9_1007 conda-forge
mistune 3.2.1 pypi_0 pypi
moocore 0.3.1 pypi_0 pypi
mpfr 4.2.2 he0a73b1_0 conda-forge
mpmath 1.3.0 pypi_0 pypi
multiprocess 0.70.19 pypi_0 pypi
narwhals 2.22.1 pypi_0 pypi
nbclient 0.11.0 pypi_0 pypi
nbconvert 7.17.1 pypi_0 pypi
nbformat 5.10.4 pypi_0 pypi
ncurses 6.6 hdb14827_0 conda-forge
nest-asyncio2 1.7.2 pypi_0 pypi
networkx 3.6.1 pyhcf101f3_0 conda-forge
notebook 7.5.7 pypi_0 pypi
notebook-shim 0.2.4 pypi_0 pypi
numba 0.65.1 pypi_0 pypi
numpy 2.4.6 pypi_0 pypi
openpyxl 3.1.5 pypi_0 pypi
openssl 3.6.3 h35e630c_0 conda-forge
optlang 1.9.1 pypi_0 pypi
packaging 26.2 pyhc364b38_0 conda-forge
pandas 3.0.3 pypi_0 pypi
pandocfilters 1.5.1 pypi_0 pypi
parso 0.8.7 pypi_0 pypi
pathos 0.3.5 pypi_0 pypi
pexpect 4.9.0 pypi_0 pypi
pillow 12.2.0 pypi_0 pypi
pip 26.1.2 pyh8b19718_0 conda-forge
pipefunc 0.93.0 pyhc364b38_0 conda-forge
platformdirs 4.10.0 pypi_0 pypi
polyround 0.4.0 pypi_0 pypi
pox 0.3.7 pypi_0 pypi
ppft 1.7.8 pypi_0 pypi
prometheus-client 0.25.0 pypi_0 pypi
prompt-toolkit 3.0.52 pypi_0 pypi
psutil 7.2.2 py312h5253ce2_0 conda-forge
ptyprocess 0.7.0 pypi_0 pypi
pure-eval 0.2.3 pypi_0 pypi
pycparser 3.0 pypi_0 pypi
pygments 2.20.0 pypi_0 pypi
pymoo 0.6.1.6 pypi_0 pypi
pyparsing 3.3.2 pypi_0 pypi
python 3.12.13 hd63d673_0_cpython conda-forge
python-dateutil 2.9.0.post0 pypi_0 pypi
python-json-logger 4.1.0 pypi_0 pypi
python_abi 3.12 8_cp312 conda-forge
pyyaml 6.0.3 pypi_0 pypi
pyzmq 27.1.0 pypi_0 pypi
readline 8.3 h853b02a_0 conda-forge
referencing 0.37.0 pypi_0 pypi
requests 2.34.2 pypi_0 pypi
rfc3339-validator 0.1.4 pypi_0 pypi
rfc3986-validator 0.1.1 pypi_0 pypi
rfc3987-syntax 1.1.0 pypi_0 pypi
rpds-py 2026.5.1 pypi_0 pypi
s2n 1.7.4 h92489ea_1 conda-forge
scikit-learn 1.9.0 pypi_0 pypi
scipy 1.17.1 pypi_0 pypi
seaborn 0.13.2 pypi_0 pypi
send2trash 2.1.0 pypi_0 pypi
setuptools 82.0.1 pyh332efcf_0 conda-forge
six 1.17.0 pypi_0 pypi
soupsieve 2.8.4 pypi_0 pypi
stack-data 0.6.3 pypi_0 pypi
suitesparse 7.10.1 ha0f6916_7100102 conda-forge
superlu 7.0.1 h8f6e6c4_0 conda-forge
swiglpk 5.0.13 pypi_0 pypi
sympy 1.14.0 pypi_0 pypi
tbb 2023.0.0 hab88423_2 conda-forge
tbb-devel 2023.0.0 hab88423_2 conda-forge
terminado 0.18.1 pypi_0 pypi
threadpoolctl 3.6.0 pypi_0 pypi
tinycss2 1.5.1 pypi_0 pypi
tk 8.6.13 noxft_h366c992_103 conda-forge
tornado 6.5.7 pypi_0 pypi
tqdm 4.68.3 pypi_0 pypi
traitlets 5.15.1 pypi_0 pypi
typing-extensions 4.15.0 pypi_0 pypi
tzdata 2026.2 pypi_0 pypi
uri-template 1.3.0 pypi_0 pypi
urllib3 2.7.0 pypi_0 pypi
wcwidth 0.8.1 pypi_0 pypi
webcolors 25.10.0 pypi_0 pypi
webencodings 0.5.1 pypi_0 pypi
websocket-client 1.9.0 pypi_0 pypi
wheel 0.47.0 pyhd8ed1ab_0 conda-forge
widgetsnbextension 4.0.15 pypi_0 pypi
wrapt 2.2.1 pypi_0 pypi
xarray 2026.4.0 pypi_0 pypi
xarray-einstats 0.10.0 pypi_0 pypi
zstd 1.5.7 hb78ec9c_6 conda-forge
Update:
Jo cannot reproduce the issue, I created a fresh env and still face the same issue.
I ran into a non-deterministic crash when using
particle_reaction_modelon aLumpedRateModelWithoutPoresunit (bulk_reaction_modelis not supported) for a unit without a solid phase. The exact same code, run repeatedly with no changes, sometimes succeeds and sometimes fails with:Root cause
Assigning a normal bulk
MassActionLawreaction toparticle_reaction_modeltriggers CADET-Process auto-cast to a particle reaction. Since this unit has no solid phase, the solid-phase reaction parameters end up empty which is correctly represented as CADET-Process's ownProxyListcontainer.In CADET-python the
param_provider_num_elements(cadet/cadet_dll_utils.py) only recognizeslistandnumpy.ndarrayas sized containers:So it reports length 1 for an empty array, CADET-Core asks for index 0, and
param_provider_get_double_array_itemraises an unhandledIndexErroronfloat(o[index]). That exception escapes the ctypes callback uncaught . Ctypes prints "Exception ignored..." and the output pointer (val[0]) is never written, so whatever uninitialized memory happens to be there gets read as if it were a valid value. Which probably explains the randomness?Why this used to work: before CADET-Process's
d8c6669("Add ProxyList/Array to allow indexed modification of aggregated properties", 2025-01-29, in every release since 0.10.1), an empty aggregated reaction array resolved toNoneand was dropped from the config entirely, so CADET-Core never asked for it at all. I confirmed this directly on CADET-Process 0.10.1 the identical reproducer succeeds 10/10 fresh CADET-Process runs; on every version since theProxyListchange, it fails 30-60% of the time, also reproducing identically with CADET-Process 0.11.0, 0.11.1, and 0.12.0(dev), independent of numpy version. So this is a CADET-python bug exposed by a CADET-Process feature change.Suggested fix: in
param_provider_get_double_array_item, catchIndexError/TypeErroraroundo[index]and write a defined value instead of leavingvalunset. Also worth makingparam_provider_num_elementsrecognize any object with__len__, not justlist/np.ndarray.Environment: CADET-Process 0.12.0 (also 0.11.0/0.11.1), CADET-python 1.2.0, CADET-Core (
cadetconda package) 5.1.1.MRE
This builds a 3-unit flow sheet, runs it in 10 fresh subprocesses, shows the failure rate, then shows a 3-line patch (catching the
IndexErrorand writing a defined0.0).mre_particle_reaction_no_pores_uninitialized_value.pyAll packages