src/sys_def.jl
MEDYAN.add_diffusion_coeff!
Update the diffusion coefficient for an existing diffusing species.
The species must already exist in s.diffusing (typically added via def_diffusing_species!.
Arguments
s::SysDef: The system definition to modify.name::Symbol: The name of the existing diffusing species.coeff::Float64: The diffusion coefficient in units of nm²/s.
Returns
The modified SysDef.
See also: def_diffusing_species! to add a new species with its diffusion coefficient in one step.
MEDYAN.def_diffusing_species!
Dynamically add a new diffusing species to the system definition.
Arguments
s::SysDef: The system definition to modify.name::Symbol: The name of the new diffusing species.coeff::Float64: The diffusion coefficient in units of (nm²/s).
Returns
The modified SysDef.
Throws
- Error if a diffusing species with the given name already exists.
MEDYAN.def_fixed_species!
Dynamically add a new fixed (non-diffusing) species to the system definition.
Must be called before adding any sites (filament sites, filament end sites, membrane sites, link types, etc.) or reactions.
Arguments
s::SysDef: The system definition to modify.name::Symbol: The name of the new fixed species.
Returns
The modified SysDef.
Throws
- Error if the fixed species already exists.
- Error if sites or reactions have already been added.
MEDYAN.def_fila_type!
Add a new filament type to the system definition.
Arguments
s::SysDef: The system definition to modify.name::Symbol: The name of the new filament type.mono_states::Vector{Symbol}: Vector of monomer state names for this filament type. Must not be empty.param::FilamentMechParams: The filament mechanical parameters.
Returns
The modified SysDef.
Throws
- Error if a filament type with the given name already exists.
- Error if
mono_statesis empty.
MEDYAN.add_filament_params!
Update the filament mechanical parameters for an existing filament type.
The filament type must already exist in s.filament (typically added via def_fila_type!.
Arguments
s::SysDef: The system definition to modify.filament_name::Symbol: The name of the existing filament type.filament_params::FilamentMechParams: The mechanical parameters to set.
Returns
The modified SysDef.
See also: def_fila_type! to add a new filament type with its parameters in one step.
MEDYAN.addfilamentsite!
MEDYAN.addfilamentendsite!
MEDYAN.add_decimated_2mon_site!
MEDYAN.addpossiblecadherinsite!
MEDYAN.addmembranesite!
MEDYAN.def_link_type!
Add a new link type to the system definition. Links represent connections between simulation elements (filament monomers, filament tips, anchors, membrane vertices) that can have associated mechanical bonds and chemical reactions.
Keyword Arguments
name::Symbol: Unique identifier for this link type.description::String="": Human-readable description of the link’s purpose.places::Vector{<:Place}: The places this link connects. Each place can be:FilaMonoIdx(): A filament monomerFilaTipIdx(): A filament tip (plus or minus end)Anchor(): A free-floating anchor point (e.g., membrane-bound protein)BallIdx(): A ball (spherical object)MembVertIdx(): A membrane vertex
bonds::Vector: Mechanical bonds between places. Each bond is aNamedTuplewith:bond: Bond type (e.g.,DistanceRestraint(), customBondsubtype)input::Tuple{Int...}: Which places the bond connectsparam::NamedTuple: Fixed parameters (e.g., spring constantk)state::NamedTuple: Mutable state (e.g., rest lengthL0)enabled::Bool=true: Whether bond is active by default
reactions::Vector{Vector}: Reactions for each place.reactions[i]is aVectorof reactions forplaces[i]. Trailing places with no reactions can be omitted (i.e.length(reactions) ≤ length(places)). Each reaction is aNamedTuplewith:Required fields:
name::Symbol: Unique identifier for this reaction within the place.affect!: Callback that executes the reaction. Signature:(c::Context; link::Link, chem_voxel::Int, reaction_id, place_idx, kwargs...) -> Int.linkis a handle identifying the specific link instance — pass it toget_state,update_link!,link2tags, etc. Return a status code for debugging purposes. Forfila_cutoffreactions, also receivesplace::FilaMonoIdx(a randomly chosen nearby filament monomer within the cutoff distance). Forball_cutoffreactions, also receivesplace::BallIdx(a randomly chosen nearby ball within the cutoff distance).
Optional fields:
rate = Returns(1.0): Function(c::Context; link::Link, link_data, place_idx, link_state::NamedTuple, kwargs...) -> Float64returning the state-dependent rate factor.link_stateis the current mutable state of this link instance (as defined by thestatekeyword). For multi-site binding, return the number of available sites (e.g.,MAX_ARP23 - link_state.num_arp23).link_datais an internal object — pass it through tolink2tagsorget_link_mechanicsfor a small performance gain over the 1-argument forms. See “When rates are reevaluated” below for important constraints on what this function should depend on.base_rate::Float64 = 1.0: Rate constant multiplier. Units depend oninvvolumepower: 1/s for unimolecular (invvolumepower=0), nm³/s for bimolecular (invvolumepower=1). For example, a diffusion-limited on-rate k_D = 4πDR ≈ 1.57×10⁹ nm³/s for D=25 μm²/s, R=5 nm. Separating large rate constants from small integer multipliers inrateavoids overflow.invvolumepower::Int64 = 0: Volume scaling. Set to 1 for bimolecular (nm³/s), 0 for unimolecular (1/s).reactants_extra::String = "": Additional reactant species whose counts multiply the propensity (e.g.,"diffusing.ARP23"). Uses the same"+"-separated format asdef_reaction!(e.g.,"diffusing.A + diffusing.B").fila_cutoff::Tuple{Symbol, Float64} = nothing: If set, e.g.,(:actin, 50.0), the propensity is multiplied by the number of nearby filament monomers of that type within the cutoff distance (nm). When the reaction fires,affect!receives one such monomer asplace::FilaMonoIdx. Some monomers are invisible to this search until after a mechanics cycle — see “Nearby count caching and mechanics” below.fila_tip_cutoff::Tuple{Symbol, Bool, Float64} = nothing: If set, e.g.,(:actin, true, 50.0), the propensity is multiplied by the number of nearby filament tips of that type within the cutoff distance (nm). TheBoolisis_minus_end:truefor minus-end tips,falsefor plus-end tips. When the reaction fires,affect!receives one such tip asplace::FilaTipIdx. Mutually exclusive withfila_cutoffandball_cutoff. Some tips are invisible to this search until after a mechanics cycle — see “Nearby count caching and mechanics” below.ball_cutoff::Float64 = nothing: If set, e.g.,50.0, the propensity is multiplied by the number of nearby balls whose centers are within the cutoff distance (nm). When the reaction fires,affect!receives one such ball asplace::BallIdx. Only balls that have been through a mechanics cycle (is_minimized) are visible to this search. Mutually exclusive withfila_cutoffandfila_tip_cutoff.enabled::Bool = true: Whether reaction is active by default.
param::NamedTuple=(;): Fixed parameters for the link type.state::NamedTuple=(;): Initial mutable state (e.g.,(num_bound=Int32(0),)).
When rates are reevaluated
Although the rate callback receives the full Context and could in principle read any simulation state, the cached rate value is only incrementally recalculated for a specific link when one of the following events occurs during a chemistry step:
update_link!is called on this link — any change tostate,places,bond_states,bond_enabled, orreaction_enabledtriggers a rate recalculation for all reactions on this link.The chemical state (
get_chem_state) of an attached place changes — specifically:- A filament monomer state changes (e.g., via polymerization, depolymerization, severing, or
update_fila_mono_state!): rates are recalculated for links attached to the changed monomer and its ±1 neighbors (and attached tips). - An anchor is moved via
update_anchor!. - A ball is modified via
update_ball!.
- A filament monomer state changes (e.g., via polymerization, depolymerization, severing, or
The entire cache is fully rebuilt at the start of each chemistry phase after mechanics — so all rates are recomputed from scratch using the current Context state after mechanics.
This means: within a chemistry step, if the rate function reads something from the Context other than link_state or the chem state of its own places (see get_chem_state), that dependency will not be tracked and the cached rate will become stale until the next full cache rebuild. For example:
- ✅ Safe: Reading
link_statefields (e.g.,link_state.num_arp23). Updated immediately whenupdate_link!changesstate. - ✅ Safe: Reading
get_chem_stateof attached places, or callingget_link_mechanics/is_minimizedon the same link (these change only after mechanics, which triggers a full cache rebuild before the next chemistry step). - ⚠️ Stale within a chemistry step: Reading diffusing species counts, other links’ states, or the chem state of places that this link is not attached to. These values may change during chemistry without triggering a rate update for this link.
Workaround — rejection sampling: If a reaction rate genuinely depends on state that rate cannot track (e.g., the state of a nearby monomer not attached to this link), have rate return an upper bound of the true rate. Then in affect!, read the current state and accept the reaction with probability true_rate / upper_bound (rejecting by returning 0 otherwise). This preserves correct stochastic kinetics despite the stale cache.
Nearby count caching and mechanics (fila_cutoff / fila_tip_cutoff / ball_cutoff reactions)
When a reaction has fila_cutoff set, its total propensity is base_rate × rate(...) × nearby_monomer_count × [reactants_extra counts] / V^invvolumepower. When a reaction has fila_tip_cutoff set, the propensity is the same but with nearby_tip_count in place of nearby_monomer_count. When a reaction has ball_cutoff set, the propensity is the same but with nearby_ball_count in place of nearby_monomer_count. These counts are cached alongside the rate factor and have important update semantics:
Snapshotted positions
1. After each mechanics cycle, monomer and tip positions are snapshotted.
These snapshotted positions do not change during chemistry.
2. The nearby count is computed using the link place's **current** position
(i.e. the `Anchor`, `FilaMonoIdx`, etc. whose reaction has the cutoff)
against the snapshotted positions. The count is recomputed whenever the
link place moves or a new link is created.
3. Monomers that have not yet been through a mechanics cycle (from
polymerization, filament creation, or severing) are not in the snapshot.
4. Similarly, a tip is visible only if it existed at the last mechanics
snapshot. Polymerization and depolymerization move an existing tip
but do not create a new one, so the tip stays visible at its
snapshotted position.
5. A ball is visible to `ball_cutoff` only when
[`is_minimized`](@ref) is `true`. Changing `position` or `radius`
via [`update_ball!`](@ref) resets `is_minimized` to `false`.Example: Membrane-bound NPF cluster
const NPF_MAX_ARP23 = Int32(3) # maximum ARP2/3 that can bind per NPF
MEDYAN.def_link_type!(s;
name=:npf_anchor,
description="Nucleation promoting factor anchored to membrane",
places=[Anchor()],
state=(;
num_arp23 = Int32(0), # bound ARP2/3 count
num_actin = Int32(0), # bound actin count
),
reactions=[
[ # reactions for places[1] (the Anchor)
# Bimolecular binding: NPF + diffusing ARP2/3 → NPF·ARP2/3
(;
name = :arp23_bind,
affect! = (c; link, chem_voxel, kwargs...) -> let
link_state = get_state(c, link)
update_link!(c, link; state=(num_arp23=link_state.num_arp23 + Int32(1),))
add_diffusing_count!(c; species=:ARP23, chem_voxel, amount=-1)
1 # success
end,
rate = (c; link_state, kwargs...) -> NPF_MAX_ARP23 - link_state.num_arp23,
base_rate = 5E7, # nm³/s (bimolecular on-rate)
reactants_extra = "diffusing.ARP23",
invvolumepower = 1,
),
# Unimolecular unbinding: NPF·ARP2/3 → NPF + ARP2/3
(;
name = :arp23_unbind,
affect! = (c; link, chem_voxel, kwargs...) -> let
link_state = get_state(c, link)
update_link!(c, link; state=(num_arp23=link_state.num_arp23 - Int32(1),))
add_diffusing_count!(c; species=:ARP23, chem_voxel, amount=+1)
1 # success
end,
rate = (c; link_state, kwargs...) -> link_state.num_arp23,
base_rate = 0.01, # 1/s per bound ARP2/3
),
],
],
)MEDYAN.addreaction!
Add a reaction to the system. Return s
reactionexpr is a string describing the reaction stoichiometry
reactionexpr is comprised of reactant and product parts seperated by a "-->"
All whitespace characters are ignored.
Each side is then split by "+" to get the species names.
Repeated or extra "+" are ignored.
A species name can be prepended by a positive integer to represent multiple copies.
rate::Float64: Base rate for the reaction. ((nm³)^(invvolumepower)/s) rate constants correspond to stochastic rate constants in the sense used by Gillespie (J. Comp. Phys., 1976, 22 (4)).invvolumepower::Int:volumefactor= (1/volume)^invvolumepowerwherevolumeis the volume of the compartment in nm³. Generally this is 0 for reactions without another diffusing reactant, and 1 if there is another diffusing reactant.
Example good reactionexpr
"diffusing.a + diffusing.b --> diffusing.c"
"diffusing.c --> diffusing.a + diffusing.b"
"+ + diffusing.c + --> + diffusing.a + + diffusing.b + +"
" --> diffusing.a + diffusing.b"
"diffusing.a + diffusing.b --> "
"diffusing.a + diffusing.a --> "
"2diffusing.a --> "
"2diffusing.a --> 20diffusing.a"
"diffusing.c + diffusing.b --> diffusing.c + diffusing.b"
"fixedspecies.rate1b --> fixedspecies.g"
"fixedspecies.rate1b + fixedspecies.g --> fixedspecies.g"
"fixedspecies.rate1b + 23fixedspecies.g --> fixedspecies.g"
"fixedspecies.g --> fixedspecies.rate1b + 23fixedspecies.g"
"fixedspecies.g + fixedspecies.rate1b--> 2fixedspecies.rate1b + 23fixedspecies.g"
"filamentsite.MT.d --> filamentsite.MT.d"
"filamentsite.MT.d + diffusing.a --> filamentsite.MT.d"
"fixedspecies.g --> diffusing.a"
"diffusing.a --> fixedspecies.g"
"filamentsite.actin.pm + diffusing.a --> filamentsite.actin.pm"MEDYAN.addreactioncallback!
Like addreaction! but also adds callback. callback is called when the reaction happens with input of MEDYAN.Context and Int the chem_voxel id where the reaction happened.
The callback should handle updating species counts.
MEDYAN.errorcheck_addcallback(callback,s::SysDef) can optionally be overloaded to add errorchecking when the callback is added.
MEDYAN.def_reaction!
Add a chem_voxel reaction to the system definition. Returns s.
reaction_expr is a string describing the reaction. The format depends on whether affect! is provided:
- Without
affect!:reaction_exprmust contain"-->"to separate reactants from products. Stoichiometry is computed automatically. - With
affect!:reaction_exprlists only the reactants (no"-->"). Net stoichiometry is zero; the callback handles all state changes.
reaction_expr is comprised of reactant and product parts separated by "-->". All whitespace is ignored. Each side is split by "+" to get species names. Repeated or extra "+" are ignored. A species name can be prepended by a positive integer to represent multiple copies (e.g., "2diffusing.A").
Species name prefixes:
diffusing.X— diffusing speciesfixedspecies.X— fixed (non-diffusing) speciesfilamentsite.T.N— filament monomer sitefilamentendsite.T.N— filament end sitedecimated_2mon_site.N— decimated two-monomer site
Keyword Arguments
Rate
base_rate::Float64: Rate constant. Units:(nm³)^invvolumepower / s. Rate constants are stochastic rate constants in the sense of Gillespie (1976).invvolumepower::Int = 0:volumefactor = (1/volume)^invvolumepowerwherevolumeis the chem_voxel volume in nm³. Typically 0 for unimolecular, 1 for bimolecular, 2 for trimolecular.
Callback (optional)
affect!: Callback fired when the reaction occurs. Signature:(c::Context; chem_voxel::Int, kwargs...) -> Any. When provided,reaction_exprmust not contain"-->".
Examples
using MEDYAN
s = MEDYAN.SysDef()
def_diffusing_species!(s, :A; coeff=2.5e7)
def_diffusing_species!(s, :B; coeff=2.5e7)
def_diffusing_species!(s, :C; coeff=2.5e7)
# Bimolecular: A + B → C
def_reaction!(s, "diffusing.A + diffusing.B --> diffusing.C";
base_rate = 1.5e6,
invvolumepower = 1,
)
# Unimolecular: C → A + B
def_reaction!(s, "diffusing.C --> diffusing.A + diffusing.B";
base_rate = 1.75,
)
# Zero-order creation
def_reaction!(s, " --> diffusing.A + diffusing.B";
base_rate = 1.75,
)
# With a callback (reactants only, no "-->")
def_reaction!(s, "diffusing.A + diffusing.B";
base_rate = 1e8,
invvolumepower = 1,
affect! = (c; chem_voxel, kwargs...) -> begin
# custom logic here
nothing
end,
)MEDYAN.def_fila_reaction!
Add a filament monomer reaction. The reaction fires at monomers whose local neighborhood matches a pattern of states. When it fires, the matched monomers can be changed to new, or a custom affect! callback can handle arbitrary side effects.
All pattern positions must lie on the filament. Monomers near the ends where the pattern would extend past the boundary are automatically skipped. Use def_fila_tip_reaction! for reactions at filament ends.
Keyword Arguments
Required
fila_type::Symbol: Filament type (e.g.,:actin).name::Symbol: Unique name for this reaction site.match::Vector: Pattern of monomer states to match, ordered minus → plus end. Each element can be:Symbol— exact state (e.g.,:a)Vector{Symbol}— any of these states (e.g.,[:a, :b])anystate— any state
State change (provide exactly one)
new::Vector{Symbol}: States to assign to the matched monomers after firing. Must be the same length asmatch. Cannot be used withaffect!.affect!: Custom callback(c::Context; chem_voxel::Int, center::FilaMonoIdx, kwargs...) -> Int. Called instead of automatic state changes. The returnedIntis a status code. Cannot be used withnew.
Stoichiometry
net_stoich::Vector{Pair{Symbol,Int}} = Pair{Symbol,Int}[]: Net change to diffusing species when the reaction fires. Each entry isspecies_name => amount, e.g.[:ligand => -1]to consume one ligand per firing. The callback handles the count update automatically. Can only be used withnew, not withaffect!.
Rate
base_rate::Float64: Rate constant. Units:(nm³)^invvolumepower / s.invvolumepower::Int = 0: Set to 0 for unimolecular, 1 for bimolecular.reactants_extra::String = "": Additional reactant species that contribute to propensity (e.g.,"diffusing.ligand"). The species is not consumed automatically.
Geometry
center::Int = cld(length(match), 2): Which position inmatchis the center of the reaction. Determines which chem_voxel the reaction is assigned to.
Examples
using MEDYAN
s = MEDYAN.SysDef()
def_fila_type!(s; name=:actin, mono_states=[:a, :b, :c], param=MEDYAN.ACTIN_TWIST_PARAMS)
# Aging: every :a monomer transitions to :b
def_fila_reaction!(s;
fila_type = :actin, name = :aging,
match = [:a],
new = [:b],
base_rate = 2.4,
)
# Cooperative transition: :a next to :b on the minus side becomes :b
def_fila_reaction!(s;
fila_type = :actin, name = :cooperate,
match = [:b, :a],
new = [:b, :b],
center = 2,
base_rate = 0.4,
)
# Bimolecular binding with a diffusing ligand using affect!
def_diffusing_species!(s, :ligand; coeff=2.5e7)
def_fila_reaction!(s;
fila_type = :actin, name = :bind_affect,
match = [:a],
affect! = (c; center, chem_voxel, kwargs...) -> let
update_fila_mono_state!(c, center, :b)
add_diffusing_count!(c; species=:ligand, chem_voxel, amount=-1)
1
end,
base_rate = 1e6,
invvolumepower = 1,
reactants_extra = "diffusing.ligand",
)
# Bimolecular binding with a diffusing ligand using net_stoich
def_fila_reaction!(s;
fila_type = :actin, name = :bind_stoich,
match = [:a],
new = [:b],
net_stoich = [:ligand => -1],
base_rate = 1e6,
invvolumepower = 1,
reactants_extra = "diffusing.ligand",
)
# Match a set of states: :a or :b → :c
def_fila_reaction!(s;
fila_type = :actin, name = :a_or_b,
match = [[:a, :b]],
new = [:c],
base_rate = 1.0,
)
# anystate with neighbor constraint: any monomer whose plus neighbor is :a
def_fila_reaction!(s;
fila_type = :actin, name = :any_then_a,
match = [anystate, :a],
new = [:a, :b],
center = 2,
base_rate = 1.0,
)MEDYAN.def_fila_tip_reaction!
Add a filament tip reaction that fires only at either the plus or minus end of a filament.
This mirrors def_fila_reaction! in API style and rate wiring, but the pattern is anchored at a filament end. new may be shorter or longer than match to represent depolymerization or polymerization.
match and new are always ordered from minus end toward plus end. If is_minus_end = true, the pattern is matched against the first length(match) monomers; if is_minus_end = false, it is matched against the last length(match) monomers.
Keyword Arguments
Required
fila_type::Symbol: Filament type (e.g.,:actin).name::Symbol: Unique name for this tip site.is_minus_end::Bool: Which filament end to target.truetargets the minus end;falsetargets the plus end.match::Vector: Pattern of monomer states to match (minus → plus order). Elements can beSymbol,Vector{Symbol}(state set), oranystate.spacing::Float64: Space needed at the filament end for this reaction (nm). This affects a load-force rate factor viaexp(-β * spacing * loadforce)whereβis 1/kT andloadforceis the external force pushing axially on the end of the filament.
State change (provide exactly one)
new::Vector{Symbol}: New monomer states (minus → plus order) for the tip region. Iflength(new) > length(match), monomers are added. Iflength(new) < length(match), monomers are removed. Cannot be used withaffect!.affect!: Custom callback(c::Context; chem_voxel::Int, tip::FilaTipIdx, kwargs...) -> Int. Called instead of automatic changes. Cannot be used withnew.
Stoichiometry
net_stoich::Vector{Pair{Symbol,Int}} = Pair{Symbol,Int}[]: Net change to diffusing species when the reaction fires. Each entry isspecies_name => amount, e.g.[:actin_mon => -1]to consume one monomer per polymerization. The callback handles the count update automatically. Can only be used withnew, not withaffect!.
Rate
base_rate::Float64: Rate constant. Units:(nm³)^invvolumepower / s.invvolumepower::Int = 0: Set to 0 for unimolecular, 1 for bimolecular.reactants_extra::String = "": Additional reactant species that contribute to propensity (e.g.,"diffusing.ligand"). The species is not consumed automatically.added_monomers::Union{Int, Nothing}: The number of monomers added by the reaction. This is automatically determined ifnewis passed. Each filament has a limited number of monomers that can be added per end
between mechanic solves given by FilamentMechParams.max_num_unmin_end. The filament tip reaction is disabled on tips where the added monomers would cause that limit to be exceeded.
Examples
using MEDYAN
s = MEDYAN.SysDef()
def_fila_type!(s; name=:actin, mono_states=[:a, :plusend, :minusend], param=MEDYAN.ACTIN_TWIST_PARAMS)
def_diffusing_species!(s, :actin_mon; coeff=2.5e7)
# Plus-end polymerization: add one monomer (bimolecular with diffusing actin)
def_fila_tip_reaction!(s;
fila_type = :actin,
name = :pp,
is_minus_end = false,
match = [:plusend],
spacing = 2.7,
new = [:a, :plusend],
net_stoich = [:actin_mon => -1],
base_rate = 1.93e7,
invvolumepower = 1,
reactants_extra = "diffusing.actin_mon",
)
# Plus-end polymerization: add one monomer (bimolecular with diffusing actin)
# Using affect!
def_fila_tip_reaction!(s;
fila_type = :actin,
name = :pp_affect,
is_minus_end = false,
match = [:a, :plusend],
spacing = 2.7,
affect! = (c; tip, chem_voxel, kwargs...) -> begin
update_fila_mono_state!(c, FilaMonoIdx(c, tip), :a)
polymerize_fila!(c, tip, :plusend)
add_diffusing_count!(c; species=:actin_mon, chem_voxel, amount=-1)
1
end,
added_monomers = 1,
base_rate = 1.93e7,
invvolumepower = 1,
reactants_extra = "diffusing.actin_mon",
)
# Minus-end depolymerization: remove one monomer, release actin back to solution
def_fila_tip_reaction!(s;
fila_type = :actin, name = :dm,
is_minus_end = true,
match = [:minusend, :a],
spacing = 0.0,
new = [:minusend],
net_stoich = [:actin_mon => +1],
base_rate = 1.4,
)MEDYAN.add_membranesitereaction!
Add a membrane site with the corresponding reaction with callback.
Keyword arguments:
- s: SysDef.
- name_newmembranesite: Symbol.
- membranediffusingreactants: Vector of symbols as membrane reactants. 0 or 1 reactant is currently supported.
- membranediffusingproducts: Vector of symbols as membrane products.
- reactionexpr_extra: Reaction expression for other species involved.
- rate: Float.
- changerage_bypotentialenergy: Whether the rate is affected by potential energy.
- invvolumepower: rate scaling with compartment volume.
Notes:
- If error occurs, this function does not ensure that s is unchanged.