Skip to main content

powerio_dist/dss/
prop.rs

1//! OpenDSS class and property name tables, in definition order.
2//!
3//! Names and order come from each class's DefineProperties in the OpenDSS
4//! source (epri-dev/OpenDSS-C): the order fixes both positional property
5//! assignment and abbreviation resolution. Lookup is case insensitive, exact
6//! match first, then the first name in definition order with the query as a
7//! prefix (THashList::FindAbbrev). Every class list ends with the inherited
8//! properties: PD elements add normamps..repair, PC elements add spectrum,
9//! and every circuit element adds basefreq, enabled, like.
10
11/// One OpenDSS object class: canonical name plus ordered property names.
12pub struct DssClass {
13    pub name: &'static str,
14    pub props: &'static [&'static str],
15}
16
17macro_rules! class {
18    ($ident:ident, $name:literal, [$($p:literal),* $(,)?]) => {
19        pub static $ident: DssClass = DssClass { name: $name, props: &[$($p),*] };
20    };
21}
22
23class!(
24    LINE,
25    "line",
26    [
27        "bus1",
28        "bus2",
29        "linecode",
30        "length",
31        "phases",
32        "r1",
33        "x1",
34        "r0",
35        "x0",
36        "c1",
37        "c0",
38        "rmatrix",
39        "xmatrix",
40        "cmatrix",
41        "switch",
42        "rg",
43        "xg",
44        "rho",
45        "geometry",
46        "units",
47        "spacing",
48        "wires",
49        "earthmodel",
50        "cncables",
51        "tscables",
52        "b1",
53        "b0",
54        "seasons",
55        "ratings",
56        "linetype",
57        // inherited
58        "normamps",
59        "emergamps",
60        "faultrate",
61        "pctperm",
62        "repair",
63        "basefreq",
64        "enabled",
65        "like",
66    ]
67);
68
69class!(
70    LINECODE,
71    "linecode",
72    [
73        "nphases",
74        "r1",
75        "x1",
76        "r0",
77        "x0",
78        "c1",
79        "c0",
80        "units",
81        "rmatrix",
82        "xmatrix",
83        "cmatrix",
84        "basefreq",
85        "normamps",
86        "emergamps",
87        "faultrate",
88        "pctperm",
89        "repair",
90        "kron",
91        "rg",
92        "xg",
93        "rho",
94        "neutral",
95        "b1",
96        "b0",
97        "seasons",
98        "ratings",
99        "linetype",
100        // inherited
101        "like",
102    ]
103);
104
105class!(
106    LOAD,
107    "load",
108    [
109        "phases",
110        "bus1",
111        "kv",
112        "kw",
113        "pf",
114        "model",
115        "yearly",
116        "daily",
117        "duty",
118        "growth",
119        "conn",
120        "kvar",
121        "rneut",
122        "xneut",
123        "status",
124        "class",
125        "vminpu",
126        "vmaxpu",
127        "vminnorm",
128        "vminemerg",
129        "xfkva",
130        "allocationfactor",
131        "kva",
132        "%mean",
133        "%stddev",
134        "cvrwatts",
135        "cvrvars",
136        "kwh",
137        "kwhdays",
138        "cfactor",
139        "cvrcurve",
140        "numcust",
141        "zipv",
142        "%seriesrl",
143        "relweight",
144        "vlowpu",
145        "puxharm",
146        "xrharm",
147        // inherited
148        "spectrum",
149        "basefreq",
150        "enabled",
151        "like",
152    ]
153);
154
155class!(
156    TRANSFORMER,
157    "transformer",
158    [
159        "phases",
160        "windings",
161        "wdg",
162        "bus",
163        "conn",
164        "kv",
165        "kva",
166        "tap",
167        "%r",
168        "rneut",
169        "xneut",
170        "buses",
171        "conns",
172        "kvs",
173        "kvas",
174        "taps",
175        "xhl",
176        "xht",
177        "xlt",
178        "xscarray",
179        "thermal",
180        "n",
181        "m",
182        "flrise",
183        "hsrise",
184        "%loadloss",
185        "%noloadloss",
186        "normhkva",
187        "emerghkva",
188        "sub",
189        "maxtap",
190        "mintap",
191        "numtaps",
192        "subname",
193        "%imag",
194        "ppm_antifloat",
195        "%rs",
196        "bank",
197        "xfmrcode",
198        "xrconst",
199        "x12",
200        "x13",
201        "x23",
202        "leadlag",
203        "wdgcurrents",
204        "core",
205        "rdcohms",
206        "seasons",
207        "ratings",
208        // inherited
209        "normamps",
210        "emergamps",
211        "faultrate",
212        "pctperm",
213        "repair",
214        "basefreq",
215        "enabled",
216        "like",
217    ]
218);
219
220class!(
221    VSOURCE,
222    "vsource",
223    [
224        "bus1",
225        "basekv",
226        "pu",
227        "angle",
228        "frequency",
229        "phases",
230        "mvasc3",
231        "mvasc1",
232        "x1r1",
233        "x0r0",
234        "isc3",
235        "isc1",
236        "r1",
237        "x1",
238        "r0",
239        "x0",
240        "scantype",
241        "sequence",
242        "bus2",
243        "z1",
244        "z0",
245        "z2",
246        "puz1",
247        "puz0",
248        "puz2",
249        "basemva",
250        "yearly",
251        "daily",
252        "duty",
253        "model",
254        "puzideal",
255        // inherited
256        "spectrum",
257        "basefreq",
258        "enabled",
259        "like",
260    ]
261);
262
263class!(
264    CAPACITOR,
265    "capacitor",
266    [
267        "bus1",
268        "bus2",
269        "phases",
270        "kvar",
271        "kv",
272        "conn",
273        "cmatrix",
274        "cuf",
275        "r",
276        "xl",
277        "harm",
278        "numsteps",
279        "states",
280        // inherited
281        "normamps",
282        "emergamps",
283        "faultrate",
284        "pctperm",
285        "repair",
286        "basefreq",
287        "enabled",
288        "like",
289    ]
290);
291
292class!(
293    REACTOR,
294    "reactor",
295    [
296        "bus1",
297        "bus2",
298        "phases",
299        "kvar",
300        "kv",
301        "conn",
302        "rmatrix",
303        "xmatrix",
304        "parallel",
305        "r",
306        "x",
307        "rp",
308        "z1",
309        "z2",
310        "z0",
311        "z",
312        "rcurve",
313        "lcurve",
314        "lmh",
315        // inherited
316        "normamps",
317        "emergamps",
318        "faultrate",
319        "pctperm",
320        "repair",
321        "basefreq",
322        "enabled",
323        "like",
324    ]
325);
326
327class!(
328    GENERATOR,
329    "generator",
330    [
331        "phases",
332        "bus1",
333        "kv",
334        "kw",
335        "pf",
336        "kvar",
337        "model",
338        "vminpu",
339        "vmaxpu",
340        "yearly",
341        "daily",
342        "duty",
343        "dispmode",
344        "dispvalue",
345        "conn",
346        "rneut",
347        "xneut",
348        "status",
349        "class",
350        "vpu",
351        "maxkvar",
352        "minkvar",
353        "pvfactor",
354        "forceon",
355        "kva",
356        "mva",
357        "xd",
358        "xdp",
359        "xdpp",
360        "h",
361        "d",
362        "usermodel",
363        "userdata",
364        "shaftmodel",
365        "shaftdata",
366        "dutystart",
367        "debugtrace",
368        "balanced",
369        "xrdp",
370        "usefuel",
371        "fuelkwh",
372        "%fuel",
373        "%reserve",
374        "refuel",
375        "dynamiceq",
376        "dynout",
377        // inherited
378        "spectrum",
379        "basefreq",
380        "enabled",
381        "like",
382    ]
383);
384
385class!(
386    PVSYSTEM,
387    "pvsystem",
388    [
389        "phases",
390        "bus1",
391        "kv",
392        "irradiance",
393        "pmpp",
394        "%pmpp",
395        "temperature",
396        "pf",
397        "conn",
398        "kvar",
399        "kva",
400        "%cutin",
401        "%cutout",
402        "effcurve",
403        "ptcurve",
404        "daily",
405        "yearly",
406        "duty",
407        "tdaily",
408        "tyearly",
409        "tduty",
410        "class",
411        "user_model",
412        "user_data",
413        "debugtrace",
414        "varfollowinverter",
415        "duty_start",
416        "wattpriority",
417        "pfpriority",
418        "pctpmpp",
419        "pctcutin",
420        "pctcutout",
421        "kvarmax",
422        "kvarmaxabs",
423        "kvarlimit",
424        "kvarlimitneg",
425        "%pminnovars",
426        "%pminkvarmax",
427        "vminpu",
428        "vmaxpu",
429        // inherited
430        "spectrum",
431        "basefreq",
432        "enabled",
433        "like",
434    ]
435);
436
437class!(
438    XYCURVE,
439    "xycurve",
440    [
441        "npts", "points", "yarray", "xarray", "csvfile", "sngfile", "dblfile", "x", "y",
442        // inherited
443        "like",
444    ]
445);
446
447class!(
448    INVCONTROL,
449    "invcontrol",
450    [
451        "derlist",
452        "mode",
453        "combimode",
454        "vvc_curve1",
455        "hysteresis_offset",
456        "voltage_curvex_ref",
457        "avgwindowlen",
458        "volt_watt_curve",
459        "dbvmin",
460        "dbvmax",
461        "armingtime",
462        "deltaq_factor",
463        "voltagechange_tolerance",
464        "varchange_tolerance",
465        "voltwatt_curve",
466        "voltwattyaxis",
467        "rateofchange_mode",
468        "lpftau",
469        "riseFallLimit",
470        "deltaP_factor",
471        "eventlog",
472        "refreactivepower",
473        "activepchange_tolerance",
474        "monvoltagecalc",
475        "monbus",
476        "monbusesvbase",
477        // inherited
478        "basefreq",
479        "enabled",
480        "like",
481    ]
482);
483
484class!(
485    SWTCONTROL,
486    "swtcontrol",
487    [
488        "switchedobj",
489        "switchedterm",
490        "action",
491        "lock",
492        "delay",
493        "normal",
494        "state",
495        "reset",
496        // inherited
497        "basefreq",
498        "enabled",
499        "like",
500    ]
501);
502
503class!(
504    REGCONTROL,
505    "regcontrol",
506    [
507        "transformer",
508        "winding",
509        "vreg",
510        "band",
511        "ptratio",
512        "ctprim",
513        "r",
514        "x",
515        "bus",
516        "delay",
517        "reversible",
518        "revvreg",
519        "revband",
520        "revr",
521        "revx",
522        "tapdelay",
523        "debugtrace",
524        "maxtapchange",
525        "inversetime",
526        "tapwinding",
527        "vlimit",
528        "ptphase",
529        "revthreshold",
530        "revdelay",
531        "revneutral",
532        "eventlog",
533        "remoteptratio",
534        "tapnum",
535        "reset",
536        "ldc_z",
537        "rev_z",
538        "cogen",
539        // inherited
540        "basefreq",
541        "enabled",
542        "like",
543    ]
544);
545
546/// The Phase A classes with property tables. Anything else parses into the
547/// raw layer untyped.
548static CLASSES: &[&DssClass] = &[
549    &LINE,
550    &LINECODE,
551    &LOAD,
552    &TRANSFORMER,
553    &VSOURCE,
554    &CAPACITOR,
555    &REACTOR,
556    &GENERATOR,
557    &PVSYSTEM,
558    &XYCURVE,
559    &INVCONTROL,
560    &SWTCONTROL,
561    &REGCONTROL,
562];
563
564/// Case insensitive exact class name lookup (`circuit` is handled by the
565/// command layer, not here).
566pub fn class_by_name(name: &str) -> Option<&'static DssClass> {
567    CLASSES
568        .iter()
569        .find(|c| c.name.eq_ignore_ascii_case(name))
570        .copied()
571}
572
573impl DssClass {
574    /// Property lookup: exact case insensitive match first, then the first
575    /// property in definition order that starts with the query.
576    pub fn prop_index(&self, query: &str) -> Option<usize> {
577        let q = query.to_ascii_lowercase();
578        self.props
579            .iter()
580            .position(|p| *p == q)
581            .or_else(|| self.props.iter().position(|p| p.starts_with(&q)))
582    }
583}
584
585#[cfg(test)]
586mod tests {
587    use super::*;
588
589    #[test]
590    fn exact_beats_prefix() {
591        // "r1" is exact even though "r0" or "rmatrix" share the prefix.
592        assert_eq!(LINE.prop_index("r1"), Some(5));
593        assert_eq!(LINE.prop_index("R1"), Some(5));
594    }
595
596    #[test]
597    fn first_prefix_match_in_definition_order() {
598        // "r" has no exact match; the first r* property in order is r1.
599        assert_eq!(LINE.prop_index("r"), Some(5));
600        // "rm" picks rmatrix, not rg or rho.
601        assert_eq!(LINE.prop_index("rm"), Some(11));
602        // "norm" picks normamps from the inherited tail.
603        assert_eq!(LINE.prop_index("norm"), Some(30));
604    }
605
606    #[test]
607    fn percent_properties() {
608        assert_eq!(TRANSFORMER.prop_index("%R"), Some(8));
609        assert_eq!(TRANSFORMER.prop_index("%Rs"), Some(36));
610        assert_eq!(TRANSFORMER.prop_index("%loadloss"), Some(25));
611    }
612
613    #[test]
614    fn load_positions_match_the_engine() {
615        // Load.cpp DefineProperties: RelWeight 35, Vlowpu 36, puXharm 37,
616        // XRharm 38 (1-based); a missing slot would shift every later
617        // positional assignment.
618        assert_eq!(LOAD.prop_index("relweight"), Some(34));
619        assert_eq!(LOAD.prop_index("vlowpu"), Some(35));
620        assert_eq!(LOAD.prop_index("puxharm"), Some(36));
621        assert_eq!(LOAD.prop_index("xrharm"), Some(37));
622        assert_eq!(LOAD.props.len(), 38 + 4); // 38 own + spectrum, basefreq, enabled, like
623    }
624
625    #[test]
626    fn class_lookup() {
627        assert!(class_by_name("Line").is_some());
628        assert!(class_by_name("LINECODE").is_some());
629        assert!(class_by_name("reactor").is_some());
630        assert!(class_by_name("xfmrcode").is_none());
631    }
632
633    #[test]
634    fn reactor_positions_match_the_engine() {
635        // Reactor.pas DefineProperties: 19 own properties (bus1..lmh) plus the
636        // PD-element inherited tail. A missing slot would shift positional
637        // assignment of every later property.
638        assert_eq!(REACTOR.prop_index("bus1"), Some(0));
639        assert_eq!(REACTOR.prop_index("kvar"), Some(3));
640        assert_eq!(REACTOR.prop_index("lmh"), Some(18));
641        // "normamps" opens the inherited PD tail right after lmh.
642        assert_eq!(REACTOR.prop_index("normamps"), Some(19));
643        assert_eq!(REACTOR.props.len(), 19 + 8);
644    }
645
646    #[test]
647    fn unknown_property() {
648        assert_eq!(LINE.prop_index("zzz"), None);
649    }
650}