Search code examples
modelicaopenmodelica

Load a record using dot notation and strings from an array in Modelica


Is there a way to load a record in Modelica, and manipulating part of the record directory with a string from an array?

I included the minimal working example below, in summary, I have two different records with parameter values named Cylinder and Board and I have an array containing their names. I would like to access one set of parameter values in a function, depending on the user input "ShapeNumber". So when the user is putting a 1, load the parameters from the Cylinder record, when the user puts a 2, load the parameters from the Board record. Ideally, I would like to add the string "Cylinder" or "Board" into the dot notation for loading the record, as indicated in the code below.

package Test_things

  record General_parameters
    parameter Real Length "Length in m";
  end General_parameters;

  record Cylinder_parameters
    extends General_parameters;
    parameter Real Diameter "Diameter in m";
  end Cylinder_parameters;

  record Board_parameters
    extends General_parameters;
    parameter Real Width "Width in m";
    parameter Real Depth "Depth in m";
  end Board_parameters;

  record Parameter_values
    constant Test_things.Cylinder_parameters Cylinder(Length=10, Diameter=0.5);
    constant Test_things.Board_parameters Board(
      Length=20,
      Width=1,
      Depth=0.01);
    constant String[2] ShapesArray = {"Cylinder","Board"};
  end Parameter_values;

  function Length_tester
    input Integer   ShapeNumber; //Which shape from within the shape array is tested

    output Boolean LongEnough;

    Test_things.Parameter_values Values; //Gives Values.Cylinder, Values.Board, Values.ShapesArray

  protected
    String ShapeName;
    Real Length;
  algorithm
    //Load correct shape
    ShapeName := Values.ShapesArray[ShapeNumber];

    // This is what I would like: Length := Values.{ShapeName}.Length;
    // which results in one of the two lines of code below.
    // Length := Values.Cylinder.Length;
    // Length := Values.Board.Length;

    // This works for now, but has to be adjusted whenever a new shape is used
    if ShapeName == "Cylinder" then
      Length := Values.Cylinder.Length;
    elseif ShapeName == "Board" then
      Length := Values.Board.Length;
    end if;

    if Length > 15 then
      LongEnough := true;
    else
      LongEnough := false;
    end if;

  end Length_tester;

  model Main
    Boolean LongEnoughCylinder;
    Boolean LongEnoughBoard;
  equation
    // Test Cylinder
    LongEnoughCylinder = Length_tester(1);
    // Test Board
    LongEnoughBoard = Length_tester(2);
  end Main;
end Test_things;

I found the work around with the if-statement, but when I add a new record, I have to update that if-statement in every function I have, rather than only updating the array with the names of the records.

Thank you for any help!


Solution

  • What you try to do is currently not possible, as Modelica does not support dynamic creation of class paths. All class paths must be hard coded.

    Therefore your if/else workaround is a legit solution. To keep the maintainability low, you could create a function getLength with contains the if/else logic and all your other functions will use this one.

    Below you can find the solution which I would use. Everything is made as generic as possible, so you can use an arbitrary number of shapes of any kind. To do so, there is the generic shape type Shapewith all possible shape parameters. Then there are the specialized shapes Board and Cylinder, which set not needed parameters on 0 in combination with the keyword final, so users can not modify them. This allows to create arrays of shapes, containing the basic shape type, as well as the specialized shapes. The testLength function will then be very simple, accepting an array of shapes. The downside of this solution is (for you as developer), that new shapes with new parameter require an update the basic shape type and the specialized shapes.

    package Test_things
    
      record Shape "Generic shape with all possible parameters"
        import SI = Modelica.SIunits;
    
        // Common parameters
        parameter ShapeTypes shapeType "Kind of shape";
        parameter SI.Length length;
    
        // Board parameters
        parameter SI.Length width;
        parameter SI.Length depth;
    
        // Cylinder parameters
        parameter SI.Diameter diameter;
      end Shape;
    
      type ShapeTypes = enumeration(Cylinder, Board);
    
      record Cylinder "Cylinder shape with unneeded parameters set on final 0"
        extends Shape(final shapeType=ShapeTypes.Cylinder, final width=0, final depth=0);
      end Cylinder;
    
      record Board "Board shape with unneeded parameters set on final 0"
        extends Shape(final shapeType=ShapeTypes.Board, final diameter=0);
      end Board;
    
      function testLength "Check if length of a specific shape in an array of shapes is bigger than 15"
        input Integer shapeNumber "Shape of interest";
        input  Shape shapes[:] "Array of shapes";
        output Boolean longEnough;
      algorithm 
        longEnough := shapes[shapeNumber].length > 15;
      end testLength;
    
      model Main
        constant Shape shapes[:] = {
          Board(length=20, width=1, depth=0.01),
          Cylinder(length=1, diameter=2)}; 
       // note: this works in OpenModelica only with the prefix constant, parameter does not compile
       // in Dymola it works with both
    
        Boolean longEnoughCylinder;
        Boolean longEnoughBoard;
      equation 
        // Test Cylinder
        longEnoughCylinder = testLength(1, shapes);
        // Test Board
        longEnoughBoard = testLength(2, shapes);
      end Main;
    
    end Test_things6;
    

    Some notes on style:

    • all classes except of function should be written in upper case
    • all instances, parameters, etc. should be written in lower case
    • instead of writing the unit into the comment, use SI units