Modeling Closed Loops

Here, we illustrate how to use controlSBML to model a closed loop system for the SBML model. Most of the work is done by the CalTech control library.

For the open loop system, we use the Antimony model below.

1SIMPLE_OLS_MODEL = """
2    J1: S1 -> S2; S1
3    J2: S2 -> S1; S2
4    J3: S2 -> ; k*S2
5    S1 = 10; S2 = 0;
6    k = 0.8
7"""

Open loop system

Suppose we want the response to a unit step in S1 to be that S2 is 3. That is, REF = 3. To analyze well does the open loop system achieve this goal, we use the following script, which produces the plot below.

 1# Desired value for S2
 2REF = 3
 3# Open loop system
 4ctlsb = ctl.ControlSBML(SIMPLE_OLS_MODEL,
 5    input_names=["S1"], output_names=["S2"])
 6simple_ols = ctlsb.makeNonlinearIOSystem("simple_ols")
 7# Plot the impulse response response relative to reference
 8times = [0.1*n for n in range(201)]
 9time_response = control.input_output_response(simple_ols,
10    times, X0=ctl.makeStateVector(simple_ols), U=0)
11plotTimeResponse(time_response, ["S2"], is_legend=False)
12plt.plot([times[0], times[-1]], [REF, REF], linestyle="--")
13plt.legend(["S2", "REF"])
_images/modeling_closed_loops-plot1.png

Controller

To achieve our control objective, we will construct a closed loop system, as depicted in the block diagram below. This system includes the original open loop system, and it adds a controller.

_images/modeling_closed_loops-fig1.png

We will use a proportional-integral (PI) controller. This controller is the two controllers. A proportional controller (P) and an integral controller (I). The equation for the proportional controller is \(u(t) = k_P e(t)\). The equation for the integral controller is \(u(t) = k_I\int_0^t e(u) du\). Thus, the equation for the PI controller is:

\[\begin{split}\dot{ x}(t) &= e(t) \\ u(t) &= k_P e(t) + k_I x(t)\end{split}\]

Modeling the PI controller as a control.NonlinearIOSystem object requires that we construct functions that correspond to the two state equations above. The updfcn function describes the derivative of state; the outfcn function produces the outputs from the current values of state. Since our model does not include a separate element for calculating \(e(t)\), this is done inside the controller. Thus, these functions input \(y(t)\). The full collection of arguments to the functions are: \(t\), \(x(t)\):, and \(y(t)\).

1# Calculate derivative of state
2def updfcn(t, x, y, _):
3    # Accumulate the control error
4    # t: float (time)
5    # x: array-float (state)
6    # y: array-float (output from OLS)
7    # returns: array-float (derivative of state)
8    dx = REF - y[0]
9    return dx
 1# Calculate output value
 2kP = 0.5
 3kI = 0.1
 4def outfcn(t, x, y, _):
 5    # Calculate the output from the input
 6    # t: float (time)
 7    # x: array-float (state)
 8    # e: array-float (inputs)
 9    # returns: array (output)
10    new_err = REF - y[0]
11    output = kI*x[0] + kP*new_err
12    return output

Notice that we introduced a new variable \(x(t)\). This is a state variable. It is used to calculate the integral of the control errors.

Now we can construct the control.NonlinearIOSystem object.

1controller = control.NonlinearIOSystem(
2      updfcn,
3      outfcn,
4      states=1,
5      inputs=['in'],
6      outputs=['out'],
7      name='controller')

In addition to specifying the update and output functions, this constructor also indicates the number of internal states (one, \(x(t)\)), the name of the input to the controller (in) and the name of the output from the controller (out). These names are used later when we connect the controller to the open loop system.

Closed loop system

The next step is to connect the controller with the OLS. Specifying these connections requires that we have names for the inputs to elements and the outputs from elements. For example, the controller generates an output signal named output; its name is controller.output (because we named this element controller in the constructor).

ctl.ControlSBML.makeNonlinearIOSystem creates A NonlinearIOSystem object with predefined names for the inputs and outputs. For example, simple_ols has the input simple_ols.S1 and the output simple_ols.S2.

Connects are specified by a collection of input and output relationships specified by a python list. The first element of the list is the name of the signal source; the second element is a signal destination (an input to a control element). So, to connect the output from simple_ols to the input to the controller, we use the list [S2, controller.in].

The control.interconnect objects are connections of NonlinearIOSystem elements. Below is a constructor for the model we are building. Note that the first argument of the constructor is the list of NonlinearIOSystem objects being interconncted. inplist and outlist are lists of the inputs to and outputs from the assembly of NonlinearIOSystem objects.

 1# Create the closed loop system
 2simple_closed = control.interconnect(
 3[simple_ols, controller],       # systems
 4connections=[
 5    ['simple_ols.S1', 'controller.out'],
 6    ['controller.in',  'simple_ols.S2'],
 7],
 8inplist=["controller.in"],
 9outlist=["simple_ols.S2", "controller.out"],
10)

Evaluation

The final step is to evaluate the performance of the above system. This is done in the same way as what we did for simple_ols using control.input_output_response. One consideration here is that this function requires specifying the initial state for all elements in simple_closed. Even with just two elements, this can be complicated. The function ctrl.makeStateVector handles this problem although there are some subtlties.

 1initial_state = ctl.makeStateVector(simple_closed)
 2times = [0.1*n for n in range(301)]
 3time_response = control.input_output_response(simple_closed,
 4      times, X0=initial_state)
 5plt.plot([times[0], times[-1]], [REF, REF], linestyle="--")
 6stmts=["plt.ylim([-5, 5])"]
 7stmts.append('plt.legend(["REF", "S2", "e(t)"])')
 8plotTimeResponse(time_response,
 9      ["S2", "controller.out"],
10      stmts=stmts, is_legend=False)
_images/modeling_closed_loops-plot2.png