CcaS-CcaR Optogenetic with GFP autoactivation Gene Circuit

Bi-stable genetic circuit control using optogenetics. Control GFP expression by modulating light input to the CcaS-CcaR two-component system.

Physics

Stochastic dynamics using the Gillespie algorithm for exact simulation.

Pure stateless physics for the CcaS-CcaR + GFP with self-activation gene circuit system.

This module contains the ground truth stochastic dynamics for the bi-stable genetic circuit, using the Gillespie algorithm for exact stochastic simulation.

The physics is completely decoupled from any task-specific logic (rewards, terminations, observations). It can be reused by different tasks (control, SysID, etc.).

System Description:

Light Input (U) → CcaSR (H) → GFP (F) with autoactivation feedback

Five Chemical Reactions: 1. CcaSR activation: ∅ → H (rate: eta * U) 2. CcaSR deactivation: H → ∅ (rate: nu * H) 3. F creation from H: ∅ → F (rate: 0.5 * a * H^nh / (Kh^nh + H^nh)) 4. F self-activation: ∅ → F (rate: 0.5 * a * F^nf / (Kf^nf + F^nf)) 5. F dilution: F → ∅ (rate: nu * F)

Reference:

Based on the bi-stable genetic circuit model from: “Control of a Bi-Stable Genetic System via Parallelized Reinforcement Learning” CDC 2025, https://gitlab.com/lugagnelab/pqn-control-cdc2025

class myriad.envs.bio.ccasr_gfp.physics.PhysicsState(time, H, F, next_reaction_time)[source]

Bases: NamedTuple

Pure physical state of the gene circuit system.

Variables:
  • time (jax.jaxlib._jax.Array) – Current simulation time (minutes)

  • H (jax.jaxlib._jax.Array) – CcaS-CcaR protein concentration (molecules)

  • F (jax.jaxlib._jax.Array) – GFP reporter protein concentration (molecules)

  • next_reaction_time (jax.jaxlib._jax.Array) – Scheduled time of next reaction (minutes). Set to inf when no reaction is pending (sample fresh). Preserved across RL step boundaries for physical accuracy.

time: Array

Alias for field number 0

H: Array

Alias for field number 1

F: Array

Alias for field number 2

next_reaction_time: Array

Alias for field number 3

to_array()[source]

Convert to flat array for NN-based agents.

Note: next_reaction_time is excluded as it’s internal bookkeeping.

Returns:

Array of shape (3,) with [time, H, F]

Return type:

Array

classmethod from_array(arr)[source]

Create state from flat array.

Parameters:

arr (Array) – Array of shape (3,) with [time, H, F]

Returns:

PhysicsState instance (next_reaction_time defaults to inf)

Return type:

PhysicsState

classmethod create(time, H, F, next_reaction_time=None)[source]

Factory method to create PhysicsState with default next_reaction_time.

Parameters:
  • time (Array) – Current simulation time

  • H (Array) – CcaS-CcaR protein concentration

  • F (Array) – GFP reporter protein concentration

  • next_reaction_time (Array | None) – Optional pending reaction time (defaults to inf)

Returns:

PhysicsState instance

Return type:

PhysicsState

class myriad.envs.bio.ccasr_gfp.physics.PhysicsConfig(timestep_minutes=5.0, max_gillespie_steps=10000)[source]

Bases: object

Static structural constants for the gene circuit system.

These are compile-time constants passed as static_argnames to jit. They define the experimental platform and circuit architecture — values that never change between cells or experiments.

Kinetic parameters (nu, Kh, nh, Kf, nf) belong in PhysicsParams because they vary between cells and are the targets of system identification.

timestep_minutes: float = 5.0
max_gillespie_steps: int = 10000
__init__(timestep_minutes=5.0, max_gillespie_steps=10000)
replace(**updates)

Returns a new object replacing the specified fields with new values.

class myriad.envs.bio.ccasr_gfp.physics.PhysicsParams(nu=0.01, Kh=90.0, nh=3.6, Kf=30.0, nf=3.6)[source]

Bases: object

Dynamic physics parameters — vmappable over parameter space.

These are the kinetic parameters that vary between cells (domain randomization) or are unknown and must be inferred (system identification).

nu: float | Array = 0.01
Kh: float | Array = 90.0
nh: float | Array = 3.6
Kf: float | Array = 30.0
nf: float | Array = 3.6
__init__(nu=0.01, Kh=90.0, nh=3.6, Kf=30.0, nf=3.6)
replace(**updates)

Returns a new object replacing the specified fields with new values.

