Skip to content

Commit

Permalink
Include parameter checks; control parameters are kP, kI, kF, kD
Browse files Browse the repository at this point in the history
  • Loading branch information
joseph-hellerstein committed Jan 20, 2024
1 parent e7ff66a commit 538f5f3
Show file tree
Hide file tree
Showing 9 changed files with 847 additions and 137 deletions.
683 changes: 683 additions & 0 deletions examples/archive/Analysis-of-Hepatitus-B.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/controlSBML/archive/siso_closed_loop_designer.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
##################################################################
def _calculateClosedLoopTf(sys_tf=None, kp=None, ki=None, kd=None, kf=None, sign=-1):
# Construct the transfer functions
controller_tf = util.makePIDTransferFunction(kp=kp, ki=ki, kd=kd)
controller_tf = util.makePIDTransferFunction(kP=kp, kI=ki, kd=kd)
# Filter
if kf is not None:
is_none = False
Expand Down
22 changes: 11 additions & 11 deletions src/controlSBML/constants.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""Constants for Project."""
import collections
from docstring_expander.kwarg import Kwarg
from docstring_expander.kwarg import Kwarg # type: ignore
import matplotlib.pyplot
import numpy as np
import pandas as pd
import pandas as pd # type: ignore
import os


Expand Down Expand Up @@ -67,7 +67,7 @@ def equals(self, other):
BIOMODELS_ZIP_FILENAME = "biomodels.zip"

# Constants
END_TIME = 5 # Default endtime
END_TIME = 5.0 # Default endtime
EVENT = "event"
INPUT = "input"
IN = "in"
Expand All @@ -82,7 +82,7 @@ def equals(self, other):
POINTS_PER_TIME = 10
SCORE = "score"
SETPOINT = "setpoint"
START_TIME = 0 # Default start time
START_TIME = 0.0 # Default start time
STATE = "state"
STEP_VAL = 1 # Multiplier used for simulation input
TIME = "time"
Expand Down Expand Up @@ -112,13 +112,13 @@ def equals(self, other):
O_YTICKLABELS = "yticklabels"

# Control parameters
CP_KP = "kp"
CP_KI = "ki"
CP_KF = "kf"
CP_KP = "kP"
CP_KI = "kI"
CP_KF = "kF"
CONTROL_PARAMETERS = [CP_KP, CP_KI, CP_KF]
KP_SPEC = "kp_spec"
KI_SPEC = "ki_spec"
KF_SPEC = "kf_spec"
KP_SPEC = "kP_spec"
KI_SPEC = "kI_spec"
KF_SPEC = "kF_spec"
CONTROL_PARAMETER_SPECS = [KP_SPEC, KI_SPEC, KF_SPEC]

# Default values of options
Expand Down Expand Up @@ -191,7 +191,7 @@ def equals(self, other):
ALL_KWARGS.extend(kwargs)

# TimeSeries
MS_IN_SEC = 1000
MS_IN_SEC = 1000.0
SEC_IN_MS = 1.0/MS_IN_SEC
TIMESERIES_INDEX_NAME = "miliseconds"
TIMES = np.linspace(0, 5, 50)
Expand Down
127 changes: 73 additions & 54 deletions src/controlSBML/control_sbml.py

Large diffs are not rendered by default.

42 changes: 23 additions & 19 deletions src/controlSBML/siso_closed_loop_designer.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
import pandas as pd
import seaborn as sns

CP_KP = "kp"
CP_KI = "ki"
CP_KF = "kf"
CONTROL_PARAMETERS = [CP_KP, CP_KI, CP_KF]
MAX_VALUE = 1e3 # Maximum value for a parameter
MIN_VALUE = 0 # Minimum value for a paramete
DEFAULT_INITIAL_VALUE = 1 # Default initial value for a parameter
Expand All @@ -29,7 +33,7 @@
ABOVE_MAX_MULTIPLIER = 1e-3
LOWPASS_POLE = 1e4 # Pole for low pass filter
# Column names
PARAMETER_DISPLAY_DCT = {cn.CP_KP: r'$k_p$', cn.CP_KI: r'$k_i$', cn.CP_KF: r'$k_f$'}
PARAMETER_DISPLAY_DCT = {CP_KP: r'$k_p$', CP_KI: r'$k_i$', CP_KF: r'$k_f$'}

Workunit = collections.namedtuple("Workunit",
"system input_name output_name setpoint times is_greedy num_restart is_report")
Expand All @@ -39,7 +43,7 @@ def _calculateClosedLoopTransferFunction(open_loop_transfer_function=None, kp=No
# Construct the transfer functions
if open_loop_transfer_function is None:
return None
controller_tf = util.makePIDTransferFunction(kp=kp, ki=ki, kd=kd)
controller_tf = util.makePIDTransferFunction(kP=kp, kI=ki, kD=kd)
# Filter
if kf is not None:
filter_tf = control.TransferFunction([kf], [1, kf])
Expand Down Expand Up @@ -128,7 +132,7 @@ def set(self, kp=None, ki=None, kf=None):
kd (float)
kf (float)
"""
value_dct = {cn.CP_KP: kp, "ki": ki, "kf": kf}
value_dct = {CP_KP: kp, CP_KI: ki, CP_KF: kf}
for name, value in value_dct.items():
if value is None:
continue
Expand All @@ -143,7 +147,7 @@ def get(self):
dict: {name: value}
"""
dct = {}
for name in cn.CONTROL_PARAMETERS:
for name in CONTROL_PARAMETERS:
if self.__getattribute__(name) is not None:
value = self.__getattribute__(name)
dct[name] = value
Expand Down Expand Up @@ -186,7 +190,7 @@ def makePlot(parameter_name1, parameter_name2, ax, vmin=None, vmax=None):
# Find the parameters in the design result
parameter_names = []
idx = list(self.design_result_df.index)[0]
for name in cn.CONTROL_PARAMETERS:
for name in CONTROL_PARAMETERS:
if not name in self.design_result_df.columns:
continue
if not np.isnan(self.design_result_df.loc[idx, name]):
Expand Down Expand Up @@ -268,14 +272,14 @@ def addAxis(grid, parameter_name, parameter_spec):
msgs.warn(msg)
# Initializations
grid = Grid(min_value=min_value, max_value=max_value, num_coordinate=num_coordinate)
addAxis(grid, cn.CP_KP, kp_spec)
addAxis(grid, cn.CP_KI, ki_spec)
addAxis(grid, cn.CP_KF, kf_spec)
addAxis(grid, CP_KP, kp_spec)
addAxis(grid, CP_KI, ki_spec)
addAxis(grid, CP_KF, kf_spec)
#
return self.designAlongGrid(grid, is_greedy=is_greedy, num_restart=num_restart, is_report=is_report,
num_process=num_process)

def simulateTransferFunction(self, transfer_function=None, period=None):
def oldsimulateTransferFunction(self, transfer_function=None, period=None):
"""
Simulates the closed loop transfer function based on the parameters of the object.
Expand Down Expand Up @@ -312,7 +316,7 @@ def designAlongGrid(self, grid:Grid, is_greedy:bool=False, num_restart:int=1,
"""
point_evaluator = PointEvaluator(self.system.copy(), self.input_name, self.output_name,
self.setpoint, self.times, is_greedy=is_greedy)
parallel_search = ParallelSearch(point_evaluator, grid.points, num_process=num_process, is_report=is_report)
parallel_search = ParallelSearch(point_evaluator, grid.points, num_process=num_process, is_report=is_report) # type: ignore
search_results = []
for _ in range(num_restart):
parallel_search.search()
Expand All @@ -328,12 +332,12 @@ def designAlongGrid(self, grid:Grid, is_greedy:bool=False, num_restart:int=1,
search_result_df = search_result_df.reset_index()
# Record the result
self.residual_mse = search_result_df.loc[0, cn.SCORE]
if cn.CP_KP in search_result_df.columns:
self.kp = search_result_df.loc[0, cn.CP_KP]
if cn.CP_KI in search_result_df.columns:
self.ki = search_result_df.loc[0, cn.CP_KI]
if cn.CP_KF in search_result_df.columns:
self.kf = search_result_df.loc[0, cn.CP_KF]
if CP_KP in search_result_df.columns:
self.kp = search_result_df.loc[0, CP_KP]
if CP_KI in search_result_df.columns:
self.ki = search_result_df.loc[0, CP_KI]
if CP_KF in search_result_df.columns:
self.kf = search_result_df.loc[0, CP_KF]
# Save the results
if self.save_path is not None:
search_result_df.to_csv(self.save_path, index=False)
Expand All @@ -352,7 +356,7 @@ def design_result_df(self):
return None
return self._design_result_df

def simulateTransferFunction(self, transfer_function=None, period=None):
def simulateTransferFunction(self, transfer_function=None, period=None)->tuple[np.array, np.array]:
"""
Simulates the closed loop transfer function based on the parameters of the object.
Expand Down Expand Up @@ -411,9 +415,9 @@ def evaluate(self, is_plot=True, **kwargs):
Timeseries (from simulation)
AntimonyBuilder (from simulation)
"""
param_dct = {n: None for n in cn.CONTROL_PARAMETERS}
param_dct = {n: None for n in CONTROL_PARAMETERS}
param_dct.update(self.get())
k_dct = {k: param_dct[k] for k in cn.CONTROL_PARAMETERS}
k_dct = {k: param_dct[k] for k in CONTROL_PARAMETERS}
try:
simulated_ts, antimony_builder = self.system.simulateSISOClosedLoop(setpoint=self.setpoint,
start_time=self.start_time, end_time=self.end_time, num_point=self.num_point,
Expand Down
57 changes: 30 additions & 27 deletions src/controlSBML/util.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import controlSBML.constants as cn
import controlSBML as ctl
import control
import control # type: ignore
from controlSBML.option_management.option_manager import OptionManager
from controlSBML import msgs

from docstring_expander.expander import Expander
from docstring_expander.expander import Expander # type: ignore
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
import pandas as pd # type: ignore
import seaborn as sns # type: ignore
import urllib.request
from typing import Optional, List


REPO_URL = "https://github.com/ModelEngineering/controlSBML/raw/main"
Expand Down Expand Up @@ -174,8 +175,8 @@ def plotManyTS(*tss, ncol=1, names=None, **kwargs):
mgr.doFigOpts()
return PlotResult(ax=axes, fig=fig)

def makeSimulationTimes(start_time=cn.START_TIME, end_time=cn.END_TIME,
points_per_time=cn.POINTS_PER_TIME):
def makeSimulationTimes(start_time:Optional[float]=cn.START_TIME, end_time:Optional[float]=cn.END_TIME,
points_per_time:Optional[int]=cn.POINTS_PER_TIME)->np.ndarray:
"""
Constructs the times for a simulation using the simulation options.
Expand All @@ -187,24 +188,26 @@ def makeSimulationTimes(start_time=cn.START_TIME, end_time=cn.END_TIME,
Returns
-------
np.ndarray
np.ndarray-int
"""
dt = 1.0/points_per_time
dt_ms = int(cn.MS_IN_SEC*dt)
start_ms = int(start_time*cn.MS_IN_SEC)
end_ms = int(end_time*cn.MS_IN_SEC)
dt = 1.0 / float(points_per_time) # type: ignore
dt_ms = int(cn.MS_IN_SEC * dt)
start_ms = int(start_time*float(cn.MS_IN_SEC)) # type: ignore
end_ms = int(end_time*cn.MS_IN_SEC) # type: ignore
times = np.arange(start_ms, end_ms + dt_ms, dt_ms)
times = times/cn.MS_IN_SEC
times = [int(t/cn.MS_IN_SEC) for t in times] # type: ignore
return np.array(times)

def makePIDTransferFunction(kp=None, ki=None, kd=None, name="controller", input_name=cn.IN, output_name=cn.OUT):
def makePIDTransferFunction(kP:Optional[float]=None, kI:Optional[float]=None,
kD:Optional[float]=None, name:Optional[str]="controller",
input_name:Optional[str]=cn.IN, output_name:Optional[str]=cn.OUT):
"""
Constructs a PID transfer function.
Args:
kp: float
ki: float
kd: float
kP: float
kI: float
kD: float
Raises:
ValueError: must provide at least one of kp, ki, kd
Expand All @@ -214,23 +217,23 @@ def makePIDTransferFunction(kp=None, ki=None, kd=None, name="controller", input_
"""
is_none = True
controller_tf = control.tf([0], [1], name=name, inputs=input_name, outputs=output_name)
if kp is not None:
if kP is not None:
is_none = False
controller_tf += control.tf([kp], [1])
if ki is not None:
controller_tf += control.tf([kP], [1])
if kI is not None:
is_none = False
controller_tf += control.tf([ki], [1, 0])
if kd is not None:
controller_tf += control.tf([kI], [1, 0])
if kD is not None:
is_none = False
controller_tf += control.tf([kd, 0], [1])
controller_tf += control.tf([kD, 0], [1])
if is_none:
raise ValueError("At least one of kp, ki, kd, kf must be defined")
controller_tf.name = name
controller_tf.input_labels = [input_name]
controller_tf.output_labels = [output_name]
return controller_tf

def mat2DF(mat, column_names=None, row_names=None):
def mat2DF(mat:np.array, column_names:Optional[List[str]]=None, row_names:Optional[List[str]]=None):
"""
Converts a numpy ndarray or array-like to a DataFrame.
Expand All @@ -247,21 +250,21 @@ def mat2DF(mat, column_names=None, row_names=None):
mat = np.reshape(mat, (len(mat), 1))
if column_names is None:
if hasattr(mat, "colnames"):
column_names = mat.colnames
column_names = mat.colnames # type: ignore
if column_names is not None:
if len(column_names) == 0:
column_names = None
if row_names is None:
if hasattr(mat, "rownames"):
if len(mat.rownames) > 0:
row_names = mat.rownames
if len(mat.rownames) > 0: # type: ignore
row_names = mat.rownames # type: ignore
if row_names is not None:
if len(row_names) == 0:
row_names = None
df = pd.DataFrame(mat, columns=column_names, index=row_names)
return df

def ppMat(mat, column_names=None, row_names=None, is_print=True):
def ppMat(mat, column_names:Optional[List[str]]=None, row_names:Optional[List[str]]=None, is_print:Optional[bool]=True):
"""
Provides a pretty print for a matrix or DataFrame)
Expand Down
Loading

0 comments on commit 538f5f3

Please sign in to comment.