Python · chapter 2 of 13

Concepts and conventions

This chapter states the model equations exactly as the code implements them, and the conventions that every function follows.

The regulatory network

A network of n genes is a signed adjacency matrix S of shape (n, n) with entries in {-1, 0, +1}:

Row i therefore lists the regulators of gene i, and column j lists the targets of gene j. Self-regulation is a nonzero diagonal entry. Any numeric array is accepted; its sign is taken. Gene indices are 0-based; labels ("G1", "G2", ... by default) can be used instead of indices wherever a gene is named.

This orientation, "row = inputs of the node", is shared with the MATLAB toolbox. When you write an edge list, source = j and target = i.

For a three-gene chain A activates B, B represses C, C represses A:

import numpy as np
S3 = np.zeros((3, 3)); S3[1, 0] = 1; S3[2, 1] = -1; S3[0, 2] = -1
m3 = grn.build_model(S3, labels=["A", "B", "C"])
files = grn.export(grn.simulate(m3, duration=1), "abc.csv")
print(open(files[1]).read())          # abc_truth.csv
Output
S3 =
 [[ 0.  0. -1.]
 [ 1.  0.  0.]
 [ 0. -1.  0.]]
source,target,sign
A,B,1
B,C,-1
C,A,-1

One-stage kinetics (default)

Each gene has one state x_i, read as the level of its product (protein). The dynamics are

dx_i/dt = production_i(x) - gamma_i x_i

where gamma_i is a first-order degradation rate and production_i combines the regulators through Hill functions. For an edge from j to i with threshold K_ij and Hill coefficient h_ij:

h+(x_j) = x_j^h / (K^h + x_j^h)      activation
h-(x_j) = 1 - h+(x_j)                 repression

R_ij(x_j) denotes h+ for an activating edge and h- for a repressing edge. Two ways of combining regulators are implemented, selected by logic in grn.build_model:

logicProduction rateReads as
"additive" (default)basal_i + sum_j k_ij R_ij(x_j)OR-like: each regulator contributes independently with weight k_ij
"multiplicative"basal_i + vmax_i prod_j R_ij(x_j)AND-like: every activator must be present and every repressor absent; the product runs over regulators only

Genes with no regulators are constitutive: their production is basal_i, which grn.build_model sets equal to vmax_i. Regulated genes get a small leak, basal_i between 0 and 10 % of vmax_i by default.

The Hill coefficient is required to be at least 1 so that the right-hand side is differentiable at zero.

Hill functions for four coefficients. Every curve passes through one half at the threshold K; the coefficient sets how switch-like the response is.
Hill functions for four coefficients. Every curve passes through one half at the threshold K; the coefficient sets how switch-like the response is.
Production of a gene with one activator and one repressor under the two logics. Additive (OR) gives a graded sum; multiplicative (AND) needs the activator present and the repressor absent at the same time.
Production of a gene with one activator and one repressor under the two logics. Additive (OR) gives a graded sum; multiplicative (AND) needs the activator present and the repressor absent at the same time.

Two-stage kinetics (mRNA and protein)

With stages=2 every gene has an mRNA level m_i and a protein level q_i. Transcription is regulated by the regulators' proteins, translation is linear, and both species degrade:

dm_i/dt = production_i(q) - gamma_i  m_i
dq_i/dt = beta_i m_i     - gammaP_i q_i

The state vector is [m, q], of length 2n, with mRNA first. model.state_labels names the entries (G1_mRNA, ..., G1_protein, ...) and model.state_gene maps each state to its gene. Solutions expose sol.mrna and sol.protein.

At steady state q_i* = (beta_i / gammaP_i) m_i*. By default beta_i = gammaP_i, so protein and mRNA coincide at steady state and the motif presets (whose thresholds K are tuned to the one-stage scale) behave the same way in both models. See Building models for the sampling ranges.

The repressilator in both models. The mRNA stage adds a lag between each transcript and its protein and lengthens the period; with slow proteins the mRNA traces become nearly square because transcription saturates while the protein does the timing.
The repressilator in both models. The mRNA stage adds a lag between each transcript and its protein and lengthens the period; with slow proteins the mRNA traces become nearly square because transcription saturates while the protein does the timing.

Time and units

Time is dimensionless. Degradation rates default to the range 0.5 to 1.5, so one time unit is roughly one mRNA lifetime. Expression is in arbitrary concentration units set by vmax / gamma, between about 1 and 10 by default. Nothing in the package assumes molecule counts; the chemical Langevin noise model introduces an effective system size through its noise_strength.

Seeds

Every function that draws random numbers has a seed argument, default 0, and creates numpy.random.default_rng(seed) before drawing. seed=None uses NumPy's unseeded entropy. In grn.simulate cell k uses seed + k, and observation noise uses seed + cells; in grn.knockout_screen the seed governs the measurement noise.

The Model object

grn.build_model returns a Model dataclass with these attributes. Parameter arrays are the ground truth for the kinetics; the f, jac, and production callables are rebuilt from them by grn.attach_handles whenever the package changes a parameter.

AttributeShapeMeaning
name, typeModel name; type is always "grn"
n, dimNumber of genes; number of states (n or 2n)
S(n, n)Signed adjacency
logic, stages"additive"/"multiplicative"; 1 or 2
k(n, n)Edge weights for additive logic (0 off the edges)
K, hill(n, n)Threshold and Hill coefficient per edge (1 off the edges)
basal, gamma, vmax(n,)Leak or constitutive rate, degradation, maximal production
beta, gammaP(n,)Translation and protein degradation (None for one stage)
ic(dim,)Default initial condition
labels, state_labelstuplesGene names; state names
state_gene, species(dim,)Gene index and "mRNA"/"protein" of each state
decay, scale(dim,)Degradation rate and characteristic level of each state
hasReg, indegree, outdegree(n,)Regulated flag and degrees
perturbationdictGenes and factors applied by grn.perturb
stimulus, breaksStimuli attached by grn.stimulate and the times at which they jump
f, jac, productioncallablesf(t, x), jac(t, x), production(t, x)
seed, optionsThe seed and sampling ranges used

Models are immutable from the package's point of view: perturb and stimulate return modified copies. If you edit a parameter array by hand, call grn.attach_handles(model) afterwards so the callables see the change. Models pickle cleanly (the callables are rebuilt on load).

What a model looks like at the prompt:

m3
Output
Model(name='grn', n=3, stages=1, logic='additive', edges=3, dim=3)
fields: name, n, S, logic, stages, k, K, hill, basal, gamma, vmax, beta, gammaP, ic, labels, seed, options, perturbation, stimulus, type, dim, hasReg, indegree, outdegree, state_gene, species, state_labels, decay, scale, breaks, f, jac, production

The Solution object

grn.simulate returns a Solution with:

AttributeMeaning
tTime grid, shape (samples,), starting at 0 after any transient
xData, (samples, dim) for one cell, (samples, dim, cells) otherwise; observation noise included
x_cleanNoise-free copy when observation noise was added, else None
mrna, proteinThe two halves of x for two-stage models
u, u_addRealised stimulus inputs, (samples, n) (multiplicative factor and additive term)
model, S, labels, state_labels, n, dim, stagesThe generating model and its ground truth
cells, icNumber of cells and the initial condition of each, (dim, cells)
stimulusThe stimuli that were applied
type, name, options"timeseries", the model name, and every option used

grn.knockout_screen returns a Screen with X ((len(genes) + 1, dim), wild type first), rows, genes, converged, and factor; its type is "screen".