OMCompiler/Compiler/NBackEnd/Classes/NBPartition.mo
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of OpenModelica. | ||
| 3 | * | ||
| 4 | * Copyright (c) 1998-2026, Open Source Modelica Consortium (OSMC), | ||
| 5 | * c/o Linköpings universitet, Department of Computer and Information Science, | ||
| 6 | * SE-58183 Linköping, Sweden. | ||
| 7 | * | ||
| 8 | * All rights reserved. | ||
| 9 | * | ||
| 10 | * THIS PROGRAM IS PROVIDED UNDER THE TERMS OF AGPL VERSION 3 LICENSE OR | ||
| 11 | * THIS OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.8. | ||
| 12 | * ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES | ||
| 13 | * RECIPIENT'S ACCEPTANCE OF THE OSMC PUBLIC LICENSE OR THE GNU AGPL | ||
| 14 | * VERSION 3, ACCORDING TO RECIPIENTS CHOICE. | ||
| 15 | * | ||
| 16 | * The OpenModelica software and the OSMC (Open Source Modelica Consortium) | ||
| 17 | * Public License (OSMC-PL) are obtained from OSMC, either from the above | ||
| 18 | * address, from the URLs: | ||
| 19 | * http://www.openmodelica.org or | ||
| 20 | * https://github.com/OpenModelica/ or | ||
| 21 | * http://www.ida.liu.se/projects/OpenModelica, | ||
| 22 | * and in the OpenModelica distribution. | ||
| 23 | * | ||
| 24 | * GNU AGPL version 3 is obtained from: | ||
| 25 | * https://www.gnu.org/licenses/licenses.html#GPL | ||
| 26 | * | ||
| 27 | * This program is distributed WITHOUT ANY WARRANTY; without | ||
| 28 | * even the implied warranty of MERCHANTABILITY or FITNESS | ||
| 29 | * FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY SET FORTH | ||
| 30 | * IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF OSMC-PL. | ||
| 31 | * | ||
| 32 | * See the full OSMC Public License conditions for more details. | ||
| 33 | * | ||
| 34 | */ | ||
| 35 | |||
| 36 | encapsulated package NBPartition | ||
| 37 | "file: NBPartition.mo | ||
| 38 | package: NBPartition | ||
| 39 | description: This file contains the data-types used to process individual | ||
| 40 | partitions of equations. | ||
| 41 | " | ||
| 42 | |||
| 43 | public | ||
| 44 | import Adjacency = NBAdjacency; | ||
| 45 | import Matching = NBMatching; | ||
| 46 | import StrongComponent = NBStrongComponent; | ||
| 47 | |||
| 48 | protected | ||
| 49 | // NF imports | ||
| 50 | import Call = NFCall; | ||
| 51 | import ClockKind = NFClockKind; | ||
| 52 | import ComponentRef = NFComponentRef; | ||
| 53 | import Expression = NFExpression; | ||
| 54 | import Type = NFType; | ||
| 55 | import Variable = NFVariable; | ||
| 56 | |||
| 57 | // Backend Imports | ||
| 58 | import BackendDAE = NBackendDAE; | ||
| 59 | import BEquation = NBEquation; | ||
| 60 | import NBEquation.{Equation, EquationKind}; | ||
| 61 | import BJacobian = NBJacobian; | ||
| 62 | import NBEquation.EquationPointers; | ||
| 63 | import NBPartitioning.{BClock, ClockedInfo}; | ||
| 64 | import Jacobian = NBackendDAE.BackendDAE; | ||
| 65 | import BVariable = NBVariable; | ||
| 66 | import NBVariable.VariablePointers; | ||
| 67 | |||
| 68 | // Util imports | ||
| 69 | import DoubleEnded; | ||
| 70 | import StringUtil; | ||
| 71 | |||
| 72 | public | ||
| 73 | type Kind = enumeration(ODE, ALG, ODE_EVT, ALG_EVT, INI, INI_0, DAE, JAC, CLK); | ||
| 74 | |||
| 75 | uniontype Association | ||
| 76 | record CONTINUOUS | ||
| 77 | Kind kind; | ||
| 78 | Option<Jacobian> jacobian "Analytic jacobian for the integrator"; | ||
| 79 | Option<Jacobian> jacobianAdjoint "Analytic adjoint jacobian for the integrator"; | ||
| 80 | Option<Jacobian> LFG_jacobian "Analytic jacobian of Lagrange term (L), ODE (f), Path Constraints (g) for MOO"; | ||
| 81 | Option<Jacobian> MRF_jacobian "Analytic jacobian of Mayer term (Mf), Final Constraints (rf) for MOO"; | ||
| 82 | Option<Jacobian> R0_jacobian "Analytic jacobian of Initial Constraints (r0) for MOO"; | ||
| 83 | end CONTINUOUS; | ||
| 84 | |||
| 85 | record CLOCKED | ||
| 86 | BClock clock; | ||
| 87 | Option<BClock> baseClock; | ||
| 88 | UnorderedSet<BClock> clock_deps "dependencies of this clocked partition"; | ||
| 89 | Boolean holdEvents; | ||
| 90 | end CLOCKED; | ||
| 91 | |||
| 92 | function toStringShort | ||
| 93 | input Association association; | ||
| 94 | output String str; | ||
| 95 | algorithm | ||
| 96 | str := match association | ||
| 97 | 18 | case CONTINUOUS() then "Continuous " + Partition.kindToString(association.kind); | |
| 98 | case CLOCKED() then "Clocked"; | ||
| 99 | else "Unknown"; | ||
| 100 | end match; | ||
| 101 | end toStringShort; | ||
| 102 | |||
| 103 | function toString | ||
| 104 | input Association association; | ||
| 105 | output String str; | ||
| 106 | algorithm | ||
| 107 | str := match association | ||
| 108 | case CONTINUOUS() algorithm | ||
| 109 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | if isSome(association.jacobian) then |
| 110 | ✗ | str := BJacobian.toString(Util.getOption(association.jacobian), Partition.kindToString(association.kind)); | |
| 111 | ✗ | if (Flags.getConfigBool(Flags.MOO_DYNAMIC_OPTIMIZATION)) then | |
| 112 | ✗ | str := "\n" + str + BJacobian.toString(Util.getOption(association.LFG_jacobian), Partition.kindToString(association.kind)); | |
| 113 | ✗ | str := "\n" + str + BJacobian.toString(Util.getOption(association.MRF_jacobian), Partition.kindToString(association.kind)); | |
| 114 | ✗ | str := "\n" + str + BJacobian.toString(Util.getOption(association.R0_jacobian), Partition.kindToString(association.kind)); | |
| 115 | end if; | ||
| 116 | else | ||
| 117 | 3 | str := StringUtil.headline_1("No Jacobian"); | |
| 118 | end if; | ||
| 119 |
3/4✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 2 times.
|
3 | if isSome(association.jacobianAdjoint) then |
| 120 | 1 | str := BJacobian.toString(Util.getOption(association.jacobianAdjoint), Partition.kindToString(association.kind) + " Adjoint") + "\n"; | |
| 121 | end if; | ||
| 122 | then str; | ||
| 123 | case CLOCKED() algorithm | ||
| 124 | ✗ | str := BClock.toString(association.clock); | |
| 125 | ✗ | if isSome(association.baseClock) then | |
| 126 | ✗ | str := StringUtil.headline_1("Sub clock: " + str + " of base clock " + BClock.toString(Util.getOption(association.baseClock))); | |
| 127 | else | ||
| 128 | ✗ | str := StringUtil.headline_1("Base clock: " + str); | |
| 129 | end if; | ||
| 130 | then str; | ||
| 131 | else algorithm | ||
| 132 | ✗ | Error.addMessage(Error.INTERNAL_ERROR,{getInstanceName() + " failed. Unknown partition association in match."}); | |
| 133 | ✗ | then fail(); | |
| 134 | end match; | ||
| 135 | end toString; | ||
| 136 | |||
| 137 | function create | ||
| 138 | "create an association for a partition from the equation array and the clocked info | ||
| 139 | holdEvents is updated later for clocked associations" | ||
| 140 | input EquationPointers equations; | ||
| 141 | input Kind kind; | ||
| 142 | input ClockedInfo info; | ||
| 143 | input UnorderedSet<ComponentRef> infer_del; | ||
| 144 | output Association association; | ||
| 145 | protected | ||
| 146 | Pointer<Option<ClockTpl>> clock_ptr = Pointer.create(NONE()); | ||
| 147 | Pointer<Option<ComponentRef>> infer_ptr = Pointer.create(NONE()); | ||
| 148 | UnorderedSet<ClockTpl> failed_set = UnorderedSet.new(hashClockTpl, isEqualClockTpl); | ||
| 149 | UnorderedSet<BClock> clock_deps = UnorderedSet.new(BClock.hash, BClock.isEqual); | ||
| 150 | Option<ClockTpl> clock_tpl; | ||
| 151 | Option<ComponentRef> infer; | ||
| 152 | ComponentRef name, base_name; | ||
| 153 | BClock clock; | ||
| 154 | algorithm | ||
| 155 | 330 | EquationPointers.mapExp(equations, function expClocked( | |
| 156 | info = info, clock_ptr = clock_ptr, infer_ptr = infer_ptr, failed_set = failed_set, clock_deps = clock_deps, infer_del = infer_del), NONE(), Expression.fakeMap); | ||
| 157 | |||
| 158 | // get the clock tuple and the inferred reference | ||
| 159 | 330 | clock_tpl := Pointer.access(clock_ptr); | |
| 160 | 330 | infer := Pointer.access(infer_ptr); | |
| 161 | |||
| 162 |
3/4✗ Branch 0 not taken.
✓ Branch 1 taken 330 times.
✓ Branch 2 taken 311 times.
✓ Branch 3 taken 19 times.
|
330 | if isSome(clock_tpl) then |
| 163 | 19 | SOME((name, clock)) := clock_tpl; | |
| 164 | |||
| 165 | // throw an error if there are different clocks in this partition | ||
| 166 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
|
19 | if not UnorderedSet.isEmpty(failed_set) then |
| 167 | ✗ | Error.addMessage(Error.INTERNAL_ERROR,{getInstanceName() + " failed because there are non-identical clocks in the same partition:\n" | |
| 168 | + "### First clock found:\n" + clockTplString((name, clock)) + "\n### Conflicting clocks:\n" + UnorderedSet.toString(failed_set, clockTplString) + "."}); | ||
| 169 | ✗ | fail(); | |
| 170 | end if; | ||
| 171 | |||
| 172 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 2 taken 15 times.
|
19 | if BClock.isBaseClock(clock) then |
| 173 | // if the clock is still an inferred clock without reference, update it to the default base clock and add to the base clocks | ||
| 174 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
|
4 | if BClock.isInferredClock(clock) then |
| 175 | ✗ | if isNone(infer) then | |
| 176 | clock := NBPartitioning.DEFAULT_BASE_CLOCK; | ||
| 177 | ✗ | UnorderedMap.add(name, clock, info.baseClocks); | |
| 178 | else | ||
| 179 | ✗ | clock := BClock.INFERRED_CLOCK(Util.getOption(infer)); | |
| 180 | end if; | ||
| 181 | end if; | ||
| 182 | 4 | association := CLOCKED(clock, NONE(), clock_deps, false); | |
| 183 | else | ||
| 184 | 15 | base_name := UnorderedMap.getSafe(name, info.subToBase, sourceInfo()); | |
| 185 | 30 | association := CLOCKED(clock, SOME(UnorderedMap.getSafe(base_name, info.baseClocks, sourceInfo())), clock_deps, false); | |
| 186 | end if; | ||
| 187 | else | ||
| 188 | 311 | association := CONTINUOUS(kind, NONE(), NONE(), NONE(), NONE(), NONE()); | |
| 189 | end if; | ||
| 190 | end create; | ||
| 191 | |||
| 192 | function merge | ||
| 193 | input output Association ass1; | ||
| 194 | input Association ass2; | ||
| 195 | input Boolean strict; | ||
| 196 | algorithm | ||
| 197 | ass1 := match (ass1, ass2) | ||
| 198 | local | ||
| 199 | BackendDAE jac1, jac2; | ||
| 200 | |||
| 201 | // merging jacobians | ||
| 202 | case (CONTINUOUS(jacobian = SOME(jac1 as BackendDAE.JACOBIAN())), CONTINUOUS(jacobian = SOME(jac2))) guard(ass1.kind == ass2.kind or not strict) algorithm | ||
| 203 | ✗ | ass1.jacobian := SOME(BJacobian.combine({jac1, jac2}, jac1.name)); | |
| 204 | then ass1; | ||
| 205 | |||
| 206 | // no jacobians to merge | ||
| 207 | case (CONTINUOUS(), CONTINUOUS()) guard(ass1.kind == ass2.kind or not strict) then ass1; | ||
| 208 | |||
| 209 | // merging clocked partitions | ||
| 210 | case (CLOCKED(), CLOCKED()) guard(not strict or (BClock.isEqual(ass1.clock, ass2.clock) and Util.optionEqual(ass1.baseClock, ass2.baseClock, BClock.isEqual))) algorithm | ||
| 211 | 3 | ass1.clock_deps := UnorderedSet.union(ass1.clock_deps, ass2.clock_deps); | |
| 212 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
6 | ass1.holdEvents := ass1.holdEvents or ass2.holdEvents; |
| 213 | then ass1; | ||
| 214 | |||
| 215 | // unmergable | ||
| 216 | else algorithm | ||
| 217 | ✗ | Error.addMessage(Error.INTERNAL_ERROR,{getInstanceName() + " failed. Cannot merge\n" + toString(ass1) + " and\n" + toString(ass2) + "."}); | |
| 218 | ✗ | then fail(); | |
| 219 | end match; | ||
| 220 | end merge; | ||
| 221 | |||
| 222 | function isClocked | ||
| 223 | input Association association; | ||
| 224 | output Boolean b; | ||
| 225 | algorithm | ||
| 226 | b := match association case CLOCKED() then true; else false; end match; | ||
| 227 | end isClocked; | ||
| 228 | |||
| 229 | // clock tpl for collecting and comparing clocks in a partition | ||
| 230 | type ClockTpl = tuple<ComponentRef, BClock>; | ||
| 231 | |||
| 232 | function clockTplString | ||
| 233 | input ClockTpl tpl; | ||
| 234 | output String str = "(" + ComponentRef.toString(Util.tuple21(tpl)) + " = " + BClock.toString(Util.tuple22(tpl)) + ")"; | ||
| 235 | end clockTplString; | ||
| 236 | |||
| 237 | function hashClockTpl | ||
| 238 | input ClockTpl tpl; | ||
| 239 | output Integer hash; | ||
| 240 | algorithm | ||
| 241 | ✗ | hash := ComponentRef.hash(Util.tuple21(tpl)); | |
| 242 | ✗ | hash := stringHashDjb2Continue(BClock.toString(Util.tuple22(tpl)), hash); | |
| 243 | end hashClockTpl; | ||
| 244 | |||
| 245 | function isEqualClockTpl | ||
| 246 | input ClockTpl tpl1; | ||
| 247 | input ClockTpl tpl2; | ||
| 248 | output Boolean b = ComponentRef.isEqual(Util.tuple21(tpl1), Util.tuple21(tpl2)) and | ||
| 249 | BClock.isEqual(Util.tuple22(tpl1), Util.tuple22(tpl2)); | ||
| 250 | end isEqualClockTpl; | ||
| 251 | |||
| 252 | protected | ||
| 253 | function expClocked | ||
| 254 | "checks if an expression is a clock and collects it. Also finds all other clock dependencies" | ||
| 255 | input output Expression exp "the examined expression"; | ||
| 256 | input ClockedInfo info "contains all base- and sub-clocks"; | ||
| 257 | input Pointer<Option<ClockTpl>> clock_ptr "the first found clock"; | ||
| 258 | input Pointer<Option<ComponentRef>> infer_ptr "potential inferred clock dependency"; | ||
| 259 | input UnorderedSet<ClockTpl> failed_set "clocks that are not equal to the first found clock"; | ||
| 260 | input UnorderedSet<BClock> clock_deps "clock dependencies found in sub sampling functions"; | ||
| 261 | input UnorderedSet<ComponentRef> infer_del "inferred clocks that can be removed"; | ||
| 262 | algorithm | ||
| 263 | exp := match exp | ||
| 264 | local | ||
| 265 | Option<BClock> clock_opt; | ||
| 266 | ComponentRef arg; | ||
| 267 | |||
| 268 | // check if its a variable that defines a clock | ||
| 269 | case Expression.CREF() guard(BVariable.isClockOrClocked(BVariable.getVarPointer(exp.cref, sourceInfo()))) algorithm | ||
| 270 |
2/2✓ Branch 1 taken 6 times.
✓ Branch 2 taken 16 times.
|
22 | if UnorderedMap.contains(exp.cref, info.baseClocks) then |
| 271 | 6 | clock_opt := SOME(UnorderedMap.getSafe(exp.cref, info.baseClocks, sourceInfo())); | |
| 272 | elseif UnorderedMap.contains(exp.cref, info.subClocks) then | ||
| 273 | 16 | clock_opt := SOME( UnorderedMap.getSafe(exp.cref, info.subClocks, sourceInfo())); | |
| 274 | else | ||
| 275 | clock_opt := NONE(); | ||
| 276 | end if; | ||
| 277 | |||
| 278 | () := match (clock_opt, Pointer.access(clock_ptr)) | ||
| 279 | local | ||
| 280 | BClock new, old; | ||
| 281 | ComponentRef name; | ||
| 282 | |||
| 283 | // old sub clock and new base clock -> potentially update inferred base clock | ||
| 284 | case (SOME(BClock.BASE_CLOCK()), SOME((name, BClock.SUB_CLOCK()))) algorithm | ||
| 285 | ✗ | removeInferredClock(name, exp.cref, info, infer_del); | |
| 286 | then (); | ||
| 287 | |||
| 288 | // old base clock getting updated to new sub clock | ||
| 289 | case (SOME(new as BClock.SUB_CLOCK()), SOME((_, BClock.BASE_CLOCK()))) algorithm | ||
| 290 | ✗ | Pointer.update(clock_ptr, SOME((exp.cref, new))); | |
| 291 | then (); | ||
| 292 | |||
| 293 | // old base clock is inferred --> always take new clock | ||
| 294 | case (SOME(new), SOME((_, BClock.BASE_CLOCK(clock = ClockKind.INFERRED_CLOCK())))) algorithm | ||
| 295 | 4 | Pointer.update(clock_ptr, SOME((exp.cref, new))); | |
| 296 | then (); | ||
| 297 | |||
| 298 | // clocks -> equal: success / different: fail | ||
| 299 | case (SOME(new), SOME((_, old))) algorithm | ||
| 300 | // if the old clock is inferred just override it | ||
| 301 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
|
1 | if BClock.isInferredClock(old) then |
| 302 | ✗ | Pointer.update(clock_ptr, SOME((exp.cref, new))); | |
| 303 | elseif not (BClock.isInferredClock(new) or BClock.isEqual(new, old)) then | ||
| 304 | ✗ | UnorderedSet.add((exp.cref, new), failed_set); | |
| 305 | end if; | ||
| 306 | then (); | ||
| 307 | |||
| 308 | // new clock | ||
| 309 | case (SOME(new), NONE()) algorithm | ||
| 310 | 38 | Pointer.update(clock_ptr, SOME((exp.cref, new))); | |
| 311 | then (); | ||
| 312 | |||
| 313 | else (); | ||
| 314 | end match; | ||
| 315 | then exp; | ||
| 316 | |||
| 317 | // only look for clock dependencies on sample functions | ||
| 318 | case Expression.CALL(call = Call.TYPED_CALL(arguments = Expression.CREF(cref = arg) :: _)) guard(Expression.isClockOrSampleFunction(exp)) algorithm | ||
| 319 |
2/2✓ Branch 1 taken 6 times.
✓ Branch 2 taken 7 times.
|
13 | if UnorderedMap.contains(arg, info.subClocks) then |
| 320 | 6 | UnorderedSet.add(UnorderedMap.getSafe(arg, info.subClocks, sourceInfo()), clock_deps); | |
| 321 | 6 | Pointer.update(infer_ptr, SOME(arg)); | |
| 322 | end if; | ||
| 323 | then exp; | ||
| 324 | |||
| 325 | // go deeper on everything else | ||
| 326 | 21345 | else Expression.mapShallow(exp, function expClocked(info = info, clock_ptr = clock_ptr, infer_ptr = infer_ptr, failed_set = failed_set, clock_deps = clock_deps, infer_del = infer_del)); | |
| 327 | end match; | ||
| 328 | end expClocked; | ||
| 329 | |||
| 330 | protected | ||
| 331 | function removeInferredClock | ||
| 332 | input ComponentRef name; | ||
| 333 | input ComponentRef new_name; | ||
| 334 | input ClockedInfo info; | ||
| 335 | input UnorderedSet<ComponentRef> infer_del; | ||
| 336 | protected | ||
| 337 | BClock base; | ||
| 338 | ComponentRef base_name; | ||
| 339 | list<ComponentRef> sub_clock_names1, sub_clock_names2; | ||
| 340 | algorithm | ||
| 341 | ✗ | base_name := UnorderedMap.getSafe(name, info.subToBase, sourceInfo()); | |
| 342 | ✗ | base := UnorderedMap.getSafe(base_name, info.baseClocks, sourceInfo()); | |
| 343 | ✗ | if BClock.isInferredClock(base) then | |
| 344 | // update the sub clock (and all other related ones) to the correct base clock | ||
| 345 | ✗ | sub_clock_names1 := UnorderedMap.getSafe(base_name, info.baseToSub, sourceInfo()); | |
| 346 | ✗ | for s_name in sub_clock_names1 loop | |
| 347 | ✗ | UnorderedMap.add(s_name, new_name, info.subToBase); | |
| 348 | end for; | ||
| 349 | // add all the sub clocks to the correct base clock | ||
| 350 | ✗ | sub_clock_names2 := UnorderedMap.getOrDefault(new_name, info.baseToSub, {}); | |
| 351 | ✗ | UnorderedMap.add(new_name, listAppend(sub_clock_names1, sub_clock_names2), info.baseToSub); | |
| 352 | // add the old inferred clock to clocks that can be removed | ||
| 353 | ✗ | UnorderedSet.add(base_name, infer_del); | |
| 354 | end if; | ||
| 355 | end removeInferredClock; | ||
| 356 | end Association; | ||
| 357 | |||
| 358 | uniontype Partition | ||
| 359 | record PARTITION | ||
| 360 | Integer index "Partition index"; | ||
| 361 | Association association "Clocked/Continuous"; | ||
| 362 | VariablePointers unknowns "Variable array of unknowns, subset of full variable array"; | ||
| 363 | Option<VariablePointers> daeUnknowns "Variable array of unknowns in the case of dae mode"; | ||
| 364 | EquationPointers equations "Equations array, subset of the full equation array"; | ||
| 365 | Option<Adjacency.Matrix> adjacencyMatrix "Adjacency matrix with all additional information"; | ||
| 366 | Option<Matching> matching "Matching (see 2.5)"; | ||
| 367 | Option<array<StrongComponent>> strongComponents "Strong Components"; | ||
| 368 | end PARTITION; | ||
| 369 | |||
| 370 | function toString | ||
| 371 | input Partition partition; | ||
| 372 | input Integer level = 0; | ||
| 373 | output String str; | ||
| 374 | algorithm | ||
| 375 | 18 | str := StringUtil.headline_2("(" + intString(partition.index) + ") " + Association.toStringShort(partition.association) + " Partition") + "\n"; | |
| 376 | str := match partition.strongComponents | ||
| 377 | local | ||
| 378 | array<StrongComponent> comps; | ||
| 379 | |||
| 380 | case SOME(comps) algorithm | ||
| 381 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
69 | for i in 1:arrayLength(comps) loop |
| 382 | 53 | str := str + StrongComponent.toString(comps[i], i) + "\n"; | |
| 383 | end for; | ||
| 384 | then str; | ||
| 385 | |||
| 386 | else algorithm | ||
| 387 | 2 | str := str + VariablePointers.toString(partition.unknowns, "Unknown") + "\n" + EquationPointers.toString(partition.equations, "") + "\n"; | |
| 388 | then str; | ||
| 389 | end match; | ||
| 390 | |||
| 391 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
|
18 | if level == 1 or level == 3 then |
| 392 | ✗ | if isSome(partition.adjacencyMatrix) then | |
| 393 | ✗ | str := str + Adjacency.Matrix.toString(Util.getOption(partition.adjacencyMatrix)) + "\n"; | |
| 394 | end if; | ||
| 395 | |||
| 396 | ✗ | if isSome(partition.matching) then | |
| 397 | ✗ | str := str + Matching.toString(Util.getOption(partition.matching)) + "\n"; | |
| 398 | end if; | ||
| 399 | end if; | ||
| 400 | |||
| 401 |
2/2✓ Branch 0 taken 15 times.
✓ Branch 1 taken 3 times.
|
18 | if level == 2 then |
| 402 | 3 | str := str + Association.toString(partition.association) + "\n"; | |
| 403 | end if; | ||
| 404 | end toString; | ||
| 405 | |||
| 406 | function toStringList | ||
| 407 | input list<Partition> partitions; | ||
| 408 | input String header = ""; | ||
| 409 | output String str = ""; | ||
| 410 | algorithm | ||
| 411 |
2/2✓ Branch 0 taken 39 times.
✓ Branch 1 taken 15 times.
|
54 | if not listEmpty(partitions) then |
| 412 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
15 | if header <> "" then |
| 413 | 15 | str := StringUtil.headline_1(header) + "\n"; | |
| 414 | end if; | ||
| 415 |
2/2✓ Branch 0 taken 15 times.
✓ Branch 1 taken 15 times.
|
30 | for part in partitions loop |
| 416 | 15 | str := str + toString(part); | |
| 417 | end for; | ||
| 418 | end if; | ||
| 419 | end toStringList; | ||
| 420 | |||
| 421 | function sort | ||
| 422 | input output Partition partition; | ||
| 423 | algorithm | ||
| 424 | ✗ | partition.unknowns := VariablePointers.sort(partition.unknowns); | |
| 425 | partition.equations := EquationPointers.sort(partition.equations); | ||
| 426 | end sort; | ||
| 427 | |||
| 428 | function hasIndex | ||
| 429 | input Partition partition; | ||
| 430 | input Integer index; | ||
| 431 | output Boolean b = partition.index == index; | ||
| 432 | end hasIndex; | ||
| 433 | |||
| 434 | function isEmpty | ||
| 435 | "returns true if the partition is empty. | ||
| 436 | maybe check more than only equations?" | ||
| 437 | input Partition partition; | ||
| 438 | output Boolean b = EquationPointers.size(partition.equations) == 0 | ||
| 439 | or Util.applyOptionOrDefault(partition.strongComponents, isEmptyArr, false); | ||
| 440 | protected | ||
| 441 | function isEmptyArr = arrayEmpty; // FIXME MetaModelica bug with inlined functions? | ||
| 442 | end isEmpty; | ||
| 443 | |||
| 444 | function isODEorDAE | ||
| 445 | input Partition part; | ||
| 446 | output Boolean b; | ||
| 447 | algorithm | ||
| 448 | b := match part.association | ||
| 449 | local | ||
| 450 | Kind kind; | ||
| 451 |
4/4✓ Branch 0 taken 407 times.
✓ Branch 1 taken 82 times.
✓ Branch 2 taken 406 times.
✓ Branch 3 taken 1 time.
|
489 | case Association.CONTINUOUS(kind = kind) then kind == Kind.ODE or kind == Kind.ODE_EVT or kind == Kind.DAE; |
| 452 | else false; | ||
| 453 | end match; | ||
| 454 | end isODEorDAE; | ||
| 455 | |||
| 456 | function isClocked | ||
| 457 | input Partition part; | ||
| 458 | output Boolean b; | ||
| 459 | algorithm | ||
| 460 | b := match part.association | ||
| 461 | local | ||
| 462 | case Association.CLOCKED() then true; | ||
| 463 | else false; | ||
| 464 | end match; | ||
| 465 | end isClocked; | ||
| 466 | |||
| 467 | function categorize | ||
| 468 | input Partition partition; | ||
| 469 | input DoubleEnded.MutableList<Partition> ode; | ||
| 470 | input DoubleEnded.MutableList<Partition> alg; | ||
| 471 | input DoubleEnded.MutableList<Partition> ode_evt; | ||
| 472 | input DoubleEnded.MutableList<Partition> alg_evt; | ||
| 473 | input DoubleEnded.MutableList<Partition> clocked; | ||
| 474 | protected | ||
| 475 | Boolean algebraic, continuous; | ||
| 476 | Kind kind; | ||
| 477 | Association association; | ||
| 478 | function isAlgebraicContinuous | ||
| 479 | input Partition part; | ||
| 480 | output Boolean alg = true; | ||
| 481 | output Boolean con = true; | ||
| 482 | algorithm | ||
| 483 |
2/2✓ Branch 1 taken 2519 times.
✓ Branch 2 taken 310 times.
|
2829 | for var in VariablePointers.toList(part.unknowns) loop |
| 484 |
4/4✓ Branch 0 taken 2313 times.
✓ Branch 1 taken 206 times.
✓ Branch 3 taken 83 times.
✓ Branch 4 taken 2230 times.
|
2519 | alg := if alg then not BVariable.isStateDerivative(var) else false; |
| 485 |
4/4✓ Branch 0 taken 1631 times.
✓ Branch 1 taken 888 times.
✓ Branch 3 taken 75 times.
✓ Branch 4 taken 1556 times.
|
2519 | con := if con then not BVariable.isDiscrete(var) else false; |
| 486 | // stop searching if both | ||
| 487 |
2/2✓ Branch 0 taken 17 times.
✓ Branch 1 taken 2502 times.
|
2519 | if not (alg or con) then |
| 488 | break; | ||
| 489 | end if; | ||
| 490 | end for; | ||
| 491 | end isAlgebraicContinuous; | ||
| 492 | algorithm | ||
| 493 | 327 | (algebraic, continuous) := isAlgebraicContinuous(partition); | |
| 494 | kind := match (algebraic, continuous) | ||
| 495 | case (true, true) then Kind.ALG; | ||
| 496 | case (false, true) then Kind.ODE; | ||
| 497 | case (true, false) then Kind.ALG_EVT; | ||
| 498 | case (false, false) then Kind.ODE_EVT; | ||
| 499 | else fail(); | ||
| 500 | end match; | ||
| 501 | partition.association := match (kind, partition.association) | ||
| 502 | case (_, Association.CLOCKED()) algorithm | ||
| 503 | 18 | DoubleEnded.push_back(clocked, partition); | |
| 504 | then partition.association; | ||
| 505 | case (Kind.ALG, association as Association.CONTINUOUS()) algorithm | ||
| 506 | 168 | association.kind := kind; | |
| 507 | 168 | partition.association := association; | |
| 508 | 168 | DoubleEnded.push_back(alg, partition); | |
| 509 | then association; | ||
| 510 | case (Kind.ODE, association as Association.CONTINUOUS()) algorithm | ||
| 511 | 66 | association.kind := kind; | |
| 512 | 66 | partition.association := association; | |
| 513 | 66 | DoubleEnded.push_back(ode, partition); | |
| 514 | then association; | ||
| 515 | case (Kind.ALG_EVT, association as Association.CONTINUOUS()) algorithm | ||
| 516 | 58 | association.kind := kind; | |
| 517 | 58 | partition.association := association; | |
| 518 | 58 | DoubleEnded.push_back(alg_evt, partition); | |
| 519 | then association; | ||
| 520 | case (Kind.ODE_EVT, association as Association.CONTINUOUS()) algorithm | ||
| 521 | 17 | association.kind := kind; | |
| 522 | 17 | partition.association := association; | |
| 523 | 17 | DoubleEnded.push_back(ode_evt, partition); | |
| 524 | then association; | ||
| 525 | else fail(); | ||
| 526 | end match; | ||
| 527 | end categorize; | ||
| 528 | |||
| 529 | function setIndex | ||
| 530 | input output Partition part; | ||
| 531 | input Pointer<Integer> index; | ||
| 532 | protected | ||
| 533 | Integer clock_idx = Pointer.access(index); | ||
| 534 | algorithm | ||
| 535 | 329 | part.index := clock_idx; | |
| 536 |
2/2✓ Branch 1 taken 18 times.
✓ Branch 2 taken 311 times.
|
329 | if isClocked(part) then |
| 537 | 36 | part.equations := EquationPointers.map(part.equations, function Equation.setKind(kind = EquationKind.CLOCKED, clock_idx = SOME(clock_idx))); | |
| 538 | end if; | ||
| 539 | 329 | Pointer.update(index, clock_idx + 1); | |
| 540 | end setIndex; | ||
| 541 | |||
| 542 | function setKind | ||
| 543 | input output Partition part; | ||
| 544 | input Kind kind; | ||
| 545 | algorithm | ||
| 546 | part.association := match part.association | ||
| 547 | local | ||
| 548 | Association ass; | ||
| 549 | case ass as Association.CONTINUOUS() algorithm | ||
| 550 | 6 | ass.kind := kind; | |
| 551 | then ass; | ||
| 552 | else algorithm | ||
| 553 | ✗ | Error.addMessage(Error.INTERNAL_ERROR,{getInstanceName() + " failed. Cannot set kind for non-continuous partition:\n" + toString(part)}); | |
| 554 | ✗ | then fail(); | |
| 555 | end match; | ||
| 556 | end setKind; | ||
| 557 | |||
| 558 | function getJacobian | ||
| 559 | input Partition part; | ||
| 560 | output Option<Jacobian> jac; | ||
| 561 | algorithm | ||
| 562 | jac := match part.association | ||
| 563 | case CONTINUOUS(jacobian = jac) then jac; | ||
| 564 | else NONE(); | ||
| 565 | end match; | ||
| 566 | end getJacobian; | ||
| 567 | |||
| 568 | function getJacobianAdjoint | ||
| 569 | input Partition part; | ||
| 570 | output Option<Jacobian> jac; | ||
| 571 | algorithm | ||
| 572 | jac := match part.association | ||
| 573 | case CONTINUOUS(jacobianAdjoint = jac) then jac; | ||
| 574 | else NONE(); | ||
| 575 | end match; | ||
| 576 | end getJacobianAdjoint; | ||
| 577 | |||
| 578 | function getJacobianLfg | ||
| 579 | input Partition part; | ||
| 580 | output Option<Jacobian> jac; | ||
| 581 | algorithm | ||
| 582 | jac := match part.association | ||
| 583 | case CONTINUOUS(LFG_jacobian = jac) then jac; | ||
| 584 | else NONE(); | ||
| 585 | end match; | ||
| 586 | end getJacobianLfg; | ||
| 587 | |||
| 588 | function getJacobianMrf | ||
| 589 | input Partition part; | ||
| 590 | output Option<Jacobian> jac; | ||
| 591 | algorithm | ||
| 592 | jac := match part.association | ||
| 593 | case CONTINUOUS(MRF_jacobian = jac) then jac; | ||
| 594 | else NONE(); | ||
| 595 | end match; | ||
| 596 | end getJacobianMrf; | ||
| 597 | |||
| 598 | function getJacobianR0 | ||
| 599 | input Partition part; | ||
| 600 | output Option<Jacobian> jac; | ||
| 601 | algorithm | ||
| 602 | jac := match part.association | ||
| 603 | case CONTINUOUS(R0_jacobian = jac) then jac; | ||
| 604 | else NONE(); | ||
| 605 | end match; | ||
| 606 | end getJacobianR0; | ||
| 607 | |||
| 608 | function getKind | ||
| 609 | input Partition part; | ||
| 610 | output Kind kind; | ||
| 611 | algorithm | ||
| 612 | kind := match part.association | ||
| 613 | case Association.CONTINUOUS(kind = kind) then kind; | ||
| 614 | else Kind.CLK; | ||
| 615 | end match; | ||
| 616 | end getKind; | ||
| 617 | |||
| 618 | function getClocks | ||
| 619 | input Partition part; | ||
| 620 | output BClock clock; | ||
| 621 | output Option<BClock> baseClock; | ||
| 622 | output Boolean holdEvents; | ||
| 623 | algorithm | ||
| 624 | (clock, baseClock, holdEvents) := match part.association | ||
| 625 | case Association.CLOCKED(clock = clock, baseClock = baseClock, holdEvents = holdEvents) then (clock, baseClock, holdEvents); | ||
| 626 | else algorithm | ||
| 627 | ✗ | Error.addMessage(Error.INTERNAL_ERROR,{getInstanceName() + " failed. Cannot get clocks for continuous partition:\n" + toString(part)}); | |
| 628 | ✗ | then fail(); | |
| 629 | end match; | ||
| 630 | end getClocks; | ||
| 631 | |||
| 632 | function setClocks | ||
| 633 | input output Partition part; | ||
| 634 | input BClock clock; | ||
| 635 | input Option<BClock> baseClock; | ||
| 636 | algorithm | ||
| 637 | part := match part.association | ||
| 638 | local | ||
| 639 | Association association; | ||
| 640 | case association as Association.CLOCKED() algorithm | ||
| 641 | 19 | association.clock := clock; | |
| 642 | 19 | association.baseClock := baseClock; | |
| 643 | 19 | part.association := association; | |
| 644 | then part; | ||
| 645 | else algorithm | ||
| 646 | ✗ | Error.addMessage(Error.INTERNAL_ERROR,{getInstanceName() + " failed. Cannot set clocks for continuous partition:\n" + toString(part)}); | |
| 647 | ✗ | then fail(); | |
| 648 | end match; | ||
| 649 | end setClocks; | ||
| 650 | |||
| 651 | function getClockDependencies | ||
| 652 | input Partition part; | ||
| 653 | output UnorderedSet<BClock> clock_deps; | ||
| 654 | algorithm | ||
| 655 | clock_deps := match part.association | ||
| 656 | case Association.CLOCKED(clock_deps = clock_deps) then clock_deps; | ||
| 657 | else algorithm | ||
| 658 | ✗ | Error.addMessage(Error.INTERNAL_ERROR,{getInstanceName() + " failed. Cannot get clock dependencies for continuous partition:\n" + toString(part)}); | |
| 659 | ✗ | then fail(); | |
| 660 | end match; | ||
| 661 | end getClockDependencies; | ||
| 662 | |||
| 663 | function getLoopResiduals | ||
| 664 | input Partition part; | ||
| 665 | output list<Pointer<Variable>> residuals = {}; | ||
| 666 | algorithm | ||
| 667 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 483 times.
✓ Branch 2 taken 483 times.
✗ Branch 3 not taken.
|
483 | if isSome(part.strongComponents) then |
| 668 |
2/2✓ Branch 2 taken 5524 times.
✓ Branch 3 taken 483 times.
|
6490 | for comp in Util.getOption(part.strongComponents) loop |
| 669 | 5524 | residuals := listAppend(StrongComponent.getLoopResiduals(comp), residuals); | |
| 670 | end for; | ||
| 671 | end if; | ||
| 672 | end getLoopResiduals; | ||
| 673 | |||
| 674 | function mapEqn | ||
| 675 | input output Partition partition; | ||
| 676 | input MapFunc func; | ||
| 677 | partial function MapFunc | ||
| 678 | input output BEquation.Equation e; | ||
| 679 | end MapFunc; | ||
| 680 | algorithm | ||
| 681 | 508 | partition.equations := EquationPointers.map(partition.equations, func); | |
| 682 | end mapEqn; | ||
| 683 | |||
| 684 | function mapExp | ||
| 685 | input output Partition partition; | ||
| 686 | input MapFunc func; | ||
| 687 | partial function MapFunc | ||
| 688 | input output Expression e; | ||
| 689 | end MapFunc; | ||
| 690 | algorithm | ||
| 691 | 508 | partition.equations := EquationPointers.mapExp(partition.equations, func); | |
| 692 | end mapExp; | ||
| 693 | |||
| 694 | function mapStrongComponents | ||
| 695 | input output Partition partition; | ||
| 696 | input MapFunc func; | ||
| 697 | partial function MapFunc | ||
| 698 | input output StrongComponent comp; | ||
| 699 | end MapFunc; | ||
| 700 | protected | ||
| 701 | array<StrongComponent> comps; | ||
| 702 | algorithm | ||
| 703 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | if isSome(partition.strongComponents) then |
| 704 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
7 | SOME(comps) := partition.strongComponents; |
| 705 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
446 | for i in 1:arrayLength(comps) loop |
| 706 |
1/2✓ Branch 0 taken 439 times.
✗ Branch 1 not taken.
|
439 | comps[i] := func(comps[i]); |
| 707 | end for; | ||
| 708 | 7 | partition.strongComponents := SOME(comps); | |
| 709 | end if; | ||
| 710 | end mapStrongComponents; | ||
| 711 | |||
| 712 | function kindToString | ||
| 713 | input Kind kind; | ||
| 714 | output String str = ""; | ||
| 715 | algorithm | ||
| 716 | str := match kind | ||
| 717 | case Kind.ODE then "ODE"; | ||
| 718 | case Kind.ALG then "ALG"; | ||
| 719 | case Kind.ODE_EVT then "ODE_EVT"; | ||
| 720 | case Kind.ALG_EVT then "ALG_EVT"; | ||
| 721 | case Kind.INI then "INI"; | ||
| 722 | case Kind.INI_0 then "INI_0"; | ||
| 723 | case Kind.DAE then "DAE"; | ||
| 724 | case Kind.JAC then "JAC"; | ||
| 725 | case Kind.CLK then "CLK"; | ||
| 726 | else algorithm | ||
| 727 | ✗ | Error.addMessage(Error.INTERNAL_ERROR,{getInstanceName() + " failed. Unknown partition kind in match."}); | |
| 728 | ✗ | then fail(); | |
| 729 | end match; | ||
| 730 | end kindToString; | ||
| 731 | |||
| 732 | function kindToInteger | ||
| 733 | input Kind kind; | ||
| 734 | output Integer i; | ||
| 735 | algorithm | ||
| 736 | i := match kind | ||
| 737 | case Kind.ODE then 0; | ||
| 738 | case Kind.ALG then 1; | ||
| 739 | case Kind.ODE_EVT then 2; | ||
| 740 | case Kind.ALG_EVT then 3; | ||
| 741 | case Kind.INI then 4; | ||
| 742 | case Kind.INI_0 then 5; | ||
| 743 | case Kind.DAE then 6; | ||
| 744 | case Kind.JAC then 7; | ||
| 745 | case Kind.CLK then 8; | ||
| 746 | else algorithm | ||
| 747 | ✗ | Error.addMessage(Error.INTERNAL_ERROR,{getInstanceName() + " failed. Unknown partition kind in match."}); | |
| 748 | ✗ | then fail(); | |
| 749 | end match; | ||
| 750 | end kindToInteger; | ||
| 751 | |||
| 752 | function clone | ||
| 753 | "only clones equations." | ||
| 754 | input output Partition par; | ||
| 755 | input Boolean shallow = true; | ||
| 756 | algorithm | ||
| 757 | 6 | par.equations := EquationPointers.clone(par.equations, shallow); | |
| 758 | // these are partially pointer based and have to be recomputed if not shallow | ||
| 759 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if not shallow then |
| 760 | 6 | par.adjacencyMatrix := NONE(); | |
| 761 | par.matching := NONE(); | ||
| 762 | par.strongComponents := NONE(); | ||
| 763 | par.association := match par.association | ||
| 764 | local | ||
| 765 | Association association; | ||
| 766 | case association as Association.CONTINUOUS() algorithm | ||
| 767 | 6 | association.jacobian := NONE(); | |
| 768 | then association; | ||
| 769 | else par.association; | ||
| 770 | end match; | ||
| 771 | end if; | ||
| 772 | end clone; | ||
| 773 | |||
| 774 | function removeAlias | ||
| 775 | "removes alias strong components and replaces it with their original strong components. | ||
| 776 | used before differentiating for jacobians." | ||
| 777 | input output Partition par; | ||
| 778 | protected | ||
| 779 | array<StrongComponent> comps; | ||
| 780 | algorithm | ||
| 781 | ✗ | if isSome(par.strongComponents) then | |
| 782 | // no need to override comps afterwards since arrays are mutable | ||
| 783 | ✗ | comps := Util.getOption(par.strongComponents); | |
| 784 | ✗ | for i in 1:arrayLength(comps) loop | |
| 785 | ✗ | comps[i] := StrongComponent.removeAlias(comps[i]); | |
| 786 | end for; | ||
| 787 | end if; | ||
| 788 | end removeAlias; | ||
| 789 | |||
| 790 | function updateHeldVars | ||
| 791 | input output Partition par; | ||
| 792 | input UnorderedSet<ComponentRef> held_crefs; | ||
| 793 | algorithm | ||
| 794 | par.association := match par.association | ||
| 795 | local | ||
| 796 | Association association; | ||
| 797 | case association as Association.CLOCKED() algorithm | ||
| 798 |
1/2✓ Branch 2 taken 19 times.
✗ Branch 3 not taken.
|
38 | association.holdEvents := not UnorderedSet.isDisjoint(held_crefs, UnorderedMap.keySet(par.unknowns.map)); |
| 799 | then association; | ||
| 800 | else par.association; | ||
| 801 | end match; | ||
| 802 | end updateHeldVars; | ||
| 803 | |||
| 804 | function merge | ||
| 805 | input output Partition part1; | ||
| 806 | input Partition part2; | ||
| 807 | input Boolean strict; | ||
| 808 | algorithm | ||
| 809 |
4/8✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
|
3 | if isSome(part1.daeUnknowns) or isSome(part2.daeUnknowns) then |
| 810 | ✗ | Error.addMessage(Error.INTERNAL_ERROR,{getInstanceName() + " failed. Cannot merge DAE-Mode partitions."}); fail(); | |
| 811 | elseif isSome(part1.strongComponents) or isSome(part2.strongComponents) then | ||
| 812 | ✗ | Error.addMessage(Error.INTERNAL_ERROR,{getInstanceName() + " failed. Should not merge sorted partitions."}); fail(); | |
| 813 | elseif isSome(part1.matching) or isSome(part2.matching) then | ||
| 814 | ✗ | Error.addMessage(Error.INTERNAL_ERROR,{getInstanceName() + " failed. Should not merge matched partitions."}); fail(); | |
| 815 | elseif isSome(part1.adjacencyMatrix) or isSome(part2.adjacencyMatrix) then | ||
| 816 | ✗ | Error.addMessage(Error.INTERNAL_ERROR,{getInstanceName() + " failed. Should not merge partitions with adjacency matrix."}); fail(); | |
| 817 | end if; | ||
| 818 | |||
| 819 | // index irrelevant, should be updated after use | ||
| 820 | 3 | part1.association := Association.merge(part1.association, part2.association, strict); | |
| 821 | part1.unknowns := VariablePointers.addList(VariablePointers.toList(part2.unknowns), part1.unknowns); | ||
| 822 | part1.equations := EquationPointers.addList(EquationPointers.toList(part2.equations), part1.equations); | ||
| 823 | end merge; | ||
| 824 | end Partition; | ||
| 825 | |||
| 826 | |||
| 827 | function kindIsInitial | ||
| 828 | input Kind kind; | ||
| 829 | output Boolean b = kind == Kind.INI or kind == Kind.INI_0; | ||
| 830 | end kindIsInitial; | ||
| 831 | |||
| 832 | annotation(__OpenModelica_Interface="nbackend"); | ||
| 833 | end NBPartition; | ||
| 834 |