How can I automatically design a controller for a model in SystemModeler?
Mathematica 9 introduced the function [PIDTune which make it super easy to automatically design controllers.
In this example the motor depicted below (available in a standard installation of SystemModeler as “MathematicaExamples.Modeling.invertedPendulum.Components.DCMotor” ) has an input V which governs the voltage in the circuit that turns the motor. We want to regulate the speed.
There are multiple ways to do this. One way is to use the function [WSMLinearize to create a state space model that PIDTune can create a controller for.
motorStateSpate =
WSMLinearize[
"MathematicaExamples.Modeling.invertedPendulum.Components.DCMotor"];
controller = PIDTune[motorStateSpate, "PIFD", "PIDData"]
We can verify the result by looking at the response from feeding the system a step function.
or = OutputResponse[controller["ReferenceOutput"],
UnitStep[t], {t, 0, 2}];
Plot[or, {t, 0, 2}, PlotRange -> All]
In order to use the calculated parameters, we need to add a controller to our model.
The computed parameters can be found by querying the [PIDData created by PIDTune.
{pars, parVals} = {{"PID.k", "PID.Ti", "PID.Td"},controller["FeedbackIdealParameters"]}
Finally, by simulating with these parameters we can directly compare to the system Mathematica calculated.
simPID = WSMSimulate["ExampleModel", {0, 2},
WSMParameterValues -> Thread[pars -> parVals]];
Plot[{simPID[{"DCMotor1.w"}, t], or}, {t, 0, 2},
PlotStyle -> {Orange, {Dotted, Thick}}, PlotRange -> All]
PS here’s the simple Modelica code for the ExampleModel above:
model ExampleModel
extends Modelica.Icons.Example;
MathematicaExamples.Modeling.invertedPendulum.Components.DCMotor DCMotor1 annotation(Placement(visible=true, transformation(origin={0.0,0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
Modelica.Blocks.Continuous.LimPID PID(yMax=1500, yMin=-1500, wd=1) annotation(Placement(visible=true, transformation(origin={-40.0,0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
Modelica.Blocks.Sources.Step step1(startTime=0) annotation(Placement(visible=true, transformation(origin={-80.0,0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
equation
connect(step1.y,PID.u_s) annotation(Line(visible=true, origin={-60.5,0.0}, points={{-8.5,0.0},{8.5,0.0}}, color={0,0,127}));
connect(PID.y,DCMotor1.V) annotation(Line(visible=true, origin={-20.5,0.0}, points={{-8.5,0.0},{8.5,0.0}}, color={0,0,127}));
connect(DCMotor1.w,PID.u_m) annotation(Line(visible=true, origin={-9.5111,-26.7479}, points={{20.5111,19.7479},{29.5111,19.7479},{29.5111,-13.2521},{-30.4889,-13.2521},{-30.4889,6.5042},{-30.4889,14.7479}}, color={0,0,127}));
end ExampleModel;
](PIDData—Wolfram Documentation)](WSMLinearize—Wolfram Documentation)](PIDTune—Wolfram Documentation)
Sometimes it might not be possible to design a controller with PIDTune (unstable model, etc).
In the following model we instead design a controller with [TransferFunctionModel and dynamically adjust it to a suitable output response with Manipulate.
The plant I want to control is an inverted pendulum driven by a simple motor device. I read the angle values and feed them back to a PID Controller.
linearizedModel = WSMLinearize["SimplePendulum.Components.Plant"];
tf = TransferFunctionModel@linearizedModel;
pidTf = TransferFunctionModel[
kp (1 + 1/(\[Tau]i \[FormalS]) + \[Tau]d \[FormalS]), \[FormalS]];
cltf = SystemsModelFeedbackConnect[
SystemsModelSeriesConnect[pidTf, tf]];
Manipulate[
Plot[{OutputResponse[
cltf /. {kp -> gain, \[Tau]i -> iTime, \[Tau]d -> dTime}, \[Pi]/
2 UnitStep[t], {t, 20}], \[Pi]/2} // Evaluate, {t, 0, 2},
PlotStyle -> Thick, GridLines -> Automatic, PlotRange -> {-1, 2}],
{{gain, 108, "\!\(\*SubscriptBox[\(k\), \(p\)]\)"}, 10,
400}, {{dTime, 0.06, "\!\(\*SubscriptBox[\(\[Tau]\), \(d\)]\)"},
0.01, 10}, {{iTime, .85, "\!\(\*SubscriptBox[\(\[Tau]\), \(i\)]\)"},
0.01, 10}, SaveDefinitions -> True]
Which creates this manipulate:
Once I’ve found appropriate values in the Manipulate I simulate the model with [WSMSimulate and can visualize the results with a plot:
{pars, parVals} = {{"PID.k", "PID.Ti", "PID.Td"}, {108, 0.85, 0.06}};
sim = WSMSimulate["SimplePendulum.SimpleExample", {0, 10},
WSMParameterValues ->
Join[Thread[pars -> parVals], {"PID.yMax" -> 100, "PID.wd" -> 1}]];
Plot[{Evaluate@sim[{"plant1.y"}, t][[1]], \[Pi]/2}, {t, 0, 2},
GridLines -> Automatic, PlotRange -> {-1, 2}]
And just to confirm we can see the 3D-animation of the system as well:
The Modelica models used:
package SimplePendulum
extends Modelica.Icons.Library;
package Components
extends Modelica.Icons.Library;
model Plant
annotation(Icon(coordinateSystem(extent={{-100.0,-100.0},{100.0,100.0}}, preserveAspectRatio=true, initialScale=0.1, grid={10,10}), graphics={Rectangle(visible=true, fillColor={255,255,255}, extent={{-100.0,-100.0},{100.0,100.0}})}), Diagram(coordinateSystem(extent={{-148.5,-45.0},{148.5,105.0}}, preserveAspectRatio=true, initialScale=0.1, grid={5,5}), graphics));
Modelica.Blocks.Interfaces.RealInput u annotation(Placement(visible=true, transformation(origin={-120.0,80.0}, extent={{-20.0,-20.0},{20.0,20.0}}, rotation=0), iconTransformation(origin={-120.0,-0.0}, extent={{-20.0,-20.0},{20.0,20.0}}, rotation=0)));
Modelica.Blocks.Interfaces.RealOutput y annotation(Placement(visible=true, transformation(origin={140.0,-0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0), iconTransformation(origin={110.0,-0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
Modelica.Mechanics.Rotational.Sources.Torque torque1 annotation(Placement(visible=true, transformation(origin={-65.0,80.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
Modelica.Mechanics.Rotational.Components.Inertia inertia1(J=0.01) annotation(Placement(visible=true, transformation(origin={-40.0,80.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
inner Modelica.Mechanics.MultiBody.World world annotation(Placement(visible=true, transformation(origin={-120.0,0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
Modelica.Mechanics.MultiBody.Parts.BodyBox bodyBox1(r={0.5,0,0}) annotation(Placement(visible=true, transformation(origin={30.0,-0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
Modelica.Mechanics.MultiBody.Joints.Revolute revolute1(useAxisFlange=true) annotation(Placement(visible=true, transformation(origin={-20.0,0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
Modelica.Mechanics.Rotational.Sensors.AngleSensor angleSensor1 annotation(Placement(visible=true, transformation(origin={40.0,80.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
Modelica.Mechanics.Rotational.Components.Damper damper1(d=0.1) annotation(Placement(visible=true, transformation(origin={-40.0,40.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
equation
connect(angleSensor1.phi,y) annotation(Line(visible=true, origin={111.1375,40.0}, points={{-60.1375,40.0},{15.6375,40.0},{15.6375,-40.0},{28.8625,-40.0}}, color={0,0,127}));
connect(u,torque1.tau) annotation(Line(visible=true, origin={-98.5,80.0}, points={{-21.5,0.0},{21.5,0.0}}, color={0,0,127}));
connect(revolute1.axis,angleSensor1.flange) annotation(Line(visible=true, origin={-3.3333,63.3333}, points={{-16.6667,-53.3333},{-16.6667,16.6667},{33.3333,16.6667}}));
connect(torque1.flange,inertia1.flange_a) annotation(Line(visible=true, origin={-52.5,80.0}, points={{-2.5,-0.0},{2.5,0.0}}));
connect(revolute1.axis,inertia1.flange_b) annotation(Line(visible=true, origin={-23.3333,56.6667}, points={{3.3333,-46.6667},{3.3333,23.3333},{-6.6667,23.3333}}));
connect(damper1.flange_a,revolute1.support) annotation(Line(visible=true, origin={-41.6,23.29}, points={{-8.4,16.71},{-11.4,16.71},{-11.4,-10.065},{15.6,-10.065},{15.6,-13.29}}));
connect(damper1.flange_b,revolute1.axis) annotation(Line(visible=true, origin={-23.3333,30.0}, points={{-6.6667,10.0},{3.3333,10.0},{3.3333,-20.0}}));
connect(world.frame_b,revolute1.frame_a) annotation(Line(visible=true, origin={-70.0,0.0}, points={{-40.0,0.0},{40.0,0.0}}));
connect(revolute1.frame_b,bodyBox1.frame_a) annotation(Line(visible=true, origin={5.0,-0.0}, points={{-15.0,0.0},{15.0,-0.0}}));
end Plant;
end Components;
model SimpleExample
extends Modelica.Icons.Example;
annotation(Diagram(coordinateSystem(extent={{-148.5,-105.0},{148.5,105.0}}, preserveAspectRatio=true, initialScale=0.1, grid={5,5})));
Modelica.Blocks.Sources.Constant const(k=3.1415/2) annotation(Placement(visible=true, transformation(origin={-80.0,0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
Modelica.Blocks.Continuous.LimPID PID(k=108, Ti=0.85, Td=0.06, yMax=100) annotation(Placement(visible=true, transformation(origin={-40.0,0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
SimplePendulum.Components.Plant plant1 annotation(Placement(visible=true, transformation(origin={0.0,0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
equation
connect(PID.y,plant1.u) annotation(Line(visible=true, origin={-20.5,0.0}, points={{-8.5,-0.0},{8.5,0.0}}, color={0,0,127}));
connect(plant1.y,PID.u_m) annotation(Line(visible=true, origin={-8.2,-8.4}, points={{19.2,8.4},{22.2,8.4},{22.2,-6.6},{-31.8,-6.6},{-31.8,-3.6}}, color={0,0,127}));
connect(const.y,PID.u_s) annotation(Line(visible=true, origin={-60.5,0.0}, points={{-8.5,-0.0},{8.5,0.0}}, color={0,0,127}));
end SimpleExample;
end SimplePendulum;
](WSMSimulate—Wolfram Documentation)](TransferFunctionModel—Wolfram Documentation)