class myriad.envs.bio.ccasr_gfp.physics.PhysicsParamsPrior(nu_loc=-4.605170185988091, nu_scale=0.0, Kh_loc=4.499809670330265, Kh_scale=0.0, nh_loc=1.2809338454620642, nh_scale=0.0, Kf_loc=3.4011973816621555, Kf_scale=0.0, nf_loc=1.2809338454620642, nf_scale=0.0)[source]

Bases: object

Log-normal prior over kinetic parameters.

Each parameter p is sampled as: p ~ exp(Normal(loc, scale)). With scale=0 the distribution collapses to a point mass at exp(loc), so the default (all scales zero) is fully deterministic and backward compatible.

nu_loc: float | Array = -4.605170185988091
nu_scale: float | Array = 0.0
Kh_loc: float | Array = 4.499809670330265
Kh_scale: float | Array = 0.0
nh_loc: float | Array = 1.2809338454620642
nh_scale: float | Array = 0.0
Kf_loc: float | Array = 3.4011973816621555
Kf_scale: float | Array = 0.0
nf_loc: float | Array = 1.2809338454620642
nf_scale: float | Array = 0.0
sample(key)[source]
__init__(nu_loc=-4.605170185988091, nu_scale=0.0, Kh_loc=4.499809670330265, Kh_scale=0.0, nh_loc=1.2809338454620642, nh_scale=0.0, Kf_loc=3.4011973816621555, Kf_scale=0.0, nf_loc=1.2809338454620642, nf_scale=0.0)
replace(**updates)

Returns a new object replacing the specified fields with new values.

myriad.envs.bio.ccasr_gfp.physics.compute_propensities(state, action, params)[source]

Compute reaction propensities (rates) for all five reactions.

Parameters:
  • state (PhysicsState) – Current physical state (time, H, F)

  • action (Array) – Discrete action {0, 1} representing light input U

  • params (PhysicsParams) – Kinetic parameters (nu, Kh, nh, Kf, nf) — vmappable

Returns:

Array of 5 propensities for reactions [R1, R2, R3, R4, R5]

Return type:

Array

myriad.envs.bio.ccasr_gfp.physics.apply_reaction(state, reaction_idx)[source]

Apply a single reaction to update the state.

Uses jax.lax.switch for JAX-compatible control flow.

Parameters:
  • state (PhysicsState) – Current physical state

  • reaction_idx (Array) – Index of reaction to apply (0-4)

Returns:

Updated physical state after reaction

Return type:

PhysicsState

myriad.envs.bio.ccasr_gfp.physics.step_physics(key, state, action, params, config, previous_action, interval_start)[source]

Pure physics step using a discrete Gillespie algorithm.

Runs Gillespie simulation from current time until the end of the current interval (interval_start + timestep_minutes). Intervals are at fixed absolute times (0, 5, 10, 15…), matching the physical setup where observations and actions occur at regular intervals.

Parameters:
  • key (Array) – RNG key for stochastic simulation

  • state (PhysicsState) – Current physical state (time, H, F, next_reaction_time)

  • action (Array) – Discrete action {0, 1} representing light input

  • params (PhysicsParams) – Dynamic parameters

  • config (PhysicsConfig) – Static physics constants

  • previous_action (Array) – Action from previous timestep. If different from action, the pending reaction time is invalidated (propensities changed).

  • interval_start (Array) – Start time of current interval (t * timestep_minutes).

Returns:

Next physical state after simulating until interval end

Return type:

PhysicsState

Control Task

Track a target GFP expression level.

Control task wrapper for CcaS-CcaR + GFP gene circuit.

Standard tracking task: Control GFP expression (F) to match a target trajectory. Reward: Negative absolute error between F and F_target.

Task variants: - Constant target: Fixed GFP level (default: F_target = 25) - Sinewave target: Time-varying sinusoidal trajectory

class myriad.envs.bio.ccasr_gfp.tasks.control.ControlTaskState(physics, t, U, F_target)[source]

Bases: NamedTuple

State for the control task.

Variables:
  • physics (myriad.envs.bio.ccasr_gfp.physics.PhysicsState) – The underlying physics state (time, H, F)

  • t (jax.jaxlib._jax.Array) – Current timestep counter (RL timesteps, not Gillespie time)

  • U (jax.jaxlib._jax.Array) – Previous action (light input from last timestep, for action-toggle detection)

  • F_target (jax.jaxlib._jax.Array) – Target trajectory for GFP expression [current, t+1, …, t+n_horizon]

physics: PhysicsState

Alias for field number 0

t: Array

Alias for field number 1

U: Array

Alias for field number 2

F_target: Array

Alias for field number 3

class myriad.envs.bio.ccasr_gfp.tasks.control.ControlTaskConfig(physics=<factory>, task=<factory>, target_type='constant', n_horizon=1, F_target_constant=25.0, sinewave_period_minutes=600.0, sinewave_amplitude=20.0, sinewave_vshift=30.0)[source]

Bases: BaseCcasrGfpTaskConfig

Configuration for the CcaS-CcaR control task.

target_type: str = 'constant'
n_horizon: int = 1
F_target_constant: float = 25.0
sinewave_period_minutes: float = 600.0
sinewave_amplitude: float = 20.0
sinewave_vshift: float = 30.0
property dt: float

Timestep duration in minutes.

__init__(physics=<factory>, task=<factory>, target_type='constant', n_horizon=1, F_target_constant=25.0, sinewave_period_minutes=600.0, sinewave_amplitude=20.0, sinewave_vshift=30.0)
replace(**updates)

Returns a new object replacing the specified fields with new values.

class myriad.envs.bio.ccasr_gfp.tasks.control.ControlTaskParams(physics=<factory>)[source]

Bases: object

Parameters for the control task.

physics: PhysicsParams
__init__(physics=<factory>)
replace(**updates)

Returns a new object replacing the specified fields with new values.

class myriad.envs.bio.ccasr_gfp.tasks.control.ControlTaskParamsPrior(physics=<factory>)[source]

Bases: object

Prior distribution over control task parameters.

physics: PhysicsParamsPrior
sample(key)[source]
__init__(physics=<factory>)
replace(**updates)

Returns a new object replacing the specified fields with new values.

myriad.envs.bio.ccasr_gfp.tasks.control.step(key, state, action, params, config)

Step the control task forward one timestep.

Parameters:
  • key (Array) – RNG key for stochastic physics simulation

  • state (ControlTaskState) – Current task state

  • action (Array) – Discrete action {0, 1} for light input

  • params (ControlTaskParams) – Task parameters

  • config (ControlTaskConfig) – Task configuration (static)

Returns:

Next observation next_state: Next task state reward: Reward (negative absolute error) done: Termination flag (1.0 if done, 0.0 otherwise) info: Dict with current protein levels for logging

Return type:

obs_next

myriad.envs.bio.ccasr_gfp.tasks.control.reset(key, params, config)

Reset the control task to initial state.

Initializes the system at zero protein concentrations and generates initial target.

Parameters:
Returns:

Initial observation (CcasCcarControlObs with named fields) state: Initial task state

Return type:

obs

myriad.envs.bio.ccasr_gfp.tasks.control.get_obs(state, params, config)[source]

Extract observation from state.

Returns a structured observation with named fields for semantic access by classical controllers. Neural network agents can call .to_array().

Parameters:
Returns:

CcasCcarControlObs with named fields (F_normalized, U_obs, F_target)

Return type:

CcasrGfpControlObs

myriad.envs.bio.ccasr_gfp.tasks.control.get_obs_shape(config)[source]

Get the shape of the observation space.

Observation: [F, F_target[0:n_horizon+1]] Shape: (1 + n_horizon + 1,)

Parameters:

config (ControlTaskConfig) – Task configuration

Returns:

Observation shape tuple

Return type:

tuple[int, …]

myriad.envs.bio.ccasr_gfp.tasks.control.get_action_space(config)[source]

Get the discrete action space for the environment.

Parameters:

config (ControlTaskConfig) – Task configuration (unused)

Returns:

0 (light off) and 1 (light on)

Return type:

Discrete space with 2 actions

myriad.envs.bio.ccasr_gfp.tasks.control.make_env(config=None, params=None, params_prior=None, **kwargs)[source]

Create a Ccasr-gfp control task environment.

Parameters:
  • config (ControlTaskConfig | None) – Custom ControlTaskConfig. If None, uses defaults.

  • params (ControlTaskParams | None) – Custom ControlTaskParams. If None, creates from kwargs.

  • params_prior (ControlTaskParamsPrior | None) – Optional prior for domain randomization. If set, env.sample_params_fn will sample distinct θ* per parallel env. Can also be triggered via flat kwargs (e.g. nu_scale=0.3).

  • **kwargs – Keyword arguments for creating config/params if not provided.

Returns:

Environment instance for the control task

Return type:

Environment[ControlTaskState, ControlTaskConfig, ControlTaskParams, CcasrGfpControlObs]

Example

>>> # Constant target at F=25
>>> env = make_env(F_target_constant=25.0)
>>> # Domain randomization
>>> env = make_env(nu_scale=0.3, Kh_scale=0.2)

Shared utilities for CcaS-CcaR + GFP gene circuit task wrappers.

class myriad.envs.bio.ccasr_gfp.tasks.base.CcasrGfpControlObs(F_normalized, F_target)[source]

Bases: NamedTuple

CcaS-CcaR control task observation with named fields.

Note: This is a partially observable system. The agent does not directly observe the light input (U) or the CcaSR concentration (H).

Variables:
  • F_normalized (jax.jaxlib._jax.Array) – GFP fluorescence normalized by F_obs_normalizer

  • F_target (jax.jaxlib._jax.Array) – Target trajectory [current, t+1, …, t+n_horizon]

F_normalized: Array

Alias for field number 0

F_target: Array

Alias for field number 1

to_array()[source]

Convert to flat array for NN-based agents.

Returns:

Array of shape (1 + n_horizon + 1,) with [F, F_target…]

Return type:

Array

classmethod from_array(arr)[source]

Create observation from flat array.

Parameters:

arr (Array) – Array of shape (1 + n_horizon + 1,) with [F, F_target…]

Returns:

CcasCcarControlObs instance

Return type:

CcasrGfpControlObs

class myriad.envs.bio.ccasr_gfp.tasks.base.TaskConfig(max_steps=288, F_obs_normalizer=80.0)[source]

Bases: object

Base configuration shared by all CcaS-CcaR tasks.

These define the task-specific episode limits and observation normalization.

max_steps: int = 288
F_obs_normalizer: float = 80.0
__init__(max_steps=288, F_obs_normalizer=80.0)
replace(**updates)

Returns a new object replacing the specified fields with new values.

class myriad.envs.bio.ccasr_gfp.tasks.base.BaseCcasrGfpTaskConfig(physics=<factory>, task=<factory>)[source]

Bases: object

Shared config fields for all CcaS-CcaR task wrappers.

Provides physics config, task config, and the max_steps protocol property in one place so individual task configs don’t repeat them.

physics: PhysicsConfig
task: TaskConfig
property max_steps: int

Required by EnvironmentConfig protocol.

__init__(physics=<factory>, task=<factory>)
replace(**updates)

Returns a new object replacing the specified fields with new values.

myriad.envs.bio.ccasr_gfp.tasks.base.step_physics_interval(key, physics, t, prev_action, action, params, config)[source]

Step physics forward one interval and return (next_physics, t + 1).

Centralises the interval_start calculation and step_physics call that every task _step function would otherwise duplicate.

myriad.envs.bio.ccasr_gfp.tasks.base.check_termination(t, task_config)[source]

Common termination check for CcaS-CcaR tasks.

The episode terminates when maximum timesteps is reached.

Parameters:
  • t (Array) – Current timestep counter

  • task_config (TaskConfig) – TaskConfig with max_steps

Returns:

1.0 if terminated, 0.0 otherwise (as float for JAX compatibility)

Return type:

done

myriad.envs.bio.ccasr_gfp.tasks.base.get_action_space()[source]

Get the discrete action space for CcaS-CcaR.

Returns:

0 (red light) and 1 (green light)

Return type:

Discrete space with 2 actions

myriad.envs.bio.ccasr_gfp.tasks.base.sample_initial_physics(key)[source]

Sample initial physics state.

We start from zero proteins at time 0. This represents the initial state before any light input.

Parameters:

key (Array) – RNG key for random initialization (unused)

Returns:

PhysicsState initialized to zero concentrations

Return type:

PhysicsState

myriad.envs.bio.ccasr_gfp.tasks.base.generate_constant_target(n_horizon, F_target_value)[source]

Generate a constant target trajectory.

Parameters:
  • n_horizon (int) – Number of future timesteps to predict

  • F_target_value (float) – Constant target value for F

Returns:

Array of shape (n_horizon + 1,) with constant target values

Return type:

Array

myriad.envs.bio.ccasr_gfp.tasks.base.generate_sinewave_target(key, t, n_horizon, timestep_minutes, period_minutes=600.0, amplitude=20.0, vshift=30.0)[source]

Generate a sinusoidal target trajectory.

Creates a time-varying target that follows a sine wave pattern. Used for testing tracking performance on dynamic targets.

Parameters:
  • key (Array) – RNG key for random phase initialization

  • t (Array) – Current timestep counter

  • n_horizon (int) – Number of future timesteps to predict

  • timestep_minutes (float) – Duration of each RL timestep in minutes

  • period_minutes (float) – Period of the sine wave in minutes (default: 600 = 10 hours)

  • amplitude (float) – Amplitude of the sine wave (default: 20)

  • vshift (float) – Vertical shift / DC offset (default: 30)

Returns:

Array of shape (n_horizon + 1,) with sinusoidal target values

Return type:

Array