merge/model-development-into-main #2
No files matched your search
@@ -0,0 +1,100 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import Counter
|
||||
from dataclasses import dataclass
|
||||
|
||||
from PythonModels.reporting.amesim_results import AmesimResults
|
||||
from PythonModels.reporting.test_mql_observations import (
|
||||
TestMqlObservationCatalog,
|
||||
build_test_mql_observation_catalog,
|
||||
)
|
||||
from PythonModels.reporting.test_mql_variables import TestMqlVariableBinding
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TestMqlOutputSignal:
|
||||
data_path: str
|
||||
domain: str
|
||||
owner_alias: str
|
||||
owner_kind: str
|
||||
submodel: str
|
||||
signal_name: str
|
||||
units: str | None
|
||||
amesim_index: int
|
||||
saved: bool
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TestMqlOutputSchema:
|
||||
signals: tuple[TestMqlOutputSignal, ...]
|
||||
|
||||
@property
|
||||
def signal_count(self) -> int:
|
||||
return len(self.signals)
|
||||
|
||||
def by_data_path(self, data_path: str) -> TestMqlOutputSignal:
|
||||
for signal in self.signals:
|
||||
if signal.data_path == data_path:
|
||||
return signal
|
||||
raise KeyError(data_path)
|
||||
|
||||
def data_paths(self) -> tuple[str, ...]:
|
||||
return tuple(signal.data_path for signal in self.signals)
|
||||
|
||||
def data_paths_by_domain(self, domain: str) -> tuple[str, ...]:
|
||||
return tuple(signal.data_path for signal in self.signals if signal.domain == domain)
|
||||
|
||||
def counts_by_domain(self) -> dict[str, int]:
|
||||
return dict(Counter(signal.domain for signal in self.signals))
|
||||
|
||||
def counts_by_submodel(self) -> dict[str, int]:
|
||||
return dict(Counter(signal.submodel for signal in self.signals))
|
||||
|
||||
def counts_by_owner_kind(self) -> dict[str, int]:
|
||||
return dict(Counter(signal.owner_kind for signal in self.signals))
|
||||
|
||||
def counts_by_units(self) -> dict[str | None, int]:
|
||||
return dict(Counter(signal.units for signal in self.signals))
|
||||
|
||||
|
||||
def build_test_mql_output_schema(
|
||||
results: AmesimResults,
|
||||
*,
|
||||
observation_catalog: TestMqlObservationCatalog | None = None,
|
||||
) -> TestMqlOutputSchema:
|
||||
observation_catalog = observation_catalog or build_test_mql_observation_catalog(results)
|
||||
domain_by_data_path = _domain_by_data_path(observation_catalog)
|
||||
signals = []
|
||||
for data_path in sorted(domain_by_data_path):
|
||||
variable = observation_catalog.variable_catalog.by_data_path(data_path)
|
||||
signals.append(_signal_from_variable(variable, domain_by_data_path[data_path]))
|
||||
return TestMqlOutputSchema(signals=tuple(signals))
|
||||
|
||||
|
||||
def _domain_by_data_path(
|
||||
observation_catalog: TestMqlObservationCatalog,
|
||||
) -> dict[str, str]:
|
||||
domain_by_data_path = {}
|
||||
for domain, data_paths in observation_catalog.data_paths_by_domain().items():
|
||||
for data_path in data_paths:
|
||||
if data_path in domain_by_data_path:
|
||||
raise ValueError(f"Data_Path {data_path!r} is assigned to multiple domains.")
|
||||
domain_by_data_path[data_path] = domain
|
||||
return domain_by_data_path
|
||||
|
||||
|
||||
def _signal_from_variable(
|
||||
variable: TestMqlVariableBinding,
|
||||
domain: str,
|
||||
) -> TestMqlOutputSignal:
|
||||
return TestMqlOutputSignal(
|
||||
data_path=variable.data_path,
|
||||
domain=domain,
|
||||
owner_alias=variable.owner_alias,
|
||||
owner_kind=variable.owner_kind,
|
||||
submodel=variable.submodel,
|
||||
signal_name=variable.signal_name,
|
||||
units=variable.units,
|
||||
amesim_index=variable.index,
|
||||
saved=variable.saved,
|
||||
)
|
||||
@@ -0,0 +1,91 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from PythonModels.reporting.amesim_results import load_test_mql_amesim_results
|
||||
from PythonModels.reporting.test_mql_observations import build_test_mql_observation_catalog
|
||||
from PythonModels.reporting.test_mql_output_schema import build_test_mql_output_schema
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
TEST_MQL_AME = REPO_ROOT / "AmesimModels" / "test_mql.ame"
|
||||
|
||||
|
||||
class TestMqlOutputSchemaTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.amesim_results = load_test_mql_amesim_results(TEST_MQL_AME)
|
||||
cls.observation_catalog = build_test_mql_observation_catalog(cls.amesim_results)
|
||||
cls.schema = build_test_mql_output_schema(
|
||||
cls.amesim_results,
|
||||
observation_catalog=cls.observation_catalog,
|
||||
)
|
||||
|
||||
def test_schema_contains_observed_output_data_paths(self) -> None:
|
||||
self.assertEqual(self.schema.signal_count, 858)
|
||||
self.assertEqual(self.schema.data_paths(), self.observation_catalog.data_paths())
|
||||
self.assertEqual(len(set(self.schema.data_paths())), self.schema.signal_count)
|
||||
|
||||
def test_counts_by_domain_owner_kind_and_submodel(self) -> None:
|
||||
self.assertEqual(
|
||||
self.schema.counts_by_domain(),
|
||||
{"chambers": 100, "lines": 388, "mechanical": 266, "orifices": 104},
|
||||
)
|
||||
self.assertEqual(
|
||||
self.schema.counts_by_owner_kind(),
|
||||
{"component": 470, "connection": 388},
|
||||
)
|
||||
self.assertEqual(self.schema.counts_by_submodel()["PNCH012"], 80)
|
||||
self.assertEqual(self.schema.counts_by_submodel()["PNCH023"], 20)
|
||||
self.assertEqual(self.schema.counts_by_submodel()["PNL0001"], 180)
|
||||
self.assertEqual(self.schema.counts_by_submodel()["MECMAS21"], 110)
|
||||
self.assertEqual(self.schema.counts_by_submodel()["LMECHN1"], 34)
|
||||
|
||||
def test_counts_by_units_preserve_amesim_units(self) -> None:
|
||||
counts = self.schema.counts_by_units()
|
||||
|
||||
self.assertEqual(counts["m/s"], 108)
|
||||
self.assertEqual(counts[None], 98)
|
||||
self.assertEqual(counts["N"], 92)
|
||||
self.assertEqual(counts["J/s"], 84)
|
||||
self.assertEqual(counts["g/s"], 84)
|
||||
self.assertEqual(counts["Pa"], 84)
|
||||
self.assertEqual(counts["K"], 84)
|
||||
self.assertEqual(counts["cm**3"], 16)
|
||||
self.assertEqual(counts["L/min"], 8)
|
||||
|
||||
def test_typical_signal_metadata_is_available_by_data_path(self) -> None:
|
||||
chamber = self.schema.by_data_path("press@pn_general_chamber")
|
||||
line = self.schema.by_data_path("dm1@pneumatic_69")
|
||||
mechanical = self.schema.by_data_path("tforce@dynamic_mechanical_node_alternative_2")
|
||||
|
||||
self.assertEqual(chamber.domain, "chambers")
|
||||
self.assertEqual(chamber.owner_alias, "pn_general_chamber")
|
||||
self.assertEqual(chamber.owner_kind, "component")
|
||||
self.assertEqual(chamber.submodel, "PNCH023")
|
||||
self.assertEqual(chamber.signal_name, "press")
|
||||
self.assertEqual(chamber.units, "Pa")
|
||||
self.assertTrue(chamber.saved)
|
||||
self.assertEqual(line.domain, "lines")
|
||||
self.assertEqual(line.owner_kind, "connection")
|
||||
self.assertEqual(line.submodel, "PNL0001")
|
||||
self.assertEqual(line.units, "g/s")
|
||||
self.assertEqual(mechanical.domain, "mechanical")
|
||||
self.assertEqual(mechanical.submodel, "LMECHN1")
|
||||
self.assertEqual(mechanical.units, "N")
|
||||
|
||||
def test_data_paths_can_be_selected_by_domain(self) -> None:
|
||||
self.assertEqual(len(self.schema.data_paths_by_domain("chambers")), 100)
|
||||
self.assertEqual(len(self.schema.data_paths_by_domain("orifices")), 104)
|
||||
self.assertEqual(len(self.schema.data_paths_by_domain("lines")), 388)
|
||||
self.assertEqual(len(self.schema.data_paths_by_domain("mechanical")), 266)
|
||||
self.assertEqual(self.schema.data_paths_by_domain("missing"), ())
|
||||
|
||||
def test_unknown_data_path_raises_key_error(self) -> None:
|
||||
with self.assertRaises(KeyError):
|
||||
self.schema.by_data_path("not_a_signal@not_an_owner")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user