Skip to content

Fix/enum parameter data type - #320

Open
corroleaus wants to merge 8 commits into
lbl-srg:masterfrom
corroleaus:fix/enum-parameter-data-type
Open

corroleaus wants to merge 8 commits into
lbl-srg:masterfrom
corroleaus:fix/enum-parameter-data-type

Conversation

@corroleaus

@corroleaus corroleaus commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

An enumeration-typed parameter carries no data type in CXF. isOfDataType is only emitted for Real, Integer, Boolean and Stringlib/cxfExtractor.js, next to a standing // TODO: check if enumeration should be here.

Defaulted enums survive by accident: the fully-qualified literal in S231:value lets a reader strip the last segment and infer the type. Without a default, nothing is left.

parameter Buildings.Controls.OBC.ASHRAE.G36.Types.VentilationStandard venStd
  "Ventilation standard, ASHRAE 62.1 or Title 24";
{ "@id": "ex:….SeriesFanVVF.Controller.venStd",
  "S231:accessSpecifier": "public",
  "S231:description": "Ventilation standard, ASHRAE 62.1 or Title 24",
  "S231:label": "venStd" }

A consumer cannot tell this is an enumeration, let alone which members are valid. The node is also not typed S231:Parameter, so the Parameter shape never applies to it.

Fix. Resolve the declared type instead of matching a fixed list. resolveEnumerationType locates the class via searchPath, confirms it declares type X = enumeration(...), and takes the qualified name from that file's within. Needed because the type is stored as written — CDL.Types.SimpleController, Types.VentilationStandard — and resolveTypeSpecifier returns early on any dotted name. Object is the enumeration class node: S231: for CDL types, ex: otherwise, same rule the enumeration_class branch already uses.

{ "@id": "ex:….SeriesFanVVF.Controller.venStd",
  "@type": "S231:Parameter",
  "S231:accessSpecifier": "public",
  "S231:description": "Ventilation standard, ASHRAE 62.1 or Title 24",
  "S231:isOfDataType": { "@id": "ex:Buildings.Controls.OBC.ASHRAE.G36.Types.VentilationStandard" },
  "S231:label": "venStd" }

Why isOfDataType → the concrete class, and no hasEnumerationType. The ontology already has a class plus a singleton individual for every data type:

S231:RealDatatype        a rdfs:Class ; rdfs:subClassOf S231:DataType .
S231:Real                a S231:DataType, S231:RealDatatype .

S231:EnumerationDatatype a rdfs:Class ; rdfs:subClassOf S231:DataType .
S231:EnumerationType     a S231:DataType, S231:EnumerationDatatype .

S231:EnumerationType is the S231:Real slot — the generic marker, already there. S231:EnumerationDatatype is the S231:RealDatatype slot — what a concrete enumeration should instantiate. The generator was using the individual:

{ "@id": "S231:Buildings.Controls.OBC.CDL.Types.SimpleController",
  "@type": "S231:EnumerationType" }

That makes SimpleController a sibling of S231:Real, not an instance of the enumeration class, so sh:class S231:DataType on the Parameter shape can't reach it. With the right term it's two hops:

venStd --isOfDataType--> …VentilationStandard --rdf:type--> S231:EnumerationDatatype --rdfs:subClassOf--> S231:DataType

Same shape as s223: hasEnumerationKind → the concrete kind, hasValue → the member. Here isOfDataType and S231:value. If a marker is wanted, sh:class S231:EnumerationDatatype selects exactly the enumeration-typed parameters. No new term either way. On parsing: the generator already opens the class file and checks for enumeration( before emitting, so it knows at generation time.

Two one-line fixes, both here.

  1. enumeration_class branch: s231Ns('EnumerationType')s231Ns('EnumerationDatatype').
  2. lib/s231ClassesProperties.ttl: S231:Datatype is referenced six times and never declared — Real, String, EnumerationType and their companion classes. Everything else spells it S231:DataType. Independent of enums: every Real and String parameter trips the shape today (at sh:Info), and it ships in CXF-Core.jsonld.

Also dropped: "S231:value": "SimpleController" on the class node. Leftover from reusing the member emission — a class has no value. Members keep theirs.

After. From test/reference/cxf/Buildings/Controls/OBC/CDL/Types/SimpleController.jsonld and test/reference/cxf/test/FromModelica/ParameterWithEnumeration.jsonld:

{ "@id": "S231:Buildings.Controls.OBC.CDL.Types.SimpleController",
  "@type": "S231:EnumerationDatatype",
  "S231:description": "Enumeration defining P, PI, PD, or PID simple controller type",
  "S231:label": "SimpleController" }

{ "@id": "S231:SimpleController.PI",
  "@type": "S231:Buildings.Controls.OBC.CDL.Types.SimpleController",
  "S231:description": "PI controller",
  "S231:label": "PI",
  "S231:value": "PI" }

{ "@id": "ex:FromModelica.ParameterWithEnumeration.conTyp",
  "@type": "S231:Parameter",
  "S231:accessSpecifier": "public",
  "S231:description": "Enumeration parameter of a CDL type",
  "S231:isOfDataType": { "@id": "S231:Buildings.Controls.OBC.CDL.Types.SimpleController" },
  "S231:label": "conTyp",
  "S231:value": "Buildings.Controls.OBC.CDL.Types.SimpleController.PI" }

The enumeration is an instance of S231:EnumerationDatatype and the class of its own members — same punning as before, and as s223. From a parameter with no default:

SELECT ?param ?enumType ?member WHERE {
  ?param    S231:isOfDataType ?enumType .
  ?enumType a S231:EnumerationDatatype .
  ?member   a ?enumType .
}

Validation. Shapes lib/s231ClassesProperties.ttl, data = ontology + one Parameter per row, as getCxfCore merges them. pyshacl 0.40.1 and shifty-cli 0.3.0 agree.

isOfDataType master this PR
enum class typed S231:EnumerationType
enum class typed S231:EnumerationDatatype
S231:Real, S231:String
S231:Integer, S231:Boolean

Not in this PR. S231:value on the parameter is a string, so a shape can't check the selection against the enumeration. Making it { "@id": … } needs fully qualified member IRIs first — S231:SimpleController.PI vs …CDL.Types.SimpleController.PI today — which rewrites every member in CXF-Core.jsonld. Follow-up. Same for RealDatatype vs IntegerDataType.

Fixtures. Enumeration1, SimpleController, Smoothness, Extrapolation, ZeroTime ×2, ParameterWithEnumeration, both CXF-Core.jsonld.

CXF-Core.jsonld and the G36 SetPoints/CoolingCoil reference predate the explicit
prefix bindings in getCxfGraph, so they still spell every IRI out in full while
fresh output abbreviates with the S231 and ex prefixes. Regenerating rewrites
the files wholesale without changing their meaning, which would bury any real
change in serialization noise, so do it on its own.

    node app.js -f Buildings/Controls/OBC/CDL -o cxf --elementary --cxfCore \
      --prettyPrint
    cp cxf/CXF-Core.jsonld .

Compared as RDF rather than as text, both files are unchanged: CXF-Core.jsonld
5197 triples before and after with none added or removed, CoolingCoil.jsonld
153. The tests already compare CXF as graphs, so they pass either way.
CXF recorded a data type only for parameters and constants declared as one of
the four Modelica built-ins. An enumeration-typed parameter therefore carried
neither S231:isOfDataType nor rdf:type S231:Parameter, so its type survived only
when a fully-qualified default literal happened to sit in S231:value, and not at
all when the declaration had no default:

    parameter Buildings.Controls.OBC.ASHRAE.G36.Types.VentilationStandard venStd
      "Ventilation standard, ASHRAE 62.1 or Title 24";

emitted a node with only accessSpecifier, description and label. A consumer
cannot recover the enumeration, which is needed to check members and to resolve
conditional expressions over them.

Resolve the declared type instead of matching it against a fixed list.
resolveEnumerationType locates the class through searchPath, confirms it
declares `type <Name> = enumeration(`, and takes the qualified name from that
file's own within clause. This is required because a declaration carries the
type exactly as written, which may be fully qualified, partially qualified
(CDL.Types.SimpleController) or relative to an enclosing package
(Types.VentilationStandard); resolveTypeSpecifier returns early on any dotted
name and so never resolves the latter two.

The emitted node uses the S231 namespace for CDL types and the example namespace
otherwise, matching the prefix rule the enumeration_class branch already applies
when it emits the enumeration itself. A type that is neither a built-in nor an
enumeration still yields no isOfDataType triple.

Adds ParameterWithEnumeration, covering an enumeration parameter with and
without a default, for a CDL and a non-CDL enumeration.

The updated references add only these triples: two per enumeration parameter in
TestEvaluation_4 and G36 SetPoints/CoolingCoil, and ten in CXF-Core for
Reals.PID, Reals.PIDWithReset, Sources.CalendarTime and Sources.TimeTable.
resolveEnumerationType assumed the file located by searchPath declares a class
of the same name, so it only recognised one-enumeration-per-file layouts. A
package that groups its enumerations in a single file — Buildings/Templates/
Components/Types.mo, Modelica/Fluid/Types.mo — was located correctly and then
rejected, losing the data type for parameters that reference it. Look the
enumeration up by the last segment of the type specifier instead, and build the
qualified name from the class the file itself declares.

Guard the reconstruction with the invariant that the result must end with the
type specifier as written. It does not hold for a monolithic package.mo holding
nested packages: Modelica.Media.Interfaces.Choices.ReferenceEnthalpy resolves to
Modelica/Media/package.mo, where the enumeration sits two packages below the
class the file declares, and would otherwise have been rebuilt as the wrong name
Modelica.Media.ReferenceEnthalpy. Checked over every fully-qualified type
reference in the Buildings library and the MSL: 112 resolve as enumerations, 0
with a name that disagrees with the reference as written.

Blank string literals and comments before matching. Classes carry their
documentation in info strings and reference material quotes declaration syntax
verbatim, so ModelicaReference/package.mo contains `type E = enumeration(...)`
inside a string; it is the one such case in 8187 files, and widening the lookup
to grouped declarations reads deeper into exactly the files that carry large
documentation blocks.
…rectory

searchPath walks down from each MODELICAPATH entry, so it locates a class only
when MODELICAPATH holds the directory containing the library. Pointed at the
library directory itself — `…/modelica-buildings/Buildings`, an equally common
convention — it looks for `…/Buildings/Buildings/Controls/…` and returns
nothing, so every enumeration silently lost its data type again and the emitter
fell back to writing no triple at all. Confirmed against a consumer that parses
staged models this way: 221 CXF files, 1395 isOfDataType triples for built-ins,
zero for enumerations.

Fall back to getMoFiles, which matches by path suffix and so resolves the class
under either convention. It is only reached when searchPath finds nothing, and
the result is still checked for an enumeration declaration and validated
against the type specifier as written, so the fallback cannot widen what
qualifies. getMoFiles returns its input unchanged when it finds nothing, hence
the filter on a real `.mo` path.

Key the cache on MODELICAPATH too. A caller may parse successive models against
different library sets in one process — modelica-json is used in-process and
the variable swapped per invocation — and a lookup that failed under one set
must not be reused under another.
…numeration

searchPath resolves relative to the enclosing package, which under a
MODELICAPATH that names the library directory itself can land on a plausible
but unrelated file: `Types.VentilationStandard` declared inside
`…G36.AHUs.MultiZone.VAV` resolves to `Buildings/Types/package.mo`, the
top-level Buildings.Types package that happens to sit one level below the
MODELICAPATH entry. Because that returned a file, the getMoFiles fallback added
in the previous commit never ran and the data type was lost again.

Try the fallback whenever no candidate checks out rather than only when
searchPath returns nothing. Candidate acceptance already required an
enumeration declaration of the right name whose rebuilt qualified name ends
with the specifier as written, so a wrong file is rejected rather than trusted,
and extending the search cannot widen what qualifies.

Extracted as firstEnumerationType, which walks candidates in order and returns
the first that satisfies both checks.
@anandkp92
anandkp92 self-requested a review August 12, 2026 18:03
@anandkp92

Copy link
Copy Markdown
Member

@corroleaus thank you for your proposal and fix to handle enumerations.

What do you think of proposing a new predicate called S231:hasEnumerationType and point to the specific type of enumeration and we use S231:isOfDataType for S231:Enumeration?

{ "@id": "ex:….SeriesFanVVF.Controller.venStd",
  "@type": "S231:Parameter",
  "S231:accessSpecifier": "public",
  "S231:description": "Ventilation standard, ASHRAE 62.1 or Title 24",
  "S231:isOfDataType": {
    "@id": "S231:Enumeration"
  },
  "S231:hasEnumerationType": "ex:Buildings.Controls.OBC.ASHRAE.G36.Types.VentilationStandard"
  "S231:label": "venStd" }

This would require us to parse ex:Buildings.Controls.OBC.ASHRAE.G36.Types.VentilationStandard to understand that it is an enumeration though.

The ontology models every data type as a class plus one singleton
individual of it: S231:RealDatatype is the class and S231:Real the
individual, and likewise S231:EnumerationDatatype and
S231:EnumerationType. The enumeration_class branch typed every concrete
enumeration with the individual, which declares SimpleController a
sibling of S231:Real rather than an instance of the enumeration class.
Nothing then connects it to S231:DataType, so the Parameter shape's
`sh:class S231:DataType` on isOfDataType cannot reach it, which is the
failure a separate hasEnumerationType predicate was proposed to route
around.

Use the class. The enumeration node is then an instance of
S231:EnumerationDatatype and, as before, the class of its own members,
so a parameter reaches S231:DataType in two hops: isOfDataType,
rdf:type, rdfs:subClassOf. S231:EnumerationType stays in the ontology
as the generic marker, parallel to S231:Real, and is no longer emitted.

Updates the enumeration references and CXF-Core.jsonld: one @type per
enumeration class, nothing else.
S231:Datatype is referenced six times in s231ClassesProperties.ttl and
never declared: the rdf:type of S231:Real, S231:String and
S231:EnumerationType, and the rdfs:subClassOf of their companion
classes S231:RealDatatype, S231:StringDatatype and
S231:EnumerationDatatype. Everything else spells it S231:DataType,
which is the declared class and the one the Parameter shape names in
`sh:class S231:DataType`. Three consecutive blocks written with the
wrong spelling, companion class name included, so they were
self-consistent enough to go unnoticed.

Because of it every Real- and String-typed parameter trips that shape
today, at sh:Info, and enumeration classes instantiating
S231:EnumerationDatatype would too. The generator emits neither
spelling, so the change is confined to the ttl and the CXF-Core.jsonld
it is merged into.
The enumeration_class branch reused the label/value/description
emission written for members, so every enumeration class carried
S231:value set to its own short name: "SimpleController" on
S231:Buildings.Controls.OBC.CDL.Types.SimpleController. A class has no
value; the triple described nothing and shipped in CXF-Core.jsonld for
all four CDL enumerations. Members keep their S231:value, which is the
member name a parameter's S231:value selects.
@corroleaus

Copy link
Copy Markdown
Contributor Author

@corroleaus thank you for your proposal and fix to handle enumerations.

What do you think of proposing a new predicate called S231:hasEnumerationType and point to the specific type of enumeration and we use S231:isOfDataType for S231:Enumeration?

{ "@id": "ex:….SeriesFanVVF.Controller.venStd",
  "@type": "S231:Parameter",
  "S231:accessSpecifier": "public",
  "S231:description": "Ventilation standard, ASHRAE 62.1 or Title 24",
  "S231:isOfDataType": {
    "@id": "S231:Enumeration"
  },
  "S231:hasEnumerationType": "ex:Buildings.Controls.OBC.ASHRAE.G36.Types.VentilationStandard"
  "S231:label": "venStd" }

This would require us to parse ex:Buildings.Controls.OBC.ASHRAE.G36.Types.VentilationStandard to understand that it is an enumeration though.

Thanks for the feedback! See updated PR body and Diff.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants