rumoca 0.7.28

Modelica compiler written in RUST
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
//! Tests for the flatten module.

use super::*;
use crate::modelica_grammar::ModelicaGrammar;
use crate::modelica_parser::parse;

fn parse_test_code(code: &str) -> ir::ast::StoredDefinition {
    let mut grammar = ModelicaGrammar::new();
    parse(code, "test.mo", &mut grammar).expect("Failed to parse test code");
    grammar.modelica.expect("No AST produced")
}

#[test]
fn test_replaceable_package_simple() {
    // Test that Medium.nXi resolves when Medium is a replaceable package
    let code = r#"
package MyMedia
    partial package PartialMedium
        constant Integer nXi = 2;
    end PartialMedium;
end MyMedia;

model TestModel
    replaceable package Medium = MyMedia.PartialMedium;
    Real x[Medium.nXi];
equation
    x = {1, 2};
end TestModel;
"#;
    let ast = parse_test_code(code);
    let result = flatten(&ast, Some("TestModel"));
    assert!(
        result.is_ok(),
        "Should flatten successfully: {:?}",
        result.err()
    );

    let flat = result.unwrap();
    // x should be flattened as an array with size 2
    assert!(flat.components.contains_key("x"), "Should have component x");
}

#[test]
fn test_replaceable_package_parent_scope_type() {
    // Test that Medium.MassFraction resolves when MassFraction is defined
    // in the parent package of PartialMedium, not in PartialMedium itself
    let code = r#"
package Media
    package Interfaces
        type MassFraction = Real(min=0, max=1);
        type AbsolutePressure = Real(min=0);

        partial package PartialMedium
            constant Integer nXi = 2;
        end PartialMedium;
    end Interfaces;
end Media;

model TankWithMedium
    replaceable package Medium = Media.Interfaces.PartialMedium;

    Medium.MassFraction Xi[Medium.nXi];
    Medium.AbsolutePressure p;
equation
    Xi = {0.5, 0.5};
    p = 101325;
end TankWithMedium;
"#;
    let ast = parse_test_code(code);
    let result = flatten(&ast, Some("TankWithMedium"));
    assert!(
        result.is_ok(),
        "Should flatten model with parent scope types: {:?}",
        result.err()
    );

    let flat = result.unwrap();
    assert!(
        flat.components.contains_key("Xi"),
        "Should have component Xi"
    );
    assert!(flat.components.contains_key("p"), "Should have component p");
}

#[test]
fn test_replaceable_package_deeply_nested() {
    // Test deeply nested package structure similar to MSL
    let code = r#"
package Media
    package Interfaces
        type MassFraction = Real(min=0, max=1);
        type AbsolutePressure = Real(min=0);
        type Temperature = Real(min=0);

        partial package PartialMedium
            constant Integer nXi = 2;
        end PartialMedium;
    end Interfaces;
end Media;

package Fluid
    package Examples
        package BatchPlant
            package BaseClasses
                model TankWithMedium
                    replaceable package Medium = Media.Interfaces.PartialMedium;

                    Medium.MassFraction Xi[Medium.nXi];
                    Medium.AbsolutePressure p;
                    Medium.Temperature T;
                equation
                    Xi = {0.5, 0.5};
                    p = 101325;
                    T = 300;
                end TankWithMedium;
            end BaseClasses;
        end BatchPlant;
    end Examples;
end Fluid;
"#;
    let ast = parse_test_code(code);
    let result = flatten(
        &ast,
        Some("Fluid.Examples.BatchPlant.BaseClasses.TankWithMedium"),
    );
    assert!(
        result.is_ok(),
        "Should flatten deeply nested model: {:?}",
        result.err()
    );

    let flat = result.unwrap();
    assert!(
        flat.components.contains_key("Xi"),
        "Should have component Xi"
    );
    assert!(flat.components.contains_key("p"), "Should have component p");
    assert!(flat.components.contains_key("T"), "Should have component T");
}

#[test]
fn test_replaceable_package_with_extends() {
    // Test that Medium.AbsolutePressure resolves when AbsolutePressure is inherited
    // via extends in PartialMedium (like MSL where PartialMedium extends Types)
    let code = r#"
package Modelica
    package Media
        package Interfaces
            package Types
                type AbsolutePressure = Real(min=0);
                type MassFraction = Real(min=0, max=1);
                type Temperature = Real(min=0);
            end Types;

            partial package PartialMedium
                extends Modelica.Media.Interfaces.Types;
                constant Integer nXi = 2;
            end PartialMedium;
        end Interfaces;
    end Media;

    package Fluid
        model Pump
            replaceable package Medium = Modelica.Media.Interfaces.PartialMedium;

            Medium.AbsolutePressure p;
            Medium.MassFraction Xi[Medium.nXi];
            Medium.Temperature T;
        equation
            p = 101325;
            Xi = {0.5, 0.5};
            T = 300;
        end Pump;
    end Fluid;
end Modelica;
"#;
    let ast = parse_test_code(code);
    let result = flatten(&ast, Some("Modelica.Fluid.Pump"));
    assert!(
        result.is_ok(),
        "Should flatten model with inherited types: {:?}",
        result.err()
    );

    let flat = result.unwrap();
    assert!(flat.components.contains_key("p"), "Should have component p");
    assert!(
        flat.components.contains_key("Xi"),
        "Should have component Xi"
    );
    assert!(flat.components.contains_key("T"), "Should have component T");
}

#[test]
fn test_replaceable_package_inherited_through_extends() {
    // Test that Medium.AbsolutePressure resolves when Medium is defined
    // in a parent class via extends (like MSL Fluid components)
    // FluidPort -> PartialTwoPort -> ActualComponent
    let code = r#"
package Modelica
    package Media
        package Interfaces
            package Types
                type AbsolutePressure = Real(min=0);
                type MassFlowRate = Real;
            end Types;

            partial package PartialMedium
                extends Modelica.Media.Interfaces.Types;
            end PartialMedium;
        end Interfaces;
    end Media;

    package Fluid
        package Interfaces
            connector FluidPort
                replaceable package Medium = Modelica.Media.Interfaces.PartialMedium;
                flow Medium.MassFlowRate m_flow;
                Medium.AbsolutePressure p;
            end FluidPort;

            partial model PartialTwoPort
                replaceable package Medium = Modelica.Media.Interfaces.PartialMedium;
                FluidPort port_a(redeclare package Medium = Medium);
            end PartialTwoPort;
        end Interfaces;

        package Fittings
            model Valve
                extends Modelica.Fluid.Interfaces.PartialTwoPort;
                Medium.AbsolutePressure dp;
            equation
                dp = 1000;
            end Valve;
        end Fittings;
    end Fluid;
end Modelica;
"#;
    let ast = parse_test_code(code);
    let result = flatten(&ast, Some("Modelica.Fluid.Fittings.Valve"));
    assert!(
        result.is_ok(),
        "Should flatten model with inherited Medium package: {:?}",
        result.err()
    );

    let flat = result.unwrap();
    assert!(
        flat.components.contains_key("dp"),
        "Should have component dp"
    );
}

#[test]
fn test_model_alias() {
    // Test that model aliases like `replaceable model FlowModel = SomeModel` work
    let code = r#"
model BaseFlowModel
    Real dp;
equation
    dp = 100;
end BaseFlowModel;

model Pipe
    replaceable model FlowModel = BaseFlowModel;
    FlowModel flowModel;
end Pipe;
"#;
    let ast = parse_test_code(code);
    let result = flatten(&ast, Some("Pipe"));
    assert!(
        result.is_ok(),
        "Should flatten model with model alias: {:?}",
        result.err()
    );

    let flat = result.unwrap();
    assert!(
        flat.components.contains_key("flowModel.dp"),
        "Should have component flowModel.dp from expanded FlowModel"
    );
}

#[test]
fn test_numbered_package_aliases() {
    // Test numbered package aliases like Medium1, Medium2
    let code = r#"
package Media
    package Interfaces
        type SpecificHeatCapacity = Real;
        type Temperature = Real;
        partial package PartialMedium
            constant Integer n = 1;
        end PartialMedium;
    end Interfaces;
end Media;

model MixtureTest
    package Medium1 = Media.Interfaces.PartialMedium;
    package Medium2 = Media.Interfaces.PartialMedium;
    Medium1.SpecificHeatCapacity cp1;
    Medium2.Temperature T2;
equation
    cp1 = 1000;
    T2 = 300;
end MixtureTest;
"#;
    let ast = parse_test_code(code);
    let result = flatten(&ast, Some("MixtureTest"));
    assert!(
        result.is_ok(),
        "Should flatten model with numbered package aliases: {:?}",
        result.err()
    );

    let flat = result.unwrap();
    assert!(
        flat.components.contains_key("cp1"),
        "Should have component cp1"
    );
    assert!(
        flat.components.contains_key("T2"),
        "Should have component T2"
    );
}

#[test]
fn test_non_replaceable_package_alias() {
    // Test that non-replaceable package aliases work the same as replaceable ones
    let code = r#"
package MyMedia
    type MolarMass = Real(min=0);
    partial package PartialMedium
        constant Integer nX = 2;
    end PartialMedium;
end MyMedia;

model TestModel
    package Medium = MyMedia.PartialMedium;  // Note: no 'replaceable' keyword
    Medium.MolarMass MM;
    Real x[Medium.nX];
equation
    MM = 0.018;
    x = {1, 2};
end TestModel;
"#;
    let ast = parse_test_code(code);
    let result = flatten(&ast, Some("TestModel"));
    assert!(
        result.is_ok(),
        "Should flatten model with non-replaceable package alias: {:?}",
        result.err()
    );

    let flat = result.unwrap();
    assert!(
        flat.components.contains_key("MM"),
        "Should have component MM"
    );
    assert!(flat.components.contains_key("x"), "Should have component x");
}

#[test]
fn test_standalone_type_from_parent_scope() {
    // Test that types can be resolved from parent package scope when
    // a class extends another class from a different package.
    // This is the pattern used in MoistAir.BaseProperties -> MassFraction
    let code = r#"
package Modelica
    package Media
        package Interfaces
            type MassFraction = Real(min=0, max=1);
            type AbsolutePressure = Real(min=0);

            partial package PartialMedium
                constant Integer nX = 2;
            end PartialMedium;
        end Interfaces;

        package Air
            package MoistAir
                extends Modelica.Media.Interfaces.PartialMedium;

                model BaseProperties
                    // MassFraction should resolve to Modelica.Media.Interfaces.MassFraction
                    // because MoistAir extends from PartialMedium which is in Interfaces
                    MassFraction x_water;
                    AbsolutePressure p;
                equation
                    x_water = 0.5;
                    p = 101325;
                end BaseProperties;
            end MoistAir;
        end Air;
    end Media;
end Modelica;
"#;
    let ast = parse_test_code(code);
    let result = flatten(&ast, Some("Modelica.Media.Air.MoistAir.BaseProperties"));
    assert!(
        result.is_ok(),
        "Should flatten model with standalone types from parent scope: {:?}",
        result.err()
    );

    let flat = result.unwrap();
    assert!(
        flat.components.contains_key("x_water"),
        "Should have component x_water"
    );
    assert!(flat.components.contains_key("p"), "Should have component p");
}

#[test]
fn test_standalone_type_inside_parent_class() {
    // Test the actual MSL pattern where MassFraction is defined INSIDE PartialMedium,
    // not in the enclosing Interfaces package.
    // MoistAir extends PartialCondensingGases extends PartialMixtureMedium extends PartialMedium
    // MassFraction is defined in PartialMedium
    // BaseProperties in MoistAir should be able to use MassFraction
    let code = r#"
package Modelica
    package Media
        package Interfaces
            partial package PartialMedium
                type MassFraction = Real(min=0, max=1);
                type DynamicViscosity = Real(min=0);
                constant Integer nX = 2;
            end PartialMedium;

            partial package PartialMixtureMedium
                extends PartialMedium;
            end PartialMixtureMedium;

            partial package PartialCondensingGases
                extends PartialMixtureMedium;
            end PartialCondensingGases;
        end Interfaces;

        package Air
            package MoistAir
                extends Modelica.Media.Interfaces.PartialCondensingGases;

                model BaseProperties
                    // MassFraction should be found in PartialMedium through the extends chain
                    MassFraction x_water;
                    DynamicViscosity eta;
                equation
                    x_water = 0.5;
                    eta = 0.001;
                end BaseProperties;
            end MoistAir;
        end Air;
    end Media;
end Modelica;
"#;
    let ast = parse_test_code(code);
    let result = flatten(&ast, Some("Modelica.Media.Air.MoistAir.BaseProperties"));
    assert!(
        result.is_ok(),
        "Should flatten model with types from extends chain: {:?}",
        result.err()
    );

    let flat = result.unwrap();
    assert!(
        flat.components.contains_key("x_water"),
        "Should have component x_water"
    );
    assert!(
        flat.components.contains_key("eta"),
        "Should have component eta"
    );
}