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');