Main Content

Defining Component Variants

Physical modeling often requires incremental modeling approach. It is a good practice to start with a simple model, run and troubleshoot it, then add the desired special effects, like fluid compressibility or fluid inertia. Another example is modeling a diode with different levels of complexity: linear, zener diode, or exponential. Composite components often require conditional inclusion of a certain member component and a flexible connection scheme.

Including different modeling variants within a single component requires applying control logic to determine the model configuration. You achieve this goal by using conditional sections in a component file.

Conditional Sections

A conditional section is a top-level section guarded by an if clause. Conditional sections are parallel to other top-level sections of a component file, such as declaration or equations sections.

A conditional section starts with an if keyword and ends with an end keyword. It can have optional elseif and else branches. The body of each branch of a conditional section can contain declaration blocks, equations, structure sections, and so on, but cannot contain the setup function.

The if and elseif branches start with a predicate expression. If a predicate is true, the branch gets activated. When all predicates are false, the else branch (if present) gets activated. The compiled model includes elements (such as declarations, equations, and so on) from active branches only.

component MyComp
  [...]
  if Predicate1
    [...] % body of branch1
  elseif Predicate2
    [...] % body of branch2
  else 
    [...] % body of branch3
  end
  [...]
end

Unlike the if statements in the equations section, different branches of a conditional section can have different variables, different number of equations, and so on. For example, you can have two variants of a pipe, one that accounts for resistive properties only and the second that also models fluid compressibility:

component MyPipe
  parameters
     fl_c = 0; % Model compressibility? (0 - no, 1 - yes)
  end
  [...] % other parameters, variables, branches
  if fl_c == 0
    equations
       % first set of equations, resistive properties only 
    end
  else
    variables
       % additional variable declarations, needed to account for fluid compressibility 
    end
    equations
       % second set of equations, including fluid compressibility 
    end
  end
end

In this example, if the block parameter Model compressibility? (0 - no, 1 - yes) is set to 0, the first set of equations gets activated and the block models only the resistive properties of the pipe. If the block user changes the value of the parameter, then the else branch gets activated, and the compiled model includes the additional variables and equations that account for fluid compressibility.

Note

Enumerations are very useful in defining component variants, because they let you specify a discrete set of acceptable parameter values. For an example of how this component can use enumeration, see Using Enumeration in Predicates.

Rules and Restrictions

Nested conditional sections are allowed. For example:

component A
  parameters
     p1 = 0;
     p2 = 0;
     p3 = 0;
  end
  if p1 > 0
   [...]
     if p2 > 0
        [...]
     end
     if p3 > 0
        [...]
     end
   [...]
   end
end

Predicates must be parametric expressions, because the structure of a model must be fixed at compile time and cannot change once the model is compiled. Using a variable in a predicate results in a compile-time error:

component A
   [...]
   variables
     v = 0;
   end
   if v > 0  % error: v>0 is not a parametric expression because v is a variable
     [...]
   else
     [...]
   end
 end

Predicates may depend on parameters of the parent (enclosing) component. They may not depend, directly or indirectly, on parameters of member (embedded) components or on domain parameters:

component A
   parameters
     p = 1;
   end
   parameters(Access=private)
     pp = c.p;
   end
   components
     c = MyComp;
   end
   nodes
     n = MyDomain;
   end
   if p > 0  % ok
     [...]
   elseif c.p > 0 % error: may not depend on parameters of embedded component
     [...]
   elseif n.p > 0 % error: may not depend on domain parameters
     [...]
   elseif pp > 0 % error: pp depends on c.p 
     [...]
   end
 end

Accessibility of class members declared inside conditional sections is equivalent to private class members (Access=private). They are not accessible from outside the component class, even if their branch is active.

The scope of the class members declared inside a conditional section is the entire component class. For example:

component A
   nodes
     p = foundation.electrical.electrical;
     n = foundation.electrical.electrical;
   end
   parameters
     p1 = 1;
   end
   if p1 > 0
     components
       r1 = MyComponentVariant1;
     end
   else
     components
       r1 = MyComponentVariant2;
     end
   end
   connections
     connect(p, r1.p);
     connect(n, r1.n);
   end
 end

However, using a conditional member outside the conditional section when the branch is not active results in a compilation error:

component A
   nodes
     p = foundation.electrical.electrical;
     n = foundation.electrical.electrical;
   end
   parameters
     p1 = 0;
   end
   if p1 > 0
     components
       r1 = MyComponentVariant1;
     end
   end
   connections
     connect(p, r1.p); % error if p1=0 and the predicate is false
   end
 end

Parameters that are referenced by predicates of conditional sections, directly and indirectly, must be compile-time parameters. The setup function may not write to these parameters, for example:

component A
   parameters
     p1 = 1;
   end
   if p1 > 0  % p1 is a compile-time parameter 
     [...]
   else
     [...]
   end
   function setup
     tmp = p1; % ok to read from p1
     p1 = 10;  % error: may not write to p1 here
   end
 end

Example

This simple example shows a component containing two resistors. The resistors can be connected either in series or in parallel, depending on the value of the control parameter:

component TwoResistors
  nodes
     p = foundation.electrical.electrical; % +:left
     n = foundation.electrical.electrical; % -:right
  end
  parameters
    p1 = {1, 'Ohm'};   % Resistor 1
    p2 = {1, 'Ohm'};   % Resistor 2
    ct = 0;            % Connection type (0 - series, 1 - parallel)
  end
  components(ExternalAccess=observe)
    r1 = foundation.electrical.elements.resistor(R=p1);
    r2 = foundation.electrical.elements.resistor(R=p2);
  end
  if ct == 0      % linear connection
    connections
      connect(p, r1.p);
      connect(r1.n, r2.p);
      connect(r2.n, n);
    end
  else          % parallel connection
    connections
      connect(r1.p, r2.p, p);
      connect(r1.n, r2.n, n);
    end
  end
end

To test the correct behavior of the conditional section, point a Simscape Component block to this component file. Place the block in a circuit with a 10V DC voltage source and a current sensor. With the default parameter values, the resistors are connected in series, and the current is 5A.

If you change the value of the Connection type (0 - series, 1 - parallel) parameter to 1, the resistors are connected in parallel, and the current is 20A.

Related Topics