Skip to content

Commit

Permalink
Refactor ControlSBML options
Browse files Browse the repository at this point in the history
  • Loading branch information
joseph-hellerstein committed Mar 18, 2024
1 parent 5d4c880 commit 0b5aad9
Show file tree
Hide file tree
Showing 8 changed files with 381 additions and 228 deletions.
77 changes: 57 additions & 20 deletions examples/cancer-vitamins.ipynb

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions src/controlSBML/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,15 @@ def equals(self, other):
# Keyword options
O_AX = "ax"
O_AX2 = "ax2"
O_SELECTIONS = "selections" # Selections for the plot
O_END_TIME = "end_time"
O_FIGURE = "figure"
O_FIGSIZE = "figsize"
O_FINAL_VALUE = "final_value"
O_FITTER_METHOD = "fitter_method"
O_KP_SPEC = "kP_spec"
O_KI_SPEC = "kI_spec"
O_KF_SPEC = "kF_spec"
O_STEP_VAL = "step_val"
O_INITIAL_VALUE = "initial_value"
O_INPUT_NAME = "input_name"
Expand All @@ -109,6 +114,8 @@ def equals(self, other):
O_LEGEND_SPEC = "legend_spec"
O_MARKERS = "markers"
O_NUM_POINT = "num_point"
O_NUM_PROCESS = "num_process"
O_NUM_RESTART = "num_restart"
O_NUM_STEP = "num_step"
O_OUTPUT_NAME = "output_name"
O_OUTPUT_NAMES = "output_names"
Expand Down
441 changes: 259 additions & 182 deletions src/controlSBML/control_sbml.py

Large diffs are not rendered by default.

34 changes: 24 additions & 10 deletions src/controlSBML/sbml_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import pandas as pd # type: ignore
import numpy as np # type: ignore
import tellurium as te # type: ignore
from typing import Optional, List


class SBMLSystem(object):
Expand Down Expand Up @@ -320,7 +321,10 @@ def simulate(self, start_time=cn.START_TIME, end_time=cn.END_TIME, num_point=Non
self.setSteadyState()
return self._simulate(start_time, end_time, num_point, is_steady_state, is_reload=False)

def _simulate(self, start_time, end_time, num_point, antimony_builder=None, is_steady_state=False, is_reload=False):
def _simulate(self, start_time:float, end_time:float, num_point:int,
antimony_builder:Optional[AntimonyBuilder]=None,
selections:Optional[list]=None,
is_steady_state:Optional[bool]=False, is_reload:Optional[bool]=False):
"""
Simulates the system the roadrunner object.
Expand All @@ -330,17 +334,20 @@ def _simulate(self, start_time, end_time, num_point, antimony_builder=None, is_s
end_time: float
num_point: int
antimoney_builder: AntimonyBuilder
selections: list-str (names of species to be selected)
is_steady_state: bool (start the simulation at steady state)
Returns
-------
DataFrame
"""
if antimony_builder is None:
antimony_builder = self.antimony_builder
antimony = str(antimony_builder)
if antimony[-1] == "\n":
antimony = antimony[:-1]
# Set defaults
selections = [] if selections is None else selections
antimony_builder = self.antimony_builder if antimony_builder is None else antimony_builder
# Initializations
antimony_str = str(antimony_builder)
if antimony_str[-1] == "\n":
antimony_str = antimony_str[:-1]
if is_reload:
try:
self._roadrunner = te.loada(str(antimony_builder))
Expand All @@ -350,7 +357,7 @@ def _simulate(self, start_time, end_time, num_point, antimony_builder=None, is_s
self.roadrunner.reset()
if is_steady_state:
self.setSteadyState()
selections = list(self.input_names)
selections.extend(self.input_names)
selections.extend(self.output_names)
selections.insert(0, cn.TIME)
data = self.roadrunner.simulate(float(start_time), float(end_time), num_point, selections=selections)
Expand All @@ -370,6 +377,7 @@ def isInitialized(self)->bool:
def simulateSISOClosedLoop(self, input_name=None, output_name=None, kP=None, kI=None, kF=None, setpoint=1,
start_time=cn.START_TIME, end_time=cn.END_TIME, times=None, num_point=None,
is_steady_state=False, inplace=False, initial_input_value=None,
selections:Optional[list[str]]=None,
sign=-1):
"""
Simulates a closed loop system.
Expand All @@ -387,6 +395,7 @@ def simulateSISOClosedLoop(self, input_name=None, output_name=None, kP=None, kI=
num_point: int (overridden by times)
inplace: bool (update the existing model with the closed loop statements)
initial_input_value: float (initial value of the input)
selections: list-str (names of species to be selected)
sign: float (sign of the feedback)
Returns:
Timeseries
Expand Down Expand Up @@ -418,11 +427,13 @@ def simulateSISOClosedLoop(self, input_name=None, output_name=None, kP=None, kI=
initial_output_value=initial_input_value, sign=sign)
# Run the simulation
result = self._simulate(start_time, end_time, num_point, is_steady_state=is_steady_state,
antimony_builder=builder, is_reload=True), builder
antimony_builder=builder, is_reload=True, selections=selections), builder
return result

def simulateStaircase(self, input_name, output_name, times=cn.TIMES, initial_value=cn.DEFAULT_INITIAL_VALUE,
num_step=cn.DEFAULT_NUM_STEP, final_value=cn.DEFAULT_FINAL_VALUE, is_steady_state=True, inplace=True):
num_step=cn.DEFAULT_NUM_STEP, final_value=cn.DEFAULT_FINAL_VALUE,
selections:Optional[List[str]]=None,
is_steady_state=True, inplace=True):
"""
Adds events for the staircase.
Args:
Expand All @@ -432,6 +443,8 @@ def simulateStaircase(self, input_name, output_name, times=cn.TIMES, initial_val
final_value: float (value for final step)
num_step: int (number of steps in staircase)
num_point_in_step: int (number of points in each step)
selections: list-str (names of species to be selected)
is_steady_state: bool (start the simulation at steady state)
inplace: bool (update the existing model with the Staircase statements)
Returns:
Timeseries
Expand All @@ -446,7 +459,8 @@ def simulateStaircase(self, input_name, output_name, times=cn.TIMES, initial_val
builder.makeStaircase(input_name, times=times, initial_value=initial_value,
num_step=num_step, final_value=final_value)
ts = self._simulate(start_time=times[0], antimony_builder=builder, end_time=times[-1], num_point=len(times),
is_steady_state=is_steady_state, is_reload=True)
is_steady_state=is_steady_state, is_reload=True,
selections=selections)
return ts, builder

@staticmethod
Expand Down
2 changes: 1 addition & 1 deletion src/controlSBML/siso_transfer_function_fitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def plot(self, **kwargs):
self._setYAxColor(ax, "left", cn.SIMULATED_COLOR)
self._setYAxColor(ax2, "right", cn.INPUT_COLOR)
ax.set_title(title)
ax.set_title(latex, y=0.2, x=0.5, pad=-14, fontsize=14, loc="right")
ax.set_title(latex, y=0.3, x=0.8, fontsize=14, loc="right")
ax.legend([self.output_name, cn.O_PREDICTED], loc="upper left")
mgr.doPlotOpts()
mgr.doFigOpts()
Expand Down
5 changes: 4 additions & 1 deletion src/controlSBML/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -623,20 +623,23 @@ def roundToDigits(number:float, num_digits:int=2):
required_decimal = -int(log_number) + num_digits
return np.round(number, required_decimal)

def subsetDct(dct, keys):
def subsetDct(dct:dict, keys:List[str], default:Optional[dict]=None)->dict:
"""
Returns a subset of a dictionary.
Args:
dct: dict
keys: list-str
default: dict (default values of subsetted keys)
Returns:
dict
"""
new_dct = {}
for key in keys:
if key in dct.keys():
new_dct[key] = dct[key]
elif (default is not None) and (key in default.keys()):
new_dct[key] = default[key]
return new_dct

def differenceDct(dct1, dct2):
Expand Down
34 changes: 20 additions & 14 deletions tests/test_control_sbml.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
import unittest


IGNORE_TEST = True
IGNORE_TEST = False
IS_PLOT = False
TIMES = np.linspace(0, 1000, 10000)
TIMES = cn.TIMES
FIGSIZE = (5, 5)
SAVE1_PATH = os.path.join(cn.TEST_DIR, "control_sbml_save_path.csv")
LINEAR_MDL = """
Expand Down Expand Up @@ -91,14 +91,17 @@ def testSetSystem(self):
def testPlotModel(self):
if IGNORE_TEST:
return
ts = self.ctlsb.plotModel(is_plot=IS_PLOT, figsize=FIGSIZE)
self.ctlsb = CTLSB.copy()
ts = self.ctlsb.plotModel(is_plot=IS_PLOT, figsize=FIGSIZE, selections=["S1", "S2", "S3"])
ts = self.ctlsb.plotModel(is_plot=IS_PLOT, figsize=FIGSIZE, selections=["S2"])
ts = self.ctlsb.plotModel(is_plot=IS_PLOT, figsize=FIGSIZE, times=np.linspace(0, 100, 1000))
ts = self.ctlsb.plotModel(is_plot=IS_PLOT, figsize=FIGSIZE, markers=False)
self.assertTrue(isinstance(ts, Timeseries))

def testPlotStaircaseResponse(self):
if IGNORE_TEST:
return
self.ctlsb = CTLSB.copy()
self.ctlsb.setSystem(input_name="S1", output_name="S3")
ts, builder = self.ctlsb.plotStaircaseResponse(is_plot=IS_PLOT, figsize=FIGSIZE,
times=np.linspace(0, 100, 1000))
Expand All @@ -118,22 +121,24 @@ def testPlotTransferFunctionFit(self):
def testPlotSISOClosedLoop(self):
if IGNORE_TEST:
return
self.ctlsb.setSystem(input_name="S1", output_name="S3")
ts, builder = self.ctlsb._plotClosedLoop(setpoint=3, is_plot=IS_PLOT, kP=1, figsize=FIGSIZE,
ctlsb = CTLSB.copy()
ctlsb.setSystem(input_name="S1", output_name="S3")
ts, builder = ctlsb._plotClosedLoop(setpoint=3, is_plot=IS_PLOT, kP=1, figsize=FIGSIZE,
times=np.linspace(0, 100, 1000))
self.assertTrue(isinstance(ts, Timeseries))
self.assertTrue(isinstance(builder, AntimonyBuilder))

def testPlotDesign(self):
if IGNORE_TEST:
return
ctlsb = CTLSB.copy()
setpoint = 5
self.ctlsb.setSystem(input_name="S1", output_name="S3")
ts, builder = self.ctlsb.plotDesign(setpoint=setpoint, kP_spec=True, kI_spec=True, figsize=FIGSIZE, is_plot=IS_PLOT,
ctlsb.setSystem(input_name="S1", output_name="S3")
ts, builder = ctlsb.plotDesign(setpoint=setpoint, kP_spec=True, kI_spec=True, figsize=FIGSIZE, is_plot=IS_PLOT,
min_parameter_value=0.001, max_parameter_value=10, num_restart=2,
num_coordinate=5, num_process=1)
num_coordinate=5, num_process=10)
# Show that kP, kI are now the defaults
_ = self.ctlsb._plotClosedLoop(setpoint=setpoint, is_plot=IS_PLOT, kP=1, figsize=FIGSIZE,
_ = ctlsb._plotClosedLoop(setpoint=setpoint, is_plot=IS_PLOT, kP=1, figsize=FIGSIZE,
times=np.linspace(0, 100, 1000))
self.assertTrue(isinstance(ts, Timeseries))
self.assertTrue(isinstance(builder, AntimonyBuilder))
Expand Down Expand Up @@ -183,11 +188,11 @@ def testGetters(self):
self.assertTrue(isinstance(self.ctlsb._transfer_function_builder, SISOTransferFunctionBuilder))
self.assertTrue(isinstance(self.ctlsb.getAntimony(), str))
self.assertTrue(isinstance(self.ctlsb.getClosedLoopTransferFunction(), control.TransferFunction))
self.assertTrue(isinstance(self.ctlsb._getOptions(), dict))
self.assertTrue(isinstance(self.ctlsb.getOptions(), dict))

def testFullAPI(self):
#if IGNORE_TEST:
# return
if IGNORE_TEST:
return
INPUT_NAME = "pIRS"
OUTPUT_NAME = "pmTORC1"
INPUT_NAME = "pIRS"
Expand Down Expand Up @@ -346,9 +351,10 @@ def testBug6(self):
end
"""
try:
ctlsb = ControlSBML(model, input_name="S0", output_name="S4")
_ = ControlSBML(model, input_name="S0", output_name="S4")
self.assertTrue(True)
except:
except Exception as e:
print(e)
self.assertTrue(False)

def testBug7(self):
Expand Down
9 changes: 9 additions & 0 deletions tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ def testSubsetDct(self):
self.assertTrue(len(subset) == 2)
self.assertTrue("b" not in subset)

def testSubsetDct2(self):
if IGNORE_TEST:
return
dct = {"a": 1, "b": 2}
subset = util.subsetDct(dct, ["a", "c"], {"c": 3})
self.assertTrue(len(subset) == 2)
self.assertTrue("b" not in subset)
self.assertTrue(subset["c"] == 3)

def testDifferenceDct(self):
if IGNORE_TEST:
return
Expand Down

0 comments on commit 0b5aad9

Please sign in to comment.