Repository navigation
fix!: reaction orders, molecularity and symmetry entropies from reactant coefficients - #859
Open
schneiderfelipe wants to merge 93 commits into
Open
schneiderfelipe wants to merge 93 commits into
schneiderfelipe wants to merge 93 commits into
Conversation
…cheme
Scheme is now a validated frozen dataclass holding reactant and product
coefficients, transition states and half-equilibrium flags. Reaction
strings and the A and B matrices are derived from them.
- Remove get_transition_states in favour of Scheme.transition_states.
- Repeated compounds on one side add up ("B + B -> C" is "2 B -> C").
- Empty sides denote sources and sinks ("-> A", "A ->").
- Reactions without compounds raise.
- Half-equilibria without their reverse, or with transition states, raise.
Species on both sides of a reaction no longer lower its order, e.g. "2 B -> B + C" is second order in B. Closes #838
Add Scheme.molecularity and remove thermo.get_molecularity, which counted reacting species instead of molecules ("2 A -> B" was unimolecular).
get_reaction_entropies now takes initial and final coefficients, so molecules on both sides of a reaction count in each state ("2 B -> B + C" loses R ln 2). Its temperature and pressure arguments had no effect and are removed.
Split the ORCA reference check into its own test.
- parse_reactions and unparse_reactions become Scheme.parse and str(scheme). - get_delta and get_reaction_entropies become Scheme.reaction_deltas, Scheme.activation_deltas, Scheme.reaction_entropies and Scheme.activation_entropies. - get_fixed_scheme becomes Scheme.fix, returning rate constant factors. - Add Scheme.equilibria for pairs of half-equilibria. - Make the activated state private and drop the B matrix. - Name the offending reaction when a half-equilibrium lacks its reverse.
Scheme.equilibria() now yields index pairs, derived arrays are computed once on first use and read-only, and str(scheme) is built from a generator.
It is reused and cheap to keep, so share it instead of handing out one-shot generators.
Keep validated arrays from construction, build reaction strings directly, mask fixed compounds, stack CLI quantities into single delta calls, share number formatting and stop logging activation entropies twice.
Reject non-integer coefficients and fixed transition states, compute rate constant factors in floating point, normalize fields before validating them, and accept reactions left without compounds after fixing, as get_fixed_scheme did.
…action - Scheme stores equilibria as (forward, reverse) index pairs instead of half-equilibrium flags, which are now derived. - An equilibrium absorbs plain reactions between its two sides, so "A -> B" followed by "A <=> B" is the equilibrium alone. - Compounds are numbered in order of appearance in the resulting reactions, so parsing str(scheme) gives back the same scheme.
…tants get_k sets each equilibrium pair from the equilibrium constant of its forward half, ignoring the reverse value of delta_freeenergies. The activated state is now the transition state, or the products, for every reaction, so reverse halves of equilibria report their own activation.
Fixing every compound of a scheme left get_dydt with an unshaped order matrix.
Replace tests that re-derived the code under test with invariants (state functions, reversal, rate scaling, detailed balance, ideal gas pressure and volume laws) and ground truth from generated inputs.
…ard one get_k used to ignore the reverse entry of delta_freeenergies for equilibria; it now raises when it is not minus the forward entry.
…ducts Chained transition states and transition state lines that never complete into a reaction used to be dropped silently, as did compounds only named there. Scheme also rejects transition states as reactants or products.
- Store reactant and product coefficients once, as read-only arrays, which also removes the reshape in get_dydt. - Derive the equilibrium flag of parser records from their keys. - Share one helper for reaction and activation deltas in the CLI, and read reaction sides and transition states without regular expressions. - Trim the Scheme.parse docstring to what it does.
Arrays of concentrations used to give wrong factors; factors now have one row per condition.
The Wigner fallback for non-positive barriers applies element-wise.
… are not supported
…onstant and eyring delta_moles and molecularity converted the wrong way, giving K_p and atm-based rate constants instead of the documented K_c and molar ones. volume is now in m³/mol, as in change_reference_state.
… free energies get_k forced the 1 M standard state when computing free energies from compounds, but took given free energies as 1 M too, while get_freeenergies puts gas-phase compounds at the reference pressure. get_k now takes free energies in the standard state of each compound's environment, converts gas ones to 1 M, and uses volume for that. Its body now calls local functions for free energies from compounds and for tunneling coefficients.
…off only with None get_k and get_kappa no longer accept the string "none", and get_kappa rejects unknown corrections even for schemes without transition states. The CLI keeps --tunneling=none.
… reject arrays of conditions get_bias takes y0 by name, as it takes data, instead of an array in the order of scheme.compounds. It now raises a clear error for arrays of temperatures or pressures, and is documented as taking floats.
…m their free energies
…t and eckart As in NumPy, scalar inputs now give scalars, instead of arrays of shape (1,) or ().
change_reference_state moves to overreact.thermo, next to equilibrium_constant, as eyring and eckart are in their own modules. overreact.io now lists parse_compounds, which overreact exports.
convert_rate_constant took atm-based rate constants at RT/p instead of RT when the reference pressure was not 1 atm, and skipped checking units when molecularity was one. Its unused pressure argument is gone.
get_k silently ignored bias with delta_freeenergies.
…dcasting loop get_internal_energies, get_enthalpies and get_entropies each define the quantity of one compound as a local function, which _per_compound evaluates along the last axis.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #838.
Schemestored only net stoichiometry, so species on both sides of a reaction lost their reactant coefficients. For example,2 B -> B + Cwas simulated as first order in B.Schemenow stores reactants, products, transition states and equilibrium pairs, and derives everything else from them.Fixes
get_dydtuses reactant coefficients, so2 B -> B + Cis second order in B (Wrong reaction order in an autocatalytic reaction #838).2 A -> Bis bimolecular.2 B -> B + Closes R ln 2.B + B -> Cis2 B -> C(it used to be first order). Zero coefficients are dropped.Scheme.equilibriastores(forward, reverse)index pairs, instead ofget_kassuming consecutive half-equilibria.A -> Bfollowed byA <=> Bused to leave an unpaired half thatget_kpaired with the wrong reaction; now an equilibrium absorbs plain reactions between its two sides.get_ksets each pair from the forward half's equilibrium constant and runs Eyring only on reactions outside equilibria, so reverse halves no longer need a zero-activation special case. The resulting rate constants are unchanged.Scheme.reaction_freeenergiesandScheme.activation_freeenergiesinclude them, instead of every caller subtracting temperature times symmetry entropies by hand.A -> A‡ -> A'‡ -> B), incomplete ones (A -> A‡), and transition states sharing a side or with a coefficient (C + T‡ -> D,2 T‡) used to be silently dropped or misread; they now raise.get_k, a reverse half of an equilibrium whose free energy is not minus the forward one, within 1e-5 J/mol;Scheme.fix, a concentration for a name that is not a compound (transition states included), a compound given both free and fixed concentrations, or a concentration without a colon; and plotting a name that is not a compound, now checked before simulating..jk): compounds are listed in the order ofScheme.names, so models compiled by older versions stay aligned.get_kandget_kappatake values from the compounds dict by name, so a dict in any order gives the same rate constants.get_k(scheme, compounds)forced every compound to the 1 M standard state, whileget_freeenergieskeeps gas-phase compounds at the reference pressure. Passing those free energies asdelta_freeenergiesgave bimolecular gas-phase rate constants 24.5 times too small (RT/p° in L/mol).get_know converts gas-phase free energies to 1 M itself, so both paths agree, andbiasnow also applies withdelta_freeenergies.get_entropiesused the first compound's environment for all compounds when none was given, so mixed gas and solution models put gases at 1 M.eyring(molecularity=)andequilibrium_constant(delta_moles=)converted from 1 M to gas-phase data, the opposite of what their docs say, off by (RT/p°)² for bimolecular reactions.get_k(volume=)was ignored; it is now the molar volume of gases.atm-1 s-1used RT/p instead of RT at reference pressures other than 1 atm, and unknown units passed silently for unimolecular reactions.import overreact: solvation methods inoverreact.thermoraisedAttributeErrorunless something else had imported their module first.get_kwithout compounds or free energies, and solvation methods without atoms, raiseTypeErrorinstead ofAssertionError.get_dydtrejects rate constants that are not one per reaction, andget_biasrejects arrays of temperatures or pressures.Breaking changes (version bumped to 2.0.0)
parse_reactions(text)Scheme.parse(text)unparse_reactions(scheme)str(scheme)get_delta(scheme.A, x)/get_delta(scheme.B, x)scheme.reaction_deltas(x)/scheme.activation_deltas(x)get_reaction_entropies(transform)scheme.reaction_entropies/scheme.activation_entropiesget_fixed_scheme(scheme, k, c)scheme, factors = scheme.fix(c), thenk * factorsget_transition_states(A, B, is_half_equilibrium)scheme.transition_statesthermo.get_molecularity(scheme.A)scheme.molecularityScheme(compounds, reactions, is_half_equilibrium, A, B)Scheme(compounds, reactants, products, transition_states, equilibria)scheme.Bscheme.activation_deltasscheme.Ascheme.products - scheme.reactantsscheme.is_half_equilibriumscheme.equilibriais_transition_state(name)scheme.transition_statesio.InterfaceFormatterrx.change_reference_staterx.thermo.change_reference_stateget_kappa(scheme, compounds, method=...)get_kappa(scheme, compounds, tunneling=...)tunneling="none"tunneling=None(the CLI keeps--tunneling=none)get_bias(..., y0=[...])in compound orderget_bias(..., y0={"A": ...})by nameget_dydt(scheme, k, ef=...)get_dydt(scheme, k, equilibrium_factor=...)scheme.activation_deltas(g) - T * scheme.activation_entropies(and the reaction analogue)scheme.activation_freeenergies(g, T)/scheme.reaction_freeenergies(g, T)(not removed, but no longer needed)Transition states are stored apart from compounds:
scheme.compoundsno longer lists transition states, so coefficient matrices, concentration vectors (y0,get_yresults) and CLI plots and tables have no transition-state entries.scheme.transition_statesholds names (('A‡', None)) instead of indices intocompounds.biasinget_k, follow the newscheme.names: compounds, then transition states.parse_modelordersmodel.compoundsthe same way, soget_freeenergies(model.compounds)stays aligned.Schemeis now a validated frozen dataclass. Its coefficient matrices and derived data are read-only arrays, computed once and cached. Compounds are numbered in order of appearance in the resulting reactions, soScheme.parse(str(scheme)) == scheme.With 2-D values, the delta methods take names on the last axis, where
get_deltatook them on the first.get_kappareturns one row per condition instead of a flattened array.eyring,equilibrium_constantandeckartreturn floats for scalar inputs, as NumPy does, instead of arrays of shape(1,)or(). Thevolumeofequilibrium_constantandeyringis in m³/mol, as inchange_reference_state.Free energies passed to
get_kfollow the standard states ofget_freeenergies: unsuffixed names, such asA, are gases at the reference pressure, and names such asA(w)are in solution at 1 M. Rate constants from compounds are unchanged, but those fromdelta_freeenergieschange by a factor of RT/p°, about 24.5 L/mol, for each gas-phase molecule a step consumes, such as the second reactant ofA + B -> C. To give solution free energies, name compounds with a solvent suffix.Arrays of conditions
For example,
get_k(scheme, compounds, temperature=[[300.0], [400.0]])returns one row of rate constants per temperature. Values per compound, name or reaction lie along the last axis, and conditions broadcast against them: a scalar applies everywhere, shape(n,)gives one value per item, and shape(m, 1)addsmconditions.get_ktakes one temperature per reaction withdelta_freeenergies, or batches of shape(m, 1). It rejects temperatures that differ between compounds or between the halves of an equilibrium.get_internal_energies,get_enthalpies,get_entropies,get_freeenergiesandget_kappabroadcast over conditions.Schemedelta and free-energy methods andScheme.fixbroadcast over leading axes. The newScheme.concentrationsbuilds concentration arrays from a dict, by name.tunnel.eckarttakes arrays of barriers and falls back to Wigner per element.get_dydttakes several states as columns, asscipy.integrate.solve_ivpdoes, soget_y's rate function evaluates many times in one call.format_numbersformats arrays with more than one dimension.overreact.thermothat only take scalar temperatures or pressures are documented as such.Other changes
-> A,A ->). Molecularity counts reactant molecules exactly, so sources have molecularity 0 (it used to be clamped to 1), which changes their rate constants on scales other thanl mol-1 s-1.get_dydtcompiles its Jacobian once with JIT, about 100 times faster per call.get_ystill doesn't pass it to the solver; re-enabling it for implicit solvers is the subject of Diffrax integration #817.hypothesis, checked against independent oracles (invariants, round trips, detailed balance, ideal gas laws, finite differences, literature values) instead of re-deriving the code under test. Non-default tolerances are set to the measured errors.coords,io,_cliand_miscmodules no longer build intermediate collections or copies they don't need. Van der Waals radii, molecular volumes, the Eckart transform, Eckart quadratures and spectrum broadening use NumPy operations instead of Python loops.Notes
0**0fix, tryingvectorized=Trueinsolve_ivpand an implicit default solver stay in Diffrax integration #817.parse_reactions,get_deltaandget_fixed_scheme, and concentration vectors with transition-state entries, so they will need updating for 2.0.0.