Skip to content

Commit fdb94a9

Browse files
committed
Updated code for acausal built-in components + added new test model TestHeatTransfer2 where code size is independent from number of temperature nodes.
1 parent ab99c83 commit fdb94a9

13 files changed

+285
-34
lines changed

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
authors = ["Hilding Elmqvist <[email protected]>", "Martin Otter <[email protected]>"]
22
name = "Modia"
33
uuid = "cb905087-75eb-5f27-8515-1ce0ec8e839e"
4-
version = "0.9.4"
4+
version = "0.10.0-dev"
55

66
[deps]
77
DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e"

docs/src/index.md

+11
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ functionalities of these packages.
4242

4343
## Release Notes
4444

45+
### Version 0.10.0
46+
47+
-
48+
49+
**Non-backwards** compatible changes (should usually not influende user models)
50+
51+
- `_buildFunction = <functionName>` changed to `_buildFunction = Par(functionName = <functionName>)` and
52+
additional argument `unitless` added to `<functionName>`.
53+
- `_instantiateFunction = Par(..)` changed to `_initSegmentFunction = Par(..)`
54+
55+
4556
### Version 0.9.4
4657

4758
- Precompile statements included (compilation of Modia package takes more time, but startup of Modia model simulations is faster).

models/HeatTransfer.jl

+36
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,40 @@ InsulatedRod = Model(
7070
]
7171
)
7272

73+
74+
include("HeatTransfer/InsulatedRod2.jl")
75+
76+
"""
77+
insulatedRod = InsulatedRod2(; L, A, rho=7500.0u"kg/m^3", lambda=74.0u"W/(m*K)", c=450.0u"J/(kg*K), T0=293.15u"K", nT=1")
7378
79+
Generate a Model(..) instance of an insulated rod with length `L` and cross sectional area `A` that
80+
models 1D heat transfer from port_a to port_b. The rod is discretized with `nT` internal temperature nodes that are initialized with `T0`.
81+
This is an acausal built-in component where the code size does not depend on `nT` and
82+
`nT` can still be changed after model code is generated and compiled.
83+
84+
For more details see Appendix B1 of [DOI: 10.3390/electronics12030500](https://doi.org/10.3390/electronics12030500).
85+
86+
# Optional Arguments:
87+
- `rho`: density of rod material
88+
- `lambda`: thermal conductivity of rod material
89+
- `c`: specific heat capacity of rod material
90+
- `T0`: initial temperature of internal temperature nodes
91+
- `nT`: number of temperature nodes (`nT > 0`).
92+
93+
# Internal variables that can be inquired/plotted:
94+
- `T`: Vector of temperatures at the internal temperature nodes
95+
- `der(T)': Vector of derivatives of `T`.
96+
"""
97+
InsulatedRod2 = Model(;
98+
_buildFunction = Par(functionName = :(buildInsulatedRod2!)), # Called once in @instantiateModel(..) before getDerivatives!(..) is generated
99+
_initSegmentFunction = Par(functionName = :(initInsulatedRod2ForNewSegment!)), # Called once before initialization of a new simulation segment
100+
L = 1.0u"m", # Length of rod
101+
A = 0.0004u"m^2", # Rod area
102+
rho = 7500.0u"kg/m^3", # Density of rod material
103+
lambda = 74.0u"W/(m*K)", # Thermal conductivity of rod material
104+
c = 450.0u"J/(kg*K)", # Specific heat capacity of rod material
105+
T0 = 293.15u"K", # Initial temperature of internal temperature nodes
106+
nT = 1, # Number of temperature nodes (nT > 0).
107+
port_a = HeatPort, # heat port on left side
108+
port_b = HeatPort # heat port on right side
109+
)

models/HeatTransfer/InsulatedRod2.jl

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# License for this file: MIT (expat)
2+
# Copyright 2023, DLR Institute of System Dynamics and Control
3+
#
4+
# This file is included in Modia/models/HeatTranser.jl
5+
6+
7+
# Structure holding the internal memory of the insulated rod
8+
mutable struct InsulatedRodStruct{FloatType}
9+
# Parameters
10+
path::String # path name of instance
11+
Ge::FloatType # = lambda*A/dx
12+
Ge2::FloatType # = 2*Ge
13+
k::FloatType # = Ge / (c*rho*A*dx)
14+
T_init::Vector{FloatType} # initial states
15+
16+
# Internal states and state deriatives
17+
T::Vector{FloatType} # states
18+
der_T::Vector{FloatType} # state derivatives
19+
20+
# Start index of state and state derivative vectors
21+
T_startIndex::Int
22+
23+
InsulatedRodStruct{FloatType}(path::String) where {FloatType} = new(path,false)
24+
end
25+
26+
27+
# Called once before initialization of first simulation segment
28+
function initInsulatedRod2!(obj::InsulatedRodStruct{FloatType}; L, A, rho, lambda, c, T0, nT)::Nothing where {FloatType}
29+
#L, A, rho=7500.0u"kg/m^3", lambda=74.0u"W/(m*K)", c=450.0u"J/(kg*K)", T0=293.15u"K", nT=1)::Nothing where {FloatType}
30+
31+
# Convert to SI units, strip units and check that values are positives
32+
path = obj.path
33+
#=
34+
@show L
35+
@Modia.strippedPositive!(path, L)
36+
@Modia.strippedPositive!(path, A)
37+
@Modia.strippedPositive!(path, rho)
38+
@Modia.strippedPositive!(path, lambda)
39+
@Modia.strippedPositive!(path, c)
40+
@Modia.strippedPositive!(path, T0)
41+
@Modia.strippedPositive!(path, nT)
42+
@show L
43+
@show A
44+
@show rho
45+
@show lambda
46+
=#
47+
L = stripUnit(L)
48+
A = stripUnit(A)
49+
rho = stripUnit(rho)
50+
lambda = stripUnit(lambda)
51+
c = stripUnit(c)
52+
T0 = stripUnit(T0)
53+
54+
# Compute derived parameters
55+
dx = L/nT
56+
obj.Ge = lambda*A/dx
57+
obj.Ge2 = 2*obj.Ge
58+
obj.k = obj.Ge / ( c*rho*A*dx )
59+
obj.T_init = fill(T0, nT)
60+
61+
# Allocate and initialize internal states
62+
obj.T = copy(obj.T_init)
63+
obj.der_T = zeros(nT)
64+
return nothing
65+
end
66+
67+
68+
# Called from @instantiateModel(..) before getDerivatives!(..) is generated
69+
function buildInsulatedRod2!(model::AbstractDict, FloatType, TimeType, unitless::Bool, buildDict, path)
70+
pathAsString = Modia.modelPathAsString(path)
71+
extraModelCode = Model( # extra code to be merged with model
72+
insRod = Var(hideResult=true), # InsulatedRodStruct instance (an equation to return this variable is generated by _buildFunction)
73+
success = Var(hideResult=true), # Dummy return argument
74+
equations = :[
75+
# copy states into insRod.T and return insRod
76+
insRod = openInsulatedRod!(instantiatedModel, $pathAsString)
77+
78+
# equations at the boundaries
79+
port_a.Q_flow = getGe2(insRod, Val($unitless))*(port_a.T - getT1( insRod, Val($unitless)))
80+
port_b.Q_flow = getGe2(insRod, Val($unitless))*(port_b.T - getTend(insRod, Val($unitless)))
81+
82+
# compute insRod.der_T and copy it into state derivatives
83+
success = computeInsulatedRodDerivatives!(instantiatedModel, insRod, port_a.T, port_b.T) # instantiatedModel is provided in the generated code
84+
])
85+
86+
# Store build info in buildDict
87+
buildDict[pathAsString] = InsulatedRodStruct{FloatType}(pathAsString)
88+
return extraModelCode
89+
end
90+
91+
92+
# Called once before initialization of a new simulation segment
93+
function initInsulatedRod2ForNewSegment!(instantiatedModel::SimulationModel{FloatType,TimeType},
94+
parameters::AbstractDict, path::String; log=false)::Nothing where {FloatType,TimeType}
95+
obj::InsulatedRodStruct{FloatType} = instantiatedModel.buildDict[path]
96+
97+
if Modia.isFirstInitialOfAllSegments(instantiatedModel)
98+
initInsulatedRod2!(obj; parameters...)
99+
end
100+
101+
# Define a vector of new state and state derivative variables
102+
obj.T_startIndex = Modia.new_x_segmented_variable!(instantiatedModel, path*".T", path*".der(T)", obj.T, "K")
103+
return nothing
104+
end
105+
106+
107+
# Open an initialized InsulatedRod2 model and return a reference to it
108+
function openInsulatedRod!(instantiatedModel::SimulationModel{FloatType,TimeType}, path::String)::InsulatedRodStruct{FloatType} where {FloatType,TimeType}
109+
obj::InsulatedRodStruct{FloatType} = instantiatedModel.buildDict[path]
110+
Modia.get_Vector_x_segmented_value!(instantiatedModel, obj.T_startIndex, obj.T)
111+
return obj
112+
end
113+
114+
115+
# Functions to inquire values from InsulatedRodStruct
116+
getT1( insRod::InsulatedRodStruct, Unitless::Val{true}) = insRod.T[1]
117+
getT1( insRod::InsulatedRodStruct, Unitless::Val{false}) = insRod.T[1]*u"K"
118+
119+
getTend(insRod::InsulatedRodStruct, Unitless::Val{true}) = insRod.T[end]
120+
getTend(insRod::InsulatedRodStruct, Unitless::Val{false}) = insRod.T[end]*u"K"
121+
122+
getGe2( insRod::InsulatedRodStruct, Unitless::Val{true}) = insRod.Ge2
123+
getGe2( insRod::InsulatedRodStruct, Unitless::Val{false}) = insRod.Ge2*u"W/K"
124+
125+
T_grad1(T,Ta,i) = if i == 1 ; (Ta - T[1])*2 else T[i-1] - T[i] end
126+
T_grad2(T,Tb,i) = if i == length(T); (T[i] - Tb)*2 else T[i] - T[i+1] end
127+
128+
function computeInsulatedRodDerivatives!(instantiatedModel, obj::InsulatedRodStruct{FloatType}, Ta, Tb)::Bool where {FloatType}
129+
Ta::FloatType = stripUnit(Ta)
130+
Tb::FloatType = stripUnit(Tb)
131+
T = obj.T
132+
k = obj.k
133+
for i in 1:length(T)
134+
obj.der_T[i] = k*(T_grad1(T,Ta,i) - T_grad2(T,Tb,i))
135+
end
136+
Modia.add_der_x_segmented_value!(instantiatedModel, obj.T_startIndex, obj.der_T)
137+
return true
138+
end

src/CodeGeneration.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ mutable struct SimulationModel{FloatType,TimeType}
336336

337337
# Available after propagateEvaluateAndInstantiate!(..) called
338338
instantiateFunctions::Vector{Tuple{Union{Expr,Symbol},OrderedDict{Symbol,Any},String}}
339-
# All definitions `_instantiateFunction = Par(functionName = XXX)` in the model to call
339+
# All definitions `_initSegmentFunction = Par(functionName = XXX)` in the model to call
340340
# `XXX(instantiatedModel, submodel, submodelPath)` in the order occurring during evaluation
341341
# of the parameters where, instantiatedFunctions[i] = (XXX, submodel, submodelPath)
342342
nsegments::Int # Current simulation segment

src/EvaluateParameters.jl

+8-8
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ function propagateEvaluateAndInstantiate2!(m::SimulationModel{FloatType,TimeType
210210
end
211211
current = OrderedDict{Symbol,Any}() # should be Map()
212212

213-
# Determine, whether "parameters" has a ":_constructor" or "_instantiateFunction" key and handle this specially
213+
# Determine, whether "parameters" has a ":_constructor" or "_initSegmentFunction" key and handle this specially
214214
constructor = nothing
215215
instantiateFunction = nothing
216216
usePath = false
@@ -248,13 +248,13 @@ function propagateEvaluateAndInstantiate2!(m::SimulationModel{FloatType,TimeType
248248
end
249249
end
250250

251-
elseif haskey(parameters, :_instantiateFunction)
252-
# For example: obj = (_instantiateFunction = Par(functionName = :(instantiateLinearStateSpace!))
253-
_instantiateFunction = parameters[:_instantiateFunction]
254-
if haskey(_instantiateFunction, :functionName)
255-
instantiateFunction = _instantiateFunction[:functionName]
251+
elseif haskey(parameters, :_initSegmentFunction)
252+
# For example: obj = (_initSegmentFunction = Par(functionName = :(instantiateLinearStateSpace!))
253+
_initSegmentFunction = parameters[:_initSegmentFunction]
254+
if haskey(_initSegmentFunction, :functionName)
255+
instantiateFunction = _initSegmentFunction[:functionName]
256256
else
257-
@warn "Model $path has key :_instantiateFunction but its value has no key :functionName"
257+
@warn "Model $path has key :_initSegmentFunction but its value has no key :functionName"
258258
end
259259

260260
elseif haskey(parameters, :value)
@@ -269,7 +269,7 @@ function propagateEvaluateAndInstantiate2!(m::SimulationModel{FloatType,TimeType
269269
if log
270270
println(" 2: ... key = $k, value = $v")
271271
end
272-
if k == :_constructor || k == :_buildFunction || k == :_buildOption || k == :_instantiateFunction ||
272+
if k == :_constructor || k == :_buildFunction || k == :_buildOption || k == :_initSegmentFunction ||
273273
k == :_path || k == :_instantiatedModel || (k == :_class && !isnothing(constructor))
274274
if log
275275
println(" 3: ... key = $k")

src/EventHandler.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ mutable struct EventHandler{FloatType,TimeType}
7373
nz::Int # Number of event indicators
7474
nzInvariant::Int # Number of event indicators defined in visible model equations
7575
# More event indicators can be defined by objects that are not visible in the generated code, i.e. nz >= nzInvariant
76-
# These event indicators are defined in propagateEvaluateAndInstantiate!(..) via _instantiateFunction(..)
76+
# These event indicators are defined in propagateEvaluateAndInstantiate!(..) via _initSegmentFunction(..)
7777
z::Vector{FloatType} # Vector of event indicators (zero crossings). If one of z[i] passes
7878
# zero, that is beforeEvent(z[i])*z[i] < 0, an event is triggered
7979
# (allocated during instanciation according to nz).

src/Modia.jl

+31-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ Main module of Modia.
99
module Modia
1010

1111
const path = dirname(dirname(@__FILE__)) # Absolute path of package directory
12-
const Version = "0.9.4"
13-
const Date = "2023-05-21"
12+
const Version = "0.10.0"
13+
const Date = "2023-05-22"
1414
const modelsPath = joinpath(Modia.path, "models")
1515

1616
print(" \n\nWelcome to ")
@@ -170,6 +170,35 @@ The function is defined as: `stripUnit(v) = ustrip.(upreferred.(v))`.
170170
"""
171171
stripUnit(v) = ustrip.(upreferred.(v))
172172

173+
174+
"""
175+
@strippedPositive!(path, name)
176+
177+
Convert `name` to its preferred units (default are the SI units), strip units and check that value is positive.
178+
In case of error, use `string(name)` and `path` in the error message:
179+
180+
# Example
181+
```
182+
using Unitful
183+
L1 = 2.0u"m"
184+
@strippedPositive!("insulatedRod", L1)
185+
# L1 = 2.0
186+
187+
L2 = -2.0u"m"
188+
@strippedPositive!("insulatedRod", L2)
189+
# error message:
190+
# Error from
191+
# insulatedRod = ...(..., L2 = -2.0u"m",...): L2 > 0 required.)
192+
```
193+
"""
194+
macro strippedPositive!(path, name)
195+
nameAsString = string(name)
196+
expr = :( $name = strippedPositive!($path, $(esc(name)), $nameAsString) )
197+
return expr
198+
end
199+
strippedPositive!(path::String, value, name) = stripUnit(value) > 0 ? stripUnit(value) : error("\nError from\n $path = ...(..., $name = $value, ...): $name > 0 required")
200+
201+
173202
"""
174203
str = modelPathAsString(modelPath::Union{Expr,Symbol,Nothing})
175204

0 commit comments

Comments
 (0)