Как использовать атрибут модуля в модели в Modelica?

мотивация

Modelica хранит единицы измерения (например, единицы СИ и не-СИ) в качестве атрибута относительно переменной. Вот пример для не-SI-модуля:

type Time_months = Real( quantity = "Time", unit = "mo", displayUnit = "months" )

Так как для моделей в экономике было бы довольно неудобно давать показатели в секундах, я хотел бы написать довольно общую функцию преобразования единиц, которая позволит преобразовывать единицы времени. Поэтому в идеале функция для преобразования в другую временную базу должна работать с тремя входами и одним выходом:

input Real timeValue "the value of time to be converted";
input String timeBaseA "the time base for timeValue, e.g. \"mo\" ";
input String timeBaseB "the time base to convert to, e.g. \"yr\" ";
output Real convertedTimeValue "the result of the conversion";

Вопросы

Если мы предположим, что переменная для некоторого значения времени уже имеет определенный атрибут единицы (например, "mo"), имеет смысл использовать эту метаинформацию в модели.

Вопрос 1: Как получить доступ к метаинформации, такой как единица измерения, внутри модели?

В идеале было бы что-то вроде следующего:

String timeBaseA := timeValue.unit;

или же

String timeBaseA := getUnit( timeValue ) "some function to read unit information";

Вопрос 2: Как мета-информация, такая как единица измерения, может быть назначена внутри функции?

В примере мы, конечно, хотели бы вернуть выходное значение с правильной единицей времени. Так что в идеале мы хотели бы иметь:

output Real convertedTime( quantity = "Time", unit = strTimeBaseB )

К сожалению, используя input приведет к ошибке, поскольку изменчивость отличается: атрибут единицы должен иметь постоянную изменчивость, но входная переменная имеет изменчивость параметра. (Использование функции - что было бы неплохо - также не удается по той же причине.)

3 ответа

Решение

По вопросу 1:

Я никогда не использовал Wolfram SystemModeler, но спецификация языка Modelica 3.4 говорит в главе 4.8 (Предопределенные типы и классы):

Атрибуты предопределенных типов переменных (Real, Integer, Boolean, String) ... не могут быть доступны с помощью точечной нотации и не ограничены уравнениями и разделами алгоритма.

По вопросу 2:

Я думаю, что можно определить единицу переменной при объявлении только из литерала или из конечного параметра - по крайней мере, это то, что я наблюдал в Dymola.

Альтернатива - использовать записи оператора

Вы можете использовать записи оператора для вашей задачи. Это позволит вам сохранить время в секундах и преобразовать его в то, что когда-либо понадобится, когда значение будет использовано.

Записи оператора позволяют определить несколько функций для их создания, сравнения или добавления, преобразования в строку и т. Д.

См. Краткий пример ниже, где определена запись оператора Time, которая может быть создана с помощью двух разных функций конструктора из секунд или дней и может быть преобразована в строки с помощью дня или секунд.

operator record Time
  Integer s "Second";

  encapsulated operator 'constructor'
    import Time;

    function from_s
      input Integer s "Seconds";
      output Time t(s=s);
    algorithm 
    end from_s;

    function from_d
      input Integer d "Days";
      output Time t(s=d*24*3600);
    algorithm 
    end from_d;
  end 'constructor';

  encapsulated operator 'String' "Convert Time to string"
    import Time;

    function formated
      input Time t;
      input String format = "s" annotation(choices(choice="s" "seconds", choice="d" "days"));
      output String str;

    algorithm 
      if format == "d" then
        str :=String(t.s/24/3600);
      else
        str :=String(t.s);
      end if;
    end formated;
  end 'String';

  encapsulated operator function '==' "Compare time records"
    import Time;
    input Time t1;
    input Time t2;
    output Boolean result "= t1 == t2";
  algorithm 
    result := t1.s == t2.s;
  end '==';

end Time;

Использование:

import Modelica.Utilities.Streams.print

t1 = Time(d=12)  // create record using day constructor
t2 = Time(s=3600*24*2)  // create record using second constructor

print(String(t1, format="s"))  // prints 1036800
print(String(t1, format="d"))  // prints 12
print(String(t2, format="s"))  // prints 172800
print(String(t2, format="d"))  // prints 2

Подробности см. В разделе "Modelica Spec 3.4", глава 14 "Перегруженные операторы".

Примечание: это было протестировано с Dymola 2019, а не с Wolfram SystemModeler

В Modelica обычно каждая переменная вычисляется на основе единиц СИ. Тогда у вас есть displayUnits построить их в другом блоке (не влияя на фактические вычисления).

Я не знаю о SystemModeler, но в Dymola преобразование между unit (вычислений) и displayUnit (только для печати) обрабатывается с помощью предварительно определенного сценария (displayUnit.mos). Пользователь может расширить его, чтобы он содержал пользовательские единицы отображения. Код для единиц измерения, относящихся ко времени, показан ниже. Я продлил его на неделю (w) в дополнение к предопределенным.

// Syntax:
// defineUnitConversion(<unit>, <derived unit>, <scale>, <opt. offset>);

// Time
defineUnitConversion("s", "ms", 1000);
defineUnitConversion("s", "min", 1/60);
defineUnitConversion("s", "h", 1/3600);
defineUnitConversion("s", "d", 1/86400);
defineUnitConversion("s", "w", 1/604800);

Это может быть выбрано на графиках вручную или по умолчанию "DisplayUnit" через Modelica.SIunits.Time t(displayUnit = "w") = ...;

Недостатком является то, что это расширение должно быть сделано в файле в каталоге установки. Поэтому его необходимо изменить после повторной установки инструмента или использования другого компьютера.

Если есть численные причины не вычислять решения за считанные секунды (например, из-за того, что значения станут большими), решение будет nominal атрибут, который позволяет масштабировать переменные.

Кстати, я думаю, что месяцы не очень хорошая единица времени, поскольку они могут иметь от 28 до 31 дня. Вот почему я выбрал недели в моем примере.

Вы можете использовать преобразование, как это делается в MSL, например, функцию Modelica.SIunits.Conversions.to_degC, которая имеет подпись:

function to_degC
  input Temperature Kelvin "Kelvin value";
  output NonSIunits.Temperature_degC Celsius "Celsius value";
end to_degC;

Это работает, но вам нужна одна такая функция для каждой единицы, между которой вы хотите конвертировать (именно поэтому большинство вычислений выполняется с использованием единиц СИ).

Другие вопросы по тегам