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.Array) – Current simulation time (minutes)

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

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

  • next_reaction_time (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(eta=1.0, nu=0.01, a=1.0, Kh=90.0, nh=3.6, Kf=30.0, nf=3.6, timestep_minutes=5.0, max_gillespie_steps=10000)[source]

Bases: object

Static physics constants for the gene circuit system.

These are compile-time constants passed as static_argnames to jit. Changing these values requires recompilation but enables better optimization.

Default values from the CDC 2025 paper implementation.

eta: float = 1.0
nu: float = 0.01
a: float = 1.0
Kh: float = 90.0
nh: float = 3.6
Kf: float = 30.0
nf: float = 3.6
timestep_minutes: float = 5.0
max_gillespie_steps: int = 10000
__init__(eta=1.0, nu=0.01, a=1.0, Kh=90.0, nh=3.6, Kf=30.0, nf=3.6, 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[source]

Bases: object

Dynamic physics parameters for domain randomization.

These can be randomized per episode to create diverse dynamics. Currently empty but maintained for protocol consistency.

__init__()
replace(**updates)

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

myriad.envs.bio.ccasr_gfp.physics.compute_propensities(state, action, config)[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

  • config (PhysicsConfig) – Static physics constants

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

myriad.envs.bio.ccasr_gfp.physics.create_physics_params(**kwargs)[source]

Factory function to create PhysicsParams.

Parameters:

**kwargs – Reserved for future domain randomization parameters

Returns:

PhysicsParams instance

Return type:

PhysicsParams

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.Array) – Current timestep counter (RL timesteps, not Gillespie time)

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

  • F_target (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: object

Configuration for the CcaS-CcaR control task.

Composed of physics config and task config for clean separation.

physics: PhysicsConfig
task: TaskConfig
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.

property max_steps: int

Required by EnvironmentConfig protocol.

__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.

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

Step the control task forward one timestep.

Parameters:
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, **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.

  • **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)
>>> # Sinewave target
>>> env = make_env(target_type="sinewave", sinewave_period_minutes=600.0)

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.Array) – GFP fluorescence normalized by F_obs_normalizer

  • F_target (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.

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