Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions ecoli/composites/ecoli_master_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"""

import os
from itertools import product

import numpy as np
import pytest
import warnings
Expand All @@ -24,6 +26,40 @@
)
from ecoli.experiments.ecoli_master_sim import EcoliSim, CONFIG_DIR_PATH

TRANSLATION_SUPPLY_FLAGS = [
"mechanistic_translation_supply",
"trna_charging",
"mechanistic_aa_transport",
"aa_supply_in_charging",
"translation_supply",
]
_FLAG_STATE_TUPLES = list(product([False, True], repeat=len(TRANSLATION_SUPPLY_FLAGS)))
TRANSLATION_FLAG_COMBINATIONS = [
dict(zip(TRANSLATION_SUPPLY_FLAGS, combo)) for combo in _FLAG_STATE_TUPLES
]
TRANSLATION_FLAG_IDS = [
",".join(
f"{name}={'on' if state else 'off'}"
for name, state in zip(TRANSLATION_SUPPLY_FLAGS, combo)
)
for combo in _FLAG_STATE_TUPLES
]
del _FLAG_STATE_TUPLES


def run_two_second_simulation(flag_overrides):
"""Run a 2 s EcoliSim with the provided translation flag overrides."""

sim = EcoliSim.from_file()
# Use Parquet emitter for strict type enforcement
sim.config["emitter"] = "parquet"
sim.config["emitter_arg"] = {"out_dir": "out/translation_flag_tests"}
sim.config["max_duration"] = 2
for flag_key, flag_value in flag_overrides.items():
sim.config[flag_key] = flag_value
sim.build_ecoli()
sim.run()


@pytest.mark.slow
def test_division(agent_id="0", max_duration=4):
Expand Down Expand Up @@ -315,12 +351,25 @@ def test_emit_unique():
assert isinstance(val["agents"]["0"]["unique"][unique_mol], list)


@pytest.mark.slow
@pytest.mark.parametrize(
"flag_overrides",
TRANSLATION_FLAG_COMBINATIONS,
ids=TRANSLATION_FLAG_IDS,
)
def test_translation_flag_harness(flag_overrides):
"""Run the 2 s simulation across every translation flag combination."""

run_two_second_simulation(flag_overrides)


test_library = {
"1": test_division,
"2": test_division_topology,
"3": test_ecoli_generate,
"4": test_lattice_lysis,
"5": test_emit_unique,
"6": test_translation_flag_harness,
}

# run experiments in test_library from the command line with:
Expand Down
17 changes: 0 additions & 17 deletions ecoli/library/json_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,23 +74,6 @@ def numpy_molecules(states):
),
"constrained": {"GLC[p]": 20.0 * units.mmol / (units.g * units.h)},
}
if "process_state" in states:
if "polypeptide_elongation" in states["process_state"]:
if (
"aa_exchange_rates"
in states["process_state"]["polypeptide_elongation"]
):
states["process_state"]["polypeptide_elongation"][
"aa_exchange_rates"
] = (
units.mmol
/ units.s
* np.array(
states["process_state"]["polypeptide_elongation"][
"aa_exchange_rates"
]
)
)
return states


Expand Down
7 changes: 2 additions & 5 deletions ecoli/processes/metabolism.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,13 +360,10 @@ def ports_schema(self):
"_divider": "zero",
},
"aa_exchange_rates": {
"_default": CONC_UNITS
/ TIME_UNITS
* np.zeros(len(self.aa_exchange_names)),
"_default": np.zeros(len(self.aa_exchange_names)),
"_emit": True,
"_updater": "set",
"_divider": "set",
"_serializer": "<class 'unum.Unum'>",
},
},
"global_time": {"_default": 0.0},
Expand Down Expand Up @@ -495,7 +492,7 @@ def next_update(self, timestep, states):
aa_in_media[self.removed_aa_uptake] = False
exchange_rates = (
states["polypeptide_elongation"]["aa_exchange_rates"] * timestep
).asNumber(CONC_UNITS / TIME_UNITS)
)
aa_uptake_package = (
exchange_rates[aa_in_media],
self.aa_exchange_names[aa_in_media],
Expand Down
7 changes: 2 additions & 5 deletions ecoli/processes/metabolism_redux.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,13 +356,10 @@ def ports_schema(self):
},
"gtp_to_hydrolyze": {"_default": 0, "_emit": True, "_divider": "zero"},
"aa_exchange_rates": {
"_default": CONC_UNITS
/ TIME_UNITS
* np.zeros(len(self.aa_exchange_names)),
"_default": np.zeros(len(self.aa_exchange_names)),
"_emit": True,
"_updater": "set",
"_divider": "set",
"_serializer": "<class 'unum.Unum'>",
},
},
"listeners": {
Expand Down Expand Up @@ -576,7 +573,7 @@ def next_update(self, timestep, states):
aa_in_media[self.removed_aa_uptake] = False
exchange_rates = (
states["polypeptide_elongation"]["aa_exchange_rates"] * timestep
).asNumber(CONC_UNITS / TIME_UNITS)
)
aa_uptake_package = (
exchange_rates[aa_in_media],
self.aa_exchange_names[aa_in_media],
Expand Down
30 changes: 20 additions & 10 deletions ecoli/processes/polypeptide_elongation.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
)
from ecoli.processes.registries import topology_registry
from ecoli.processes.partition import PartitionedProcess
from ecoli.processes.metabolism import CONC_UNITS, TIME_UNITS


MICROMOLAR_UNITS = units.umol / units.L
Expand Down Expand Up @@ -267,10 +268,6 @@ def __init__(self, parameters=None):
self.seed = self.parameters["seed"]
self.random_state = np.random.RandomState(seed=self.seed)

self.zero_aa_exchange_rates = (
MICROMOLAR_UNITS / units.s * np.zeros(len(self.amino_acids))
)

def ports_schema(self):
return {
"environment": {
Expand Down Expand Up @@ -422,7 +419,7 @@ def ports_schema(self):
"_divider": "zero",
},
"aa_exchange_rates": {
"_default": self.zero_aa_exchange_rates.copy(),
"_default": np.zeros(len(self.amino_acids)),
"_emit": True,
"_updater": "set",
"_divider": "set",
Expand Down Expand Up @@ -755,7 +752,15 @@ def request(
) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64], dict]:
aa_counts_for_translation = self.amino_acid_counts(aasInSequences)

requests = {"bulk": [(self.process.amino_acid_idx, aa_counts_for_translation)]}
# Bulk requests have to be integers (wcEcoli implicitly casts floats to ints)
requests = {
"bulk": [
(
self.process.amino_acid_idx,
aa_counts_for_translation.astype(np.int64),
)
]
}

# Not modeling charging so set fraction charged to 0 for all tRNA
fraction_charged = np.zeros(len(self.process.amino_acid_idx))
Expand Down Expand Up @@ -1033,7 +1038,12 @@ def request(
# Adjust aa_supply higher if amino acid concentrations are low
# Improves stability of charging and mimics amino acid synthesis
# inhibition and export
self.process.aa_supply *= self.aa_supply_scaling(aa_conc, aa_in_media)
# Polypeptide elongation operates using concentration units of CONC_UNITS (uM)
# but aa_supply_scaling uses M units, so convert using unit_conversion (1e-6)
self.process.aa_supply *= self.aa_supply_scaling(
self.charging_params["unit_conversion"] * aa_conc.asNumber(CONC_UNITS),
aa_in_media,
)

aa_counts_for_translation = (
v_rib
Expand Down Expand Up @@ -1159,9 +1169,9 @@ def request(
}
},
"polypeptide_elongation": {
"aa_exchange_rates": self.counts_to_molar
/ units.s
* (import_rates - export_rates)
"aa_exchange_rates": (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to show units in the column name like "polypeptide_elongation__aa_exchange_rates(mmol/s)" or something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Units have long been a pain point for the model. Your solution of adding the units to the listener name makes total sense to me. One thing to be careful of is special characters in the listener name. You will need to enclose the column names for these listeners in an additional set of double quotes in any custom SQL that you write (see #370).

Doing this for all listeners with units is a big lift. Maybe you or someone else can tackle that in a separate PR?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, this PR looks great to me!

self.counts_to_molar / units.s * (import_rates - export_rates)
).asNumber(CONC_UNITS / TIME_UNITS)
},
},
)
Expand Down
4 changes: 1 addition & 3 deletions reconstruction/ecoli/dataclasses/process/metabolism.py
Original file line number Diff line number Diff line change
Expand Up @@ -1004,7 +1004,7 @@ def set_phenomological_supply_constants(self, sim_data: "SimulationDataEcoli"):
)

def aa_supply_scaling(
self, aa_conc: Unum, aa_present: Unum
self, aa_conc: npt.NDArray[np.float64], aa_present: npt.NDArray[np.bool_]
) -> npt.NDArray[np.float64]:
"""
Called during polypeptide_elongation process
Expand All @@ -1021,8 +1021,6 @@ def aa_supply_scaling(
higher supply rate if >1, lower supply rate if <1
"""

aa_conc = aa_conc.asNumber(METABOLITE_CONCENTRATION_UNITS)

aa_supply = self.fraction_supply_rate
aa_import = aa_present * self.fraction_import_rate
aa_synthesis = 1 / (1 + aa_conc / self.KI_aa_synthesis)
Expand Down
Loading