"""
***********************************************************************************
modelica.py
DAE Tools: pyDAE module, www.daetools.com
Copyright (C) Dragan Nikolic
***********************************************************************************
DAE Tools is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License version 3 as published by the Free Software
Foundation. DAE Tools is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with the
DAE Tools software; if not, see <http://www.gnu.org/licenses/>.
************************************************************************************
"""
import os, shutil, sys, numpy, math, traceback
from daetools.pyDAE import *
from .formatter import daeExpressionFormatter
from .analyzer import daeCodeGeneratorAnalyzer
from .code_generator import daeCodeGenerator
modelTemplate = """\
class %(model_name)s
/* Description:
%(doc_string)s
%(warnings)s
*/
/* Import libs */
import Modelica.Math.*;
%(parameters_defs)s
%(stns_defs)s
%(variables_defs)s
equation
%(equations_defs)s
/* DOFs */
%(assigned_inits)s
%(whens)s
initial equation
%(initial_conditions)s
annotation(experiment(StartTime = %(start_time)s, StopTime = %(end_time)s, Tolerance = %(tolerance)s));
end %(model_name)s;
"""
[docs]class daeCodeGenerator_Modelica(daeCodeGenerator):
def __init__(self):
self.exprFormatter = daeExpressionFormatter_Modelica()
self.analyzer = daeCodeGeneratorAnalyzer()
self._reset()
def _reset(self):
self.rootModelName = ''
self.defaultIndent = ' '
self.warnings = []
self.topLevelModel = None
self.simulation = None
self.variable_types = []
self.domains = []
self.parameters = []
self.variables = []
self.equations = []
self.stns = []
self.whens = []
self.domains_inits = []
self.parameters_inits = []
self.assigned_inits = []
self.initial_conditions = []
[docs] def generateSimulation(self, simulation, directory):
# Reset all arrays
self._reset()
if not simulation:
raise RuntimeError('Invalid simulation object')
if not os.path.isdir(directory):
os.makedirs(directory)
# Achtung, Achtung!!
# rootModelName and exprFormatter.modelCanonicalName should not be stripped
# of illegal characters, since they are used to get relative names
self.rootModelName = simulation.m.Name
self.exprFormatter.modelCanonicalName = simulation.m.Name
self.simulation = simulation
self.topLevelModel = simulation.m
model_name = simulation.m.GetStrippedName()
instance_name = simulation.m.GetStrippedName()
doc_string = sys.modules[simulation.__module__].__doc__
indent = 1
s_indent = indent * self.defaultIndent
self.analyzer.analyzeSimulation(simulation)
self.exprFormatter.IDs = self.analyzer.runtimeInformation['IDs']
self.exprFormatter.indexMap = self.analyzer.runtimeInformation['IndexMappings']
self._generateRuntimeInformation(self.analyzer.runtimeInformation)
separator = '\n' + self.defaultIndent
warnings = ''
if len(self.warnings) > 0:
warnings = 'daetools-Modelica code-generator warnings:\n'
warnings += '\n'.join(self.warnings)
# Model
variable_types_defs = separator.join(self.variable_types)
domains_defs = separator.join(self.domains)
parameters_defs = separator.join(self.parameters)
variables_defs = separator.join(self.variables)
stns_defs = separator.join(self.stns)
equations_defs = separator.join(self.equations)
# Simulation
domains_inits = separator.join(self.domains_inits)
parameters_inits = separator.join(self.parameters_inits)
assigned_inits = separator.join(self.assigned_inits)
whens = separator.join(self.whens)
initial_conditions = separator.join(self.initial_conditions)
reporting_interval = self.simulation.ReportingInterval
time_horizon = self.simulation.TimeHorizon
relative_tolerance = self.simulation.DAESolver.RelativeTolerance
dictInfo = {
'instance_name' : instance_name,
'model_name' : model_name,
'doc_string' : doc_string,
'variable_types_defs': variable_types_defs,
'domains_defs' : domains_defs,
'parameters_defs' : parameters_defs,
'variables_defs' : variables_defs,
'stns_defs' : stns_defs,
'equations_defs' : equations_defs,
'domains_inits' : domains_inits,
'parameters_inits' : parameters_inits,
'assigned_inits' : assigned_inits,
'whens' : whens,
'initial_conditions' : initial_conditions,
'start_time' : 0.0,
'end_time' : time_horizon,
'tolerance' : relative_tolerance,
'warnings' : warnings
}
model_contents = modelTemplate % dictInfo;
f = open(os.path.join(directory, '%s.mo' % model_name), "w")
f.write(model_contents)
f.close()
if len(self.warnings) > 0:
print('CODE GENERATOR WARNINGS:')
print(warnings)
def _processEquations(self, Equations, indent):
s_indent = indent * self.defaultIndent
for equation in Equations:
description = equation['Description']
if description:
self.equations.append(s_indent + '/* {0}: */'.format(description))
for eeinfo in equation['EquationExecutionInfos']:
res = self.exprFormatter.formatRuntimeNode(eeinfo['ResidualRuntimeNode'])
self.equations.append(s_indent + '{0} = 0;'.format(res))
def _processSTNs(self, STNs, indent):
s_indent = indent * self.defaultIndent
s_indent1 = (indent + 1) * self.defaultIndent
s_indent2 = (indent + 2) * self.defaultIndent
rootModel = self.exprFormatter.formatIdentifier(self.rootModelName)
for stn in STNs:
if stn['Class'] == 'daeIF':
relativeName = daeGetRelativeName(self.rootModelName, stn['CanonicalName'])
relativeName = self.exprFormatter.formatIdentifier(relativeName)
stnVariableName = self.exprFormatter.flattenIdentifier(relativeName)
description = stn['Description']
states = ', '.join(st['Name'] for st in stn['States'])
activeState = stn['ActiveState']
nStates = len(stn['States'])
for i, state in enumerate(stn['States']):
# Not all states have state_transitions ('else' state has no state transitions)
on_condition_action = None
if i == 0:
# There is only one OnConditionAction in IF
on_condition_action = state['OnConditionActions'][0]
condition = self.exprFormatter.formatRuntimeConditionNode(on_condition_action['ConditionRuntimeNode'])
temp = 'if ({0}) then'.format(condition)
self.equations.append(temp)
elif (i > 0) and (i < nStates - 1):
# There is only one OnConditionAction in ELSE_IFs
on_condition_action = state['OnConditionActions'][0]
condition = self.exprFormatter.formatRuntimeConditionNode(on_condition_action['ConditionRuntimeNode'])
temp = 'elseif ({0}) then'.format(condition)
self.equations.append(temp)
else:
temp = 'else '
self.equations.append(temp)
# 1a. Generate NestedSTNs
self._processSTNs(state['NestedSTNs'], indent+1)
# 1b. Put equations into the residuals list
self._processEquations(state['Equations'], indent+1)
end = s_indent + 'end if;'
self.equations.append(end)
elif stn['Class'] == 'daeSTN':
relativeName = daeGetRelativeName(self.rootModelName, stn['CanonicalName'])
relativeName = self.exprFormatter.formatIdentifier(relativeName)
stnVariableName = self.exprFormatter.flattenIdentifier(relativeName)
description = stn['Description']
states = ', '.join(st['Name'] for st in stn['States'])
activeState = stn['ActiveState']
# Achtung, Achtung!!
# If there are no active state changes ("when" blocks) Modelica complains about the number of equations.
# Is it a bug in OpenModelica or the rule? Investigate...
# The workaround is to count the number of state-change actions and if larger than 0
# the initially active state goes to the "initial equation" section.
# If equal to zero, it never changes so it can go to the String declaration
numberOfChangeStateActions = 0
counter = 0
nStates = len(stn['States'])
for i, state in enumerate(stn['States']):
# Not all states have state_transitions ('else' state has no state transitions)
on_condition_action = None
if i == 0:
temp = 'if ({name} == "{state}") then'.format(name = stnVariableName,
state = state["Name"])
self.equations.append(temp)
elif (i > 0) and (i < nStates - 1):
temp = 'elseif ({name} == "{state}") then'.format(name = stnVariableName,
state = state["Name"])
self.equations.append(temp)
else:
temp = 'else '
self.equations.append(temp)
if len(state['OnConditionActions']) > 0:
temp = '/* OnConditionActions for {stn}.{state} */'.format(stn = stnVariableName,
state = state['Name'])
self.whens.append(temp)
for on_condition_action in state['OnConditionActions']:
condition = self.exprFormatter.formatRuntimeConditionNode(on_condition_action['ConditionRuntimeNode'])
if counter == 0:
temp = 'when ({0} == "{1}") and ({2}) then'.format(stnVariableName, state['Name'], condition)
else:
temp = 'elsewhen ({0} == "{1}") and ({2}) then'.format(stnVariableName, state['Name'], condition)
self.whens.append(temp)
# Generate on condition actions
# Nota bene:
# Add the number of state-change actions to numberOfChangeStateActions
numberOfChangeStateActions += self._processActions(on_condition_action['Actions'], indent+1)
counter += 1
# Generate NestedSTNs
self._processSTNs(state['NestedSTNs'], indent+1)
# Generate equations
self._processEquations(state['Equations'], indent+1)
if counter > 0:
end = 'end when;'
self.whens.append(end)
end = 'end if;'
self.equations.append(end)
# Finally add the String variable (taking into account the total number of state-change actions)
if numberOfChangeStateActions > 0:
# If there are state-change actions, an initial value goes to the initial equation section
stnTemplate = 'String {name} "{description}"; /* States: [{states}] */'
self.stns.append(stnTemplate.format(name = stnVariableName,
description = description,
activeState = activeState,
states = states))
# Initial equations
stnTemplate = '{name} = "{activeState}";'
self.initial_conditions.append(stnTemplate.format(name = stnVariableName,
activeState = activeState))
else:
# If there are NO state-change actions, an initial value goes into the String variable declaration
stnTemplate = 'String {name} = "{activeState}" "{description}"; /* States: [{states}] */'
self.stns.append(stnTemplate.format(name = stnVariableName,
description = description,
activeState = activeState,
states = states))
def _processActions(self, Actions, indent):
s_indent = indent * self.defaultIndent
numberOfChangeStateActions = 0
for action in Actions:
if action['Type'] == 'eChangeState':
stnVariableName = action['STN']
stateTo = action['StateTo']
self.whens.append(s_indent + '{0} = "{1}";'.format(stnVariableName, stateTo))
numberOfChangeStateActions += 1
elif action['Type'] == 'eSendEvent':
self.warnings.append('Modelica code cannot be generated for SendEvent actions - the model will not work as expected!!')
elif action['Type'] == 'eReAssignOrReInitializeVariable':
relativeName = daeGetRelativeName(self.wrapperInstanceName, action['VariableWrapper'].Variable.CanonicalName)
relativeName = self.exprFormatter.formatIdentifier(relativeName)
domainIndexes = action['VariableWrapper'].DomainIndexes
node = action['RuntimeNode']
strDomainIndexes = ''
if len(domainIndexes) > 0:
strDomainIndexes = '[' + ','.join() + ']'
variableName = relativeName + strDomainIndexes
value = self.exprFormatter.formatRuntimeNode(node)
self.whens.append(s_indent + 'reinit({0}, {1});'.format(variableName, value))
elif action['Type'] == 'eUserDefinedAction':
self.warnings.append('Modelica code cannot be generated for UserDefined actions - the model will not work as expected!!')
else:
pass
return numberOfChangeStateActions
def _generateRuntimeInformation(self, runtimeInformation):
Ntotal = runtimeInformation['TotalNumberOfVariables']
Neq = runtimeInformation['NumberOfEquations']
IDs = runtimeInformation['IDs']
initValues = runtimeInformation['Values']
initDerivatives = runtimeInformation['TimeDerivatives']
indexMappings = runtimeInformation['IndexMappings']
absoluteTolerances = runtimeInformation['AbsoluteTolerances']
blockIDs = Neq * [-1]
blockInitValues = Neq * [-1]
absTolerances = Neq * [1E-5]
blockInitDerivatives = Neq * [0.0]
for oi, bi in list(indexMappings.items()):
if IDs[oi] == cnAlgebraic:
blockIDs[bi] = cnAlgebraic
elif IDs[oi] == cnDifferential:
blockIDs[bi] = cnDifferential
if IDs[oi] != cnAssigned:
blockInitValues[bi] = initValues[oi]
blockInitDerivatives[bi] = initDerivatives[oi]
absTolerances[bi] = absoluteTolerances[oi]
rootModel = self.exprFormatter.formatIdentifier(self.rootModelName)
for domain in runtimeInformation['Domains']:
relativeName = daeGetRelativeName(self.rootModelName, domain['CanonicalName'])
formattedName = self.exprFormatter.formatIdentifier(relativeName)
name = self.exprFormatter.flattenIdentifier(formattedName)
description = domain['Description']
numberOfPoints = domain['NumberOfPoints']
units = self.exprFormatter.formatUnits(domain['Units'])
domains = '[' + str(domain['NumberOfPoints']) + ']'
points = self.exprFormatter.formatNumpyArray(domain['Points']) # Numpy array
domTemplate = 'parameter Integer {name}_np = {numberOfPoints} "Number of points in domain {name}";'
self.parameters.append(domTemplate.format(name = name,
description = description,
numberOfPoints = numberOfPoints))
domTemplate = 'parameter Real {name}[{name}_np](each unit = "{units}") = {points} "{description}";'
self.parameters.append(domTemplate.format(name = name,
description = description,
units = units,
points = points,
numberOfPoints = numberOfPoints))
for parameter in runtimeInformation['Parameters']:
relativeName = daeGetRelativeName(self.rootModelName, parameter['CanonicalName'])
formattedName = self.exprFormatter.formatIdentifier(relativeName)
name = self.exprFormatter.flattenIdentifier(formattedName)
description = parameter['Description']
numberOfPoints = int(parameter['NumberOfPoints'])
units = self.exprFormatter.formatUnits(parameter['Units'])
values = self.exprFormatter.formatNumpyArray(parameter['Values']) # Numpy array
numberOfDomains = len(parameter['Domains'])
if numberOfDomains > 0:
relativeDomains = [daeGetRelativeName(self.rootModelName, dn) for dn in parameter['DomainNames']]
formatDomains = [self.exprFormatter.formatIdentifier(dn) for dn in relativeDomains]
flatDomains = [self.exprFormatter.flattenIdentifier(dn)+'_np' for dn in formatDomains]
domains = '{0}'.format(', '.join(flatDomains))
attributes = '(each unit = "%s")' % units
paramTemplate = 'parameter Real {name}[{domains}]{attributes} = {values} "{description}";'
self.parameters.append(paramTemplate.format(name = name,
attributes = attributes,
description = description,
values = values,
domains = domains))
else:
attributes = '(unit = "%s")' % units
paramTemplate = 'parameter Real {name}{attributes} = {value} "{description}";'
self.parameters.append(paramTemplate.format(name = name,
attributes = attributes,
value = values,
description = description))
variableTypesUsed = {}
for variable in runtimeInformation['Variables']:
relativeName = daeGetRelativeName(self.rootModelName, variable['CanonicalName'])
formattedName = self.exprFormatter.formatIdentifier(relativeName)
name = self.exprFormatter.flattenIdentifier(formattedName)
description = variable['Description']
numberOfPoints = variable['NumberOfPoints']
units = self.exprFormatter.formatUnits(variable['Units'])
varTypeName = variable['VariableType'].Name
varType = variable['VariableType']
numberOfDomains = len(variable['Domains'])
vartypeTemplate = '{name}({min}, {max}, {default}, {absTol});'
if not varTypeName in variableTypesUsed:
variableTypesUsed[varTypeName] = None
self.variable_types.append(vartypeTemplate.format(name = varTypeName,
min = varType.LowerBound,
max = varType.UpperBound,
default = varType.InitialGuess,
absTol = varType.AbsoluteTolerance))
if numberOfDomains > 0:
relativeDomains = [daeGetRelativeName(self.rootModelName, dn) for dn in variable['DomainNames']]
formatDomains = [self.exprFormatter.formatIdentifier(dn) for dn in relativeDomains]
flatDomains = [self.exprFormatter.flattenIdentifier(dn)+'_np' for dn in formatDomains]
domains = '{0}'.format(', '.join(flatDomains))
attributes = '(each unit = "%s")' % units
varTemplate = 'Real {name}[{domains}]{attributes} "{description}";'
self.variables.append(varTemplate.format(name = name,
attributes = attributes,
description = description,
domains = domains))
domainsIndexesMap = variable['DomainsIndexesMap']
for i in range(0, numberOfPoints):
domIndexes = tuple(domainsIndexesMap[i]) # list of integers
ID = int(variable['IDs'][domIndexes]) # cnDifferential, cnAssigned or cnAlgebraic
value = float(variable['Values'][domIndexes]) # numpy float
overallIndex = variable['OverallIndex'] + i
#fullName = name + '[' + ','.join(str(di+self.exprFormatter.indexBase) for di in domIndexes) + ']'
fullName = self.exprFormatter.formatVariable(variable['CanonicalName'], domIndexes, overallIndex)
if ID == cnDifferential:
temp = '{name} = {value} /* {units} */;'.format(name = fullName,
value = value,
units = units)
self.initial_conditions.append(temp)
elif ID == cnAssigned:
temp = '{name} = {value} /* {units} */;'.format(name = fullName,
value = value,
units = units)
self.assigned_inits.append(temp)
else:
ID = int(variable['IDs']) # cnDifferential, cnAssigned or cnAlgebraic
value = float(variable['Values']) # numpy float
if ID == cnDifferential:
attributes = '(start = 0.0, unit = "%s")' % units
varTemplate = 'Real {name}{attributes} "{description}";'
initTemplate = '{name} = {value} /* {units} */;'
self.initial_conditions.append(initTemplate.format(name = name,
units = units,
value = value))
elif ID == cnAssigned:
attributes = '(unit = "%s")' % units
varTemplate = 'Real {name}{attributes} "{description}";'
temp = '{name} = {value} /* {units} */;'.format(name = name,
value = value,
units = units)
self.assigned_inits.append(temp)
else:
attributes = '(unit = "%s")' % units
varTemplate = 'Real {name}{attributes} "{description}";'
self.variables.append(varTemplate.format(name = name,
attributes = attributes,
value = value,
rootModel = rootModel,
description = description))
indent = 0
# First, generate equations for port connections
for port_connection in runtimeInformation['PortConnections']:
self.equations.append('/* Port connection: {0} -> {1} */'.format(port_connection['PortFrom'], port_connection['PortTo']))
self._processEquations(port_connection['Equations'], indent)
# Then, generate ordinary equations
self._processEquations(runtimeInformation['Equations'], indent)
# Finally, generate equations for IFs/STNs
self._processSTNs(runtimeInformation['STNs'], indent)