///|
pub struct Generator {
priv states : Array[Array[Int32]]
priv edges : Array[Array[(Int32, Double)]]
priv exits : Array[Double]
}
///|
fn count_key(state : Array[Int32]) -> String {
state.map(n => n.to_string()).join(",")
}
///|
/// Constructs a closed CTMC from an explicit user-supplied state set. A missing
/// positive-rate successor is an error, never an undocumented reflecting wall.
pub fn Network::generator(
self : Network,
states : Array[Array[Int32]],
) -> Generator raise KineticsError {
if states.is_empty() || states.length() > 4096 {
raise Budget("CTMC needs 1..4096 explicit states")
}
if states.length() > 2000000 / self.names.length() {
raise Budget("CTMC state cells")
}
let indices : Map[String, Int32] = Map([])
for i in 0.. index
None =>
raise Invalid(
"CTMC state set is not closed under positive-rate reactions",
)
}
if target == i {
continue
}
edges.push((target, a[j]))
total += a[j]
finite(total, "CTMC exit rate")
edge_count += 1
if edge_count > 1000000 {
raise Budget("CTMC edge budget")
}
}
rows.push(edges)
exits.push(total)
}
{ states: states.map(s => s.copy()), edges: rows, exits, }
}
///|
pub fn Generator::size(self : Generator) -> Int32 {
self.states.length()
}
///|
pub fn Generator::count_states(self : Generator) -> Array[Array[Int32]] {
self.states.map(s => s.copy())
}
///|
pub fn Generator::exit_rates(self : Generator) -> Array[Double] {
self.exits.copy()
}
///|
/// Parallel channels are retained as separate edges; dense() sums them.
pub fn Generator::row_edges(
self : Generator,
row : Int32,
) -> Array[(Int32, Double)] raise KineticsError {
if row < 0 || row >= self.size() {
raise Invalid("generator row index")
}
self.edges[row].copy()
}
///|
pub fn Generator::dense(
self : Generator,
) -> Array[Array[Double]] raise KineticsError {
if self.size() > 512 {
raise Budget("dense generator limited to 512 states")
}
let matrix = Array::makei(self.size(), _ => Array::make(self.size(), 0.0))
for i in 0.. Unit raise KineticsError {
if p.length() != size {
raise Invalid("probability shape")
}
let mut sum = 0.0
for x in p {
nonnegative(x, "probability")
sum += x
}
if (sum - 1.0).abs() > 1.0e-10 {
raise Invalid("probability mass must sum to one")
}
}
///|
/// Forward master-equation derivative for a row probability vector p.
pub fn Generator::derivative(
self : Generator,
probabilities : Array[Double],
) -> Array[Double] raise KineticsError {
validate_probability(probabilities, self.size())
let result = Array::make(self.size(), 0.0)
for i in 0..