Example: Composite Hybrid System with Switched Subsystem
We create a composite system that consists of a plant, two controllers, and a switch that toggles between the controllers based on some criteria.
A = [0, 1; 0, 0];
B = [0; 1];
K0 = [-1, -1];
K1 = [ 2, -1];
input_dim = 1;
plant = hybrid.subsystems.LinearContinuousSubsystem(A, B);
controller_0 = hybrid.subsystems.MemorylessSubsystem(2, 1, @(~, z_plant) K0*z_plant);
controller_1 = hybrid.subsystems.MemorylessSubsystem(2, 1, @(~, z_plant) K1*z_plant);
switcher = hybrid.subsystems.SwitchSubsystem(input_dim);
sys = CompositeHybridSystem('plant', plant, ...
'kappa0', controller_0, ...
'kappa1', controller_1, ...
'switcher', switcher)
sys = CompositeHybridSystem: ├ Subsystem 1: 'plant' (hybrid.subsystems.LinearContinuousSubsystem) │ Flow input: @(~,~,~,~,~)zeros(1,1) │ Jump input: @(~,~,~,~,~)zeros(1,1) │ Output: y1=@(x)C*x │ Dimensions: State=2, Input=1, Output=2 ├ Subsystem 2: 'kappa0' (hybrid.subsystems.MemorylessSubsystem) │ Flow input: @(~,~,~,~,~)zeros(2,1) │ Jump input: @(~,~,~,~,~)zeros(2,1) │ Output: y2=@(~,z_plant)K0*z_plant │ Dimensions: State=0, Input=2, Output=1 ├ Subsystem 3: 'kappa1' (hybrid.subsystems.MemorylessSubsystem) │ Flow input: @(~,~,~,~,~)zeros(2,1) │ Jump input: @(~,~,~,~,~)zeros(2,1) │ Output: y3=@(~,z_plant)K1*z_plant │ Dimensions: State=0, Input=2, Output=1 └ Subsystem 4: 'switcher' (hybrid.subsystems.SwitchSubsystem) Flow input: @(~,~,~,~,~)zeros(4,1) Jump input: @(~,~,~,~,~)zeros(4,1) Output: y4=@(q,u)output_switch(q,u) Dimensions: State=1, Input=4, Output=1
The full state vector is passed as input to the controllers. We can ommit the '~'s from the argument list since we only use the first argument is used.
sys.setInput(controller_0, @(z_plant, ~, ~, ~) z_plant); % '~'s included sys.setInput(controller_1, @(z_plant) z_plant); % '~'s omitted
The current choice of controller is stored as state variable q in switcher , where controller_0 is passed through as the output of switcher whenever q = 0 and controller_1 is passed through when q = 1 . The plant state z_plant and output of the controllers, named u0 and u1 , are passed to the switcher. The SwitchSubsystem class provides a wrapInput method that handles the creation of the input vector from the given values. The third argument to wrapInput is the criteria for switching to to q = 0 and the fourth argument is the criteria for switching to q = 1 . If # q=0 and the third argument of wrapInput is zero, # q=1 and fourth argument of wrapInput is zero, or # the third and fourth arguments of wrapInput are zero, then q is held constant.
sys.setInput(switcher, @(z_plant, u0, u1) ...
switcher.wrapInput(u0, u1, norm(z_plant) >= 3, norm(z_plant) <= 1));
The output of the switch is passed to the plant.
sys.setInput(plant, @(~, ~, ~, u_switched) u_switched);
Compute a solution. Note that the MemorylessSubsystems have no state, so empty arrays are given in x0 for the corresponding subsystems.
x0 = {[10; 0], [], [], 1};
config = HybridSolverConfig('Refine', 8); % 'Refine' option makes the plots smoother.
sol = sys.solve(x0, [0, 100], [0, 100], config)
sol =
CompositeHybridSolution with properties:
subsys_count: 4
x0: [7×1 double]
xf: [7×1 double]
termination_cause: T_REACHED_END_OF_TSPAN
t: [2007×1 double]
j: [2007×1 double]
x: [2007×7 double]
flow_lengths: [39×1 double]
jump_times: [38×1 double]
shortest_flow_length: 0
total_flow_length: 100
jump_count: 38
Plot the solution, using different colors when \(q=0\) and \(q=1\) .
clf
hold on
q = sol('switcher').x;
hpb = HybridPlotBuilder().jumpMarker('none');
% Plot solution where q=0
hpb.filter(q == 0)...
.legend('$q = 0$')...
.plotPhase(sol('plant'));
% Plot solution where q=1
hpb.filter(q == 1)...
.legend('$q = 1$')...
.flowColor('green')...
.plotPhase(sol('plant'));
% Configure plot appearance
axis equal
axis padded
set(gca(), 'XAxisLocation', 'origin');
set(gca(), 'YAxisLocation', 'origin');