geem-lab / geem-lab/overreact

Wrong reaction order in an autocatalytic reaction

Open
#838 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
65
Forks
10
Avg merge
5d 9h
Merged PRs (30d)
7

Description

**Please complete the following tasks**

- [x] I have searched the documentation and help resources before reporting.
- [x] I have searched the [discussions](https://github.com/geem-lab/overreact/discussions) to avoid duplicates.
- [x] I have searched the [open](https://github.com/geem-lab/overreact/issues) and [closed](https://github.com/geem-lab/overreact/issues?q=is%3Aissue+is%3Aclosed) issues to avoid duplicates.

**Describe the bug**
Consider the autocatalytic reaction "2B -> B + C":

```python
import numpy as np

import overreact as rx

scheme = rx.parse_reactions("2 B -> B + C")

stoichiometry = np.asarray(scheme.A)
kinetic_orders = np.where(stoichiometry > 0, 0, -stoichiometry)
reaction_order = np.sum(kinetic_orders[:, 0])

print("reaction (should be 2nd order in B):", scheme.reactions[0])
print("computed net stoichiometry (A):", stoichiometry[:, 0])
print("reaction order:", reaction_order)

# actually computing the schema to show it's first order:
dydt = rx.get_dydt(scheme, k=np.array([1.0]))
rate_b1 = dydt(0.0, np.array([1.0, 0.0]))[1]
rate_b2 = dydt(0.0, np.array([2.0, 0.0]))[1]

print("rate at [B] = 1:", float(rate_b1))
print("rate at [B] = 2:", float(rate_b2))
```

Output:

```
reaction (should be 2nd order in B): 2 B -> B + C
computed net stoichiometry (A): [-1. 1.]
reaction order: 1.0
An NVIDIA GPU may be present on this machine, but a CUDA-enabled jaxlib is not installed. Falling back to cpu.
rate at [B] = 1: 1.0
rate at [B] = 2: 2.0
```

**To Reproduce**
Steps to reproduce the behavior:

1. Open a terminal.
2. Save the above in a script with a proper overreact environment.
3. Execute and see the inferred reaction order in B is 1 instead of 2.

**Expected behavior**
Should be 2nd order in B.

**Origin**

I think [_add_reaction()](https://github.com/geem-lab/overreact/blob/dec053bb108c117fef10eee446be06655384ecdc/overreact/core.py#L603-L639) when taken together with [_dydt()](https://github.com/geem-lab/overreact/blob/dec053bb108c117fef10eee446be06655384ecdc/overreact/simulate.py#L262-L265) is the faulty point.

```python
def _add_reaction(reactants, products, is_half_equilibrium, transition):
"""Local helper function with side-effects."""
# TODO(schneiderfelipe): what if reaction is defined, then redefined
# as equilibrium, or vice-versa?
if (
transition is not None
and (reactants, products, is_half_equilibrium, transition) in reactions
):
return

# found new reaction
reactions[(reactants, products, is_half_equilibrium, transition)] = None

A_vector = np.zeros(len(compounds))
for coefficient, reactant in reactants:
A_vector[compounds[reactant]] = -coefficient
B_vector = A_vector

if transition is not None:
B_vector = A_vector.copy()

# it's assumed that
# 1. there's a singe transition compound, and
# 2. its coefficient equals one
B_vector[compounds[transition[-1][-1]]] = 1

for coefficient, product in products:
A_vector[compounds[product]] += coefficient

if (
is_half_equilibrium
and (products, reactants, is_half_equilibrium, transition) in reactions
):
B_vector = np.zeros(len(compounds))

A.append(A_vector)
B.append(B_vector)
```

The parser sotres the reactants:

A_vector[B] = -2

Then it adds the products:

A_vector[B] += 1
A_vector[C] += 1

Which is fine for the net concentration changes, but when we get to [_dydt()](https://github.com/geem-lab/overreact/blob/dec053bb108c117fef10eee446be06655384ecdc/overreact/simulate.py#L262-L265), more specifically, in the line below:

```python
M = jnp.where(A > 0, 0, -A).T
```

we compute M based on A, which results in a first order reaction.

This has appeared when trying to implement the [Robertson problem](https://github.com/DENG-MIT/CRNN/discussions/16) as a test in #817.

Contributor guide

Open the contributing guide

Research direction

Start with _add_reaction() in overreact/core.py and _dydt() in overreact/simulate.py, especially the construction of M from A. Run the reproduction script for "2 B -> B + C" and inspect the existing tests, including the Robertson problem work referenced in #817. Done means the reported reaction order and rate scaling are second order in B without breaking net concentration changes.

Written by the indexing model from the issue text.

Assessment

Tech stack
numpy, python
Domain
backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.