MATLAB · chapter 10 of 13

Recipes

Complete, copy-and-run workflows. Each one ends with data and its ground truth on disk.

1. A benchmark time-series dataset

Fifty cells of a 30-gene scale-free network with intrinsic and measurement noise.

S     = grn.randomNetwork(30, Topology = "scalefree", MeanDegree = 2, ...
            FractionRepressors = 0.4, Seed = 2);
model = grn.buildModel(S, Logic = "multiplicative", Seed = 2, Name = "sf30");
sol   = grn.simulate(model, Cells = 50, RandomIC = true, Duration = 20, ...
            SampleDt = 0.25, Noise = "langevin", NoiseStrength = 0.2, ...
            NoiseSigma = 0.05, Seed = 2);
grn.export(sol, "sf30_timeseries.csv");
grn.export(sol, "sf30_timeseries.mat");
fig = grn.plotNetwork(model);        grn.exportFigure(fig, "sf30_network.pdf");
fig = grn.plotExpression(sol, Cell = 1); grn.exportFigure(fig, "sf30_expression.png");
Recipe 1 output: one cell of the benchmark dataset with the other cells behind it.
Recipe 1 output: one cell of the benchmark dataset with the other cells behind it.

2. A DREAM-style package: knockouts, knockdowns, and wild-type time series

model = grn.buildModel(grn.randomNetwork(10, Seed = 3), Seed = 3, Name = "net10");
ko = grn.knockoutScreen(model, NoiseSigma = 0.02, Seed = 3);
kd = grn.knockoutScreen(model, Type = "knockdown", NoiseSigma = 0.02, Seed = 4);
ts = grn.simulate(model, Cells = 10, RandomIC = true, Duration = 20, ...
        NoiseSigma = 0.02, Seed = 5);
grn.export(ko, "net10_knockouts.csv");
grn.export(kd, "net10_knockdowns.csv");
grn.export(ts, "net10_timeseries.csv");
all(ko.converged) && all(kd.converged)     % check before shipping

3. A dose-response experiment

Steady-state expression of every gene as a source gene is driven from 0 to 3 times its normal rate.

model = grn.buildModel(grn.motif("sim", Size = 5), Seed = 1);   % keeps labels TF, T1..T4
doses = linspace(0, 3, 13);
X = zeros(numel(doses), model.dim);
for d = 1:numel(doses)
    s = grn.stimulus("TF", "step", Baseline = doses(d), Amplitude = doses(d));
    X(d, :) = grn.steadyState(model, Stimulus = s).';
end
plot(doses, X(:, 2:end)); xlabel("input to TF"); ylabel("target expression")

A ramp gives the same information dynamically:

ramp = grn.stimulus("TF", "ramp", Baseline = 0, Amplitude = 3, Start = 0, Stop = 60);
sol = grn.simulate(model, Stimulus = ramp, Duration = 80);
grn.plotExpression(sol);

4. Single-cell-like snapshots

Many cells, one sample each at a random time after a stimulus.

model = grn.buildModel(grn.randomNetwork(20, Seed = 6), Stages = 2, Seed = 6);
on = grn.stimulus(1, "step", Amplitude = 4, Start = 0);
sol = grn.simulate(model, Stimulus = on, Cells = 200, RandomIC = true, ...
    Noise = "langevin", NoiseStrength = 0.3, Duration = 10, SampleDt = 0.1, Seed = 6);
rng(6);
tIdx = randi(numel(sol.t), sol.cells, 1);               % one time per cell
cellsX = zeros(sol.cells, sol.n);
for c = 1:sol.cells
    cellsX(c, :) = sol.mrna(tIdx(c), :, c);              % mRNA, like a scRNA-seq readout
end
pseudotime = sol.t(tIdx);
Recipe 4 output. Left: two genes across 200 cells, coloured by sampling time. Right: the snapshot matrix with cells ordered by their (hidden) sampling time.
Recipe 4 output. Left: two genes across 200 cells, coloured by sampling time. Right: the snapshot matrix with cells ordered by their (hidden) sampling time.

5. Comparing motif variants

Coherent and incoherent feed-forward loops under the same step input.

on = grn.stimulus("X", "step", Baseline = 0, Amplitude = 1, Start = 2);
for name = ["ffl_coherent", "ffl_incoherent"]
    model = grn.buildModel(name, Logic = "multiplicative", Seed = 1, ...
        Params = struct("gamma", [5; 1; 3], "vmax", 5, "basal", [5; 0; 0]));
    sol = grn.simulate(model, Stimulus = on, InitialCondition = zeros(3, 1), Duration = 20);
    fig = grn.plotExpression(sol); grn.exportFigure(fig, name + ".pdf");
end
Recipe 5 output. The coherent loop delays Z and then holds it; the incoherent loop produces a pulse.
Recipe 5 output. The coherent loop delays Z and then holds it; the incoherent loop produces a pulse.

Ten networks that share a topology but differ in kinetics.

S = grn.randomNetwork(15, Seed = 1);
for k = 1:10
    model = grn.buildModel(S, Seed = k, Name = "net15_" + k);
    sol = grn.simulate(model, Cells = 5, RandomIC = true, Seed = k);
    grn.export(sol, sprintf("net15_%02d.mat", k));
end

7. Sweeping a parameter of a motif

Where does the repressilator start to oscillate? (The analytic answer for Hill 4 is alpha = 2.)

alphas = 1.5:0.1:3;
amp = zeros(size(alphas));
for a = 1:numel(alphas)
    m = grn.buildModel("repressilator", Params = struct("k", alphas(a), "vmax", alphas(a)));
    sol = grn.simulate(m, Duration = 100, Transient = 300);
    amp(a) = max(sol.x(:, 1)) - min(sol.x(:, 1));
end
plot(alphas, amp, "o-"); xlabel("\alpha"); ylabel("amplitude")
Recipe 7 output (left) and the toggle switch's bifurcation diagram (right). Both thresholds land on the analytic value alpha = 2.
Recipe 7 output (left) and the toggle switch's bifurcation diagram (right). Both thresholds land on the analytic value alpha = 2.