-
Notifications
You must be signed in to change notification settings - Fork 2
Getting Started
ODESCA is based on object-oriented programming, therefore it is relevant to understand the basic concept of the classes. After creating the classes, the data inside can be manipulated with the methods and the instances. These can be passed to functions.
For detail information about the classes move to the Class Documentation section.
The workflow in a new project typically follows these few steps:
- Creating the components (if not already created in other projects or as standard components)
- Creating instances of the components and parameterize them
- Creating system and connecting all components
- Analyzing the system mathematically (and creating a model of it)
In the following sections the four steps are described in more detail with examples. For a better understanding a system with two components will be created. Resistor and capacitor combined together in series form, create one component.
For the creation and parameterization of the differential equations, components are needed. These components (E.g.: Pipes, Sensors, etc.) all have unique equations which have to be modeled. For this purpose, a sub class of ODESCA_Component has to be created. To generate a custom component, use the utility function
ODESCA_Util.createNewComponentFile()
This method will open a dialog for the creation of a new component file.
After setting a name in the section Name, for example "RCelement", a location for the save location need to specified. By default, the location of example components is shown. The file should open automatically by clicking on the "Create Component" button.
Inside the file there are three sections which are marked as “User editable” where the differential equations system has to be described. Note that every change outside these sections (aside of the renaming of the file described above) may lead to an invalid component.
In this section every numeric variable necessary to create the equations can be defined in the array constructionParamNames. The names of the construction parameters have to be in this array as strings. For the example system the RC-element needs no construction parameters. Array and parameter area would look like below.
%==============================================================
%% DEFINITION OF CONSTRUCTION PARAMETERS (User editable)
%==============================================================
constructionParamNames = {};
%==============================================================
In general, if no construction parameters are needed, the array has to be empty.
The construction parameters can be accessed in the two sections below. They are stored in a structure which is part of the superior class ODESCA_Component. To access these parameters, use the command
obj.constructionParam.PARAMNAME
PARAMNAME is the name given to the construction parameters in the first section.
In the second section the states, inputs, outputs and parameters of the system have to be defined in the arrays stateNames, stateUnits, inputNames, inputUnits, outputNames, outputUnits, paramNames and paramUnits. For each of these groups the names and the units have to be written to the arrays as strings. The number of units has to match the number of names. Each component need to have at least one output. Arrays can be empty if no states, no inputs or no parameters are required.
In the case of a RC-element, it could look like this:
%==============================================================
%% DEFINITION OF EQUATION COMPONENTS (User editable)
%==============================================================
stateNames = {'Voltage_C'};
stateUnits = {'V'};
inputNames = {'Voltage_in'};
inputUnits = {'V'};
outputNames = {'Voltage_out'};
outputUnits = {'V'};
paramNames = {'Resistance', 'Capacity'};
paramUnits = {'Ohm', 'Farad'};
% =============================================================
In section three equations for state changes (f) and equations for outputs (g) of the component are created. To define these equations, the arrays f and g of the superior class ODESCA_Component have to be accessed.
For the state equations use the command
obj.f(NUM) =
and for the output equations use the command
obj.g(NUM) =
NUM is the number of the input in the arrays stateNames and inputNames. Avoiding an error, the number of equations should match the number of states and the number of outputs defined in the second section. Equations are defined symbolically using the symbolic variables [x1, x2, …] for the states, the symbolic variables [u1, u2, …] for the inputs and symbolic variables with the names of the parameters for the parameters. Symbolic variables that are not states, inputs or parameters, should not be part of the equations.
There are two ways to access these symbolic variables: First is to access them on the component itself by calling the arrays
obj.x(NUM)
for the states,
obj.u(NUM)
for the inputs and
obj.p(NUM)
for the parameters where the position NUM of the symbolic variable corresponds with the position of the parts in the name arrays of the second section. For the second way of use the template generates variables with the names of the states, inputs and parameters which contain the symbolic representation.
For the example of a RC-element, it could look like this:
%==============================================================
%% DEFINITION OF EQUATIONS (User Editable)
%==============================================================
obj.f(1) = (1/(Resistance * Capacity)) * (obj.u(1) - obj.x(1));
obj.g(1) = obj.x(1);
%==============================================================
After all the sections are filled, the component is ready for use. If the equations are not created correctly (wrong number of equations, wrong symbolic variables in the equations) the component will throw error on using it.
For a detailed example on how a component may look like, see the example “ExamplePipe” in the folder “Examples/Components/Examples”.
After the creation of the custom component class files, instances of these classes can be created. In a new script called "RCSystem" these instances can be modified and parameterized before they are added to a system.
RC = RCelement('RC1')
If the custom component has construction parameters (like the nodes of a pipe) these construction parameters have to be set to numeric values before any other action can be made to the instance of the component. To set the construction parameters use the method
setConstructionParam(paramName, value)
After all construction parameters have been set, component will be filled with the states, inputs, outputs, parameters and equations. For this purpose, a method called
tryCalculateEquations()
is called internally, which checks, if the equations are created correctly in the class. If a component does not have construction parameters, the component will be filled when an instance of it is created.
After the equations have been calculated the component can be modified in different ways. The parameters can be set with values, the position of the inputs and outputs can be changed, the name of the component can be changed and parameters can be set as inputs.
RC.setParam('Resistance', 5)
RC.setParam('Capacity', 50)
An easy way to store different configurations of a component is to create a function which creates and parameterizes the component and returns it afterwards. If this function is stored in a folder which is called “+COMPONENTNAME” the function can be called by COMPONENTNAME.Function() name. The behavior is similar to a static method which creates a new parameterized version of the component. The “+”-Operator of the folder creates a new namespace. For an example see the folder “Examples/Components/Examples/+ExamplePipe”. Inside the file the concept is explained in detail.
The components can be added to a system described in the following chapter. If one component should be added multiple times to a system with slightly different parameters (E.g.: a system could have multiple pipes with different length or radius), the component can either be copied and the copy can be modified or one component can be used and the parameters are changed between the times the component is added to a system.
Note that a change made to a component after it was added to a system is not made to the equations of the system because the content of the component is copied to the system.
To connect components, create models and analyze the equations, a system is needed. So first of all, a new instance of the class ODESCA_System has to be created. To do so call the constructor in this way by using the command
ODESCA_System(name, comp)
The arguments “name” and “comp” specify the name of the system and the first component can be added.
sys = ODESCA_System(RCsystem, RC)
After creating a new system, components can be added by using the
addComponent(comp)
method. The component which should be added to the system has to be the argument of this method. It is not possible to add a component with the same name as a component already added to the system. Note that all construction parameters the component might have must be set before adding the component. Otherwise the component cannot be added to the system because of the equations cannot be created.
RC.setName('RC2')
sys.addComponent(RC)
sys.setParam('RC2_Resistance', 2)
sys.setParam('RC2_Capacity', 20)
While adding a component its name is added to the names of the states, inputs, outputs and parameters. This is necessary to prevent name conflict and to determine to which component the data belongs. E.g.: if a component named “Sensor” with a state called “Temperature” is added to the system, the name of the state changes to “Sensor_Temperature”. Note that the properties of the component are copied to the system so a change to the instance of a component is not made to the corresponding equations in the system afterwards!
If the system only contains one component the next step can be skipped.
Now that the system is filled with components, it is time to connect them by replacing the inputs with outputs or equations. To do so use the method
connectInput(input, connection)
The argument “input” determines the input that should be replaced. The argument “connection” determines the variable that replaces the input. It can either be the name of an output as a string or a symbolic expression containing numeric values and states, inputs and parameters used in the system. NOTE that it must not contain the input which should be replaced, obviously. E.g.: To connect the input of the second RC element (“RC2_Voltage_in”) to the output of the first RC element (“RC1_Voltage_out”) use the following command:
sys.connectInput('RC2_Voltage_in', 'RC1_Voltage_out')
If there are outputs that should not appear in the system or the model, use the command
removeOutput(toRemove)
The argument “toRemove” is the name of the output which should be removed or its position in the list of outputs.
sys.removeOutput('RC1_Voltage_out')
Now that the system is created, it can be analyzed. Furthermore, a nonlinear Simulink model can be created from it. To create a nonlinear Simulink model, use the function
createNonlinearSimulinkModel(system, options)
of the system class:
sys.createNonlinearSimulinkModel()
For more clarification, scripts with detailed comments that create systems can be found in the folder “Library/Systems/Example”.
After a system with all components is created, system can be analyzed mathematically. One way to do this is to create steady states. In a steady state the system is in an equilibrium which means the inputs and states are constant: f(x0, u0) = 0
To create a steady state, the constant inputs u0 and the constant states x0 of the steady state have to be used in the method
createSteadyState(x0, u0, name)
The method creates a new steady state and adds it to the system. The argument is name optional but helpful to give the steady state a meaningful identifier.
SteadyState = sys.createSteadyState([5, 5], 5, 'SteadyState')
After a steady state is created a linear approximation can be created. To create a linear approximation, use the method
linearize()
The method creates a linear approximation and adds it to the steady state.
Note: To create a linear approximation, the control system toolbox has to be available. If it is not, the method will throw an error.
To get all linearization of the steady states, use the method linear() which returns the linearization's in an array.
lin = linear(SteadyState)
Now that one or multiple linearization where obtained, a number of linear analysis method can be used. E.g.: if lin is an array of multiple linearization, the call
lin.bodeplot('from', 1, 'to', n)
plots the bode plot for all linearization. The options ‘from’ and ‘to’ specify that only the plot from input 1 to output number n should be displayed. In this example there is only one bode plot to be created, so the following command can be used.
lin.bodeplot()
Another example is the analysis of the stability which can be done easily for all linearization with the command
lin.isAsymptoticStable()
The method returns an array where each entry corresponds to the linearization in the array lin. 1 means asymptotic stable. In the same way the ability to control and to observe can be checked with the following command:
stable = lin.isAsymptoticStable()
ctrl = lin.isControllable('hautus')
obsv = lin.isObservable('hautus')
For a detailed example on how a system analyze may look like, see the example “PipeSystem” in the folder “Examples/Systems”.