完善MECMAS21参数配置与分组交互
This commit is contained in:
1 parent
32f21f08ff
commit
be54070855
28 files changed
+3418
-202
No files matched your search
@@ -5,8 +5,16 @@ import unittest
|
||||
from app.simulation.components.experimental.library import LIBRARY
|
||||
from app.simulation.components.amesim.library import LIBRARY as AMESIM_LIBRARY
|
||||
from app.simulation.core.base import AlgebraicComponent
|
||||
from app.simulation.core.catalog import ComponentDisplaySpec, PortDisplaySpec
|
||||
from app.simulation.core.metadata import ParameterDefinition, ParameterOption
|
||||
from app.simulation.core.catalog import (
|
||||
ComponentDisplaySpec,
|
||||
ParameterGroupDisplaySpec,
|
||||
PortDisplaySpec,
|
||||
)
|
||||
from app.simulation.core.metadata import (
|
||||
ParameterCondition,
|
||||
ParameterDefinition,
|
||||
ParameterOption,
|
||||
)
|
||||
from app.simulation.core.medium import IdealGasMedium
|
||||
from app.simulation.core.ports import PortDefinition
|
||||
from app.simulation.registry import (
|
||||
@@ -144,6 +152,166 @@ class ComponentRegistryTests(unittest.TestCase):
|
||||
library=LIBRARY,
|
||||
)
|
||||
|
||||
def test_parameter_groups_are_strictly_validated(self) -> None:
|
||||
parameters = (
|
||||
ParameterDefinition("mode", 1.0, label="模式"),
|
||||
ParameterDefinition("gain", 2.0, label="增益"),
|
||||
ParameterDefinition("offset", 0.0, label="偏置"),
|
||||
)
|
||||
|
||||
def validate_groups(parameter_groups: object) -> None:
|
||||
class GroupedComponent(AlgebraicComponent):
|
||||
MODEL_TYPE = "grouped_component"
|
||||
MODEL_VERSION = "1.0.0"
|
||||
PORTS = ()
|
||||
PARAMETERS = parameters
|
||||
RESULT_VARIABLES = ()
|
||||
DISPLAY = ComponentDisplaySpec(
|
||||
label="参数分组测试组件",
|
||||
library_id="experimental",
|
||||
category_id="flow",
|
||||
symbol="generic",
|
||||
ports=(),
|
||||
parameter_groups=parameter_groups, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def create(cls, *, name, medium, parameters):
|
||||
del medium, parameters
|
||||
return cls(name)
|
||||
|
||||
validate_component_model_class(GroupedComponent, library=LIBRARY)
|
||||
|
||||
valid_groups = (
|
||||
ParameterGroupDisplaySpec(
|
||||
id="advanced",
|
||||
label="高级参数",
|
||||
parameters=("mode", "gain"),
|
||||
order=10,
|
||||
),
|
||||
)
|
||||
validate_groups(valid_groups)
|
||||
self.assertEqual(
|
||||
valid_groups[0].as_catalog_dict(),
|
||||
{
|
||||
"id": "advanced",
|
||||
"label": "高级参数",
|
||||
"parameters": ["mode", "gain"],
|
||||
"order": 10,
|
||||
"defaultExpanded": False,
|
||||
},
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(ValueError, "must be a tuple"):
|
||||
validate_groups(list(valid_groups))
|
||||
with self.assertRaisesRegex(ValueError, "ParameterGroupDisplaySpec"):
|
||||
validate_groups((object(),))
|
||||
with self.assertRaisesRegex(ValueError, "must start with a lowercase"):
|
||||
validate_groups(
|
||||
(
|
||||
ParameterGroupDisplaySpec(
|
||||
id="Advanced",
|
||||
label="高级参数",
|
||||
parameters=("mode",),
|
||||
),
|
||||
)
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "label must not be empty"):
|
||||
validate_groups(
|
||||
(
|
||||
ParameterGroupDisplaySpec(
|
||||
id="advanced",
|
||||
label=" ",
|
||||
parameters=("mode",),
|
||||
),
|
||||
)
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "order must be an integer"):
|
||||
validate_groups(
|
||||
(
|
||||
ParameterGroupDisplaySpec(
|
||||
id="advanced",
|
||||
label="高级参数",
|
||||
parameters=("mode",),
|
||||
order=1.5, # type: ignore[arg-type]
|
||||
),
|
||||
)
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "must be a boolean"):
|
||||
validate_groups(
|
||||
(
|
||||
ParameterGroupDisplaySpec(
|
||||
id="advanced",
|
||||
label="高级参数",
|
||||
parameters=("mode",),
|
||||
default_expanded=1, # type: ignore[arg-type]
|
||||
),
|
||||
)
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "non-empty tuple"):
|
||||
validate_groups(
|
||||
(
|
||||
ParameterGroupDisplaySpec(
|
||||
id="advanced",
|
||||
label="高级参数",
|
||||
parameters=(),
|
||||
),
|
||||
)
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "unknown parameters: missing"):
|
||||
validate_groups(
|
||||
(
|
||||
ParameterGroupDisplaySpec(
|
||||
id="advanced",
|
||||
label="高级参数",
|
||||
parameters=("missing",),
|
||||
),
|
||||
)
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "contains duplicate names: mode"):
|
||||
validate_groups(
|
||||
(
|
||||
ParameterGroupDisplaySpec(
|
||||
id="advanced",
|
||||
label="高级参数",
|
||||
parameters=("mode", "mode"),
|
||||
),
|
||||
)
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "contains duplicate names: advanced"):
|
||||
validate_groups(
|
||||
(
|
||||
ParameterGroupDisplaySpec(
|
||||
id="advanced",
|
||||
label="高级参数",
|
||||
parameters=("mode",),
|
||||
),
|
||||
ParameterGroupDisplaySpec(
|
||||
id="advanced",
|
||||
label="其他参数",
|
||||
parameters=("gain",),
|
||||
),
|
||||
)
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
ValueError,
|
||||
"assign parameters to multiple groups: mode",
|
||||
):
|
||||
validate_groups(
|
||||
(
|
||||
ParameterGroupDisplaySpec(
|
||||
id="advanced",
|
||||
label="高级参数",
|
||||
parameters=("mode", "gain"),
|
||||
),
|
||||
ParameterGroupDisplaySpec(
|
||||
id="secondary",
|
||||
label="其他参数",
|
||||
parameters=("mode", "offset"),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
def test_property_model_parameter_options_are_strictly_validated(self) -> None:
|
||||
def validate_parameter(parameter: ParameterDefinition) -> None:
|
||||
class PropertyModelComponent(AlgebraicComponent):
|
||||
@@ -235,6 +403,251 @@ class ComponentRegistryTests(unittest.TestCase):
|
||||
)
|
||||
)
|
||||
|
||||
def test_choice_parameters_and_visibility_conditions_are_strictly_validated(
|
||||
self,
|
||||
) -> None:
|
||||
def validate_parameters(
|
||||
parameters: tuple[ParameterDefinition, ...],
|
||||
) -> None:
|
||||
class ChoiceComponent(AlgebraicComponent):
|
||||
MODEL_TYPE = "choice_component"
|
||||
MODEL_VERSION = "1.0.0"
|
||||
PORTS = ()
|
||||
PARAMETERS = parameters
|
||||
RESULT_VARIABLES = ()
|
||||
DISPLAY = ComponentDisplaySpec(
|
||||
label="离散参数测试组件",
|
||||
library_id="experimental",
|
||||
category_id="flow",
|
||||
symbol="generic",
|
||||
ports=(),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def create(cls, *, name, medium, parameters):
|
||||
del medium, parameters
|
||||
return cls(name)
|
||||
|
||||
validate_component_model_class(ChoiceComponent, library=LIBRARY)
|
||||
|
||||
def choice_parameter(
|
||||
name: str,
|
||||
default: float = 0.0,
|
||||
*,
|
||||
visible_when: tuple[ParameterCondition, ...] = (),
|
||||
) -> ParameterDefinition:
|
||||
return ParameterDefinition(
|
||||
name,
|
||||
default,
|
||||
label=name,
|
||||
editor="choice",
|
||||
options=(
|
||||
ParameterOption(0.0, "关闭"),
|
||||
ParameterOption(1.0, "开启"),
|
||||
),
|
||||
visible_when=visible_when,
|
||||
)
|
||||
|
||||
controller = choice_parameter("mode", 1.0)
|
||||
dependent = ParameterDefinition(
|
||||
"gain",
|
||||
2.0,
|
||||
label="增益",
|
||||
visible_when=(ParameterCondition("mode", (0.0, 1.0)),),
|
||||
)
|
||||
validate_parameters((controller, dependent))
|
||||
self.assertEqual(
|
||||
dependent.as_interface_dict()["visibleWhen"],
|
||||
[{"parameter": "mode", "values": [0.0, 1.0]}],
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(ValueError, "choice editor must declare options"):
|
||||
validate_parameters(
|
||||
(
|
||||
ParameterDefinition(
|
||||
"mode",
|
||||
0.0,
|
||||
label="模式",
|
||||
editor="choice",
|
||||
),
|
||||
)
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
ValueError,
|
||||
"options require the 'choice' or 'amesimGasPropertyModel' editor",
|
||||
):
|
||||
validate_parameters(
|
||||
(
|
||||
ParameterDefinition(
|
||||
"mode",
|
||||
0.0,
|
||||
label="模式",
|
||||
options=(ParameterOption(0.0, "关闭"),),
|
||||
),
|
||||
)
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "unknown parameter 'missing'"):
|
||||
validate_parameters(
|
||||
(
|
||||
controller,
|
||||
ParameterDefinition(
|
||||
"gain",
|
||||
2.0,
|
||||
label="增益",
|
||||
visible_when=(
|
||||
ParameterCondition("missing", (1.0,)),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "cannot reference itself"):
|
||||
validate_parameters(
|
||||
(
|
||||
choice_parameter(
|
||||
"mode",
|
||||
visible_when=(ParameterCondition("mode", (1.0,)),),
|
||||
),
|
||||
)
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
ValueError,
|
||||
"visibility controller 'continuous' must declare options",
|
||||
):
|
||||
validate_parameters(
|
||||
(
|
||||
ParameterDefinition("continuous", 0.0, label="连续量"),
|
||||
ParameterDefinition(
|
||||
"gain",
|
||||
2.0,
|
||||
label="增益",
|
||||
visible_when=(
|
||||
ParameterCondition("continuous", (0.0,)),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
ValueError,
|
||||
"duplicate visibility controller 'mode'",
|
||||
):
|
||||
validate_parameters(
|
||||
(
|
||||
controller,
|
||||
ParameterDefinition(
|
||||
"gain",
|
||||
2.0,
|
||||
label="增益",
|
||||
visible_when=(
|
||||
ParameterCondition("mode", (0.0,)),
|
||||
ParameterCondition("mode", (1.0,)),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "unsupported: 2"):
|
||||
validate_parameters(
|
||||
(
|
||||
controller,
|
||||
ParameterDefinition(
|
||||
"gain",
|
||||
2.0,
|
||||
label="增益",
|
||||
visible_when=(ParameterCondition("mode", (2.0,)),),
|
||||
),
|
||||
)
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "contain a cycle"):
|
||||
validate_parameters(
|
||||
(
|
||||
choice_parameter(
|
||||
"first",
|
||||
visible_when=(ParameterCondition("second", (1.0,)),),
|
||||
),
|
||||
choice_parameter(
|
||||
"second",
|
||||
visible_when=(ParameterCondition("first", (1.0,)),),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
def test_visibility_condition_value_shape_is_strictly_validated(self) -> None:
|
||||
def validate_condition(condition: object) -> None:
|
||||
class VisibilityComponent(AlgebraicComponent):
|
||||
MODEL_TYPE = "visibility_component"
|
||||
MODEL_VERSION = "1.0.0"
|
||||
PORTS = ()
|
||||
PARAMETERS = (
|
||||
ParameterDefinition(
|
||||
"mode",
|
||||
0.0,
|
||||
label="模式",
|
||||
editor="choice",
|
||||
options=(ParameterOption(0.0, "关闭"),),
|
||||
),
|
||||
ParameterDefinition(
|
||||
"gain",
|
||||
1.0,
|
||||
label="增益",
|
||||
visible_when=(condition,), # type: ignore[arg-type]
|
||||
),
|
||||
)
|
||||
RESULT_VARIABLES = ()
|
||||
DISPLAY = ComponentDisplaySpec(
|
||||
label="可见性测试组件",
|
||||
library_id="experimental",
|
||||
category_id="flow",
|
||||
symbol="generic",
|
||||
ports=(),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def create(cls, *, name, medium, parameters):
|
||||
del medium, parameters
|
||||
return cls(name)
|
||||
|
||||
validate_component_model_class(VisibilityComponent, library=LIBRARY)
|
||||
|
||||
with self.assertRaisesRegex(ValueError, "must use ParameterCondition"):
|
||||
validate_condition(object())
|
||||
with self.assertRaisesRegex(ValueError, "must use a non-empty tuple"):
|
||||
validate_condition(ParameterCondition("mode", ()))
|
||||
with self.assertRaisesRegex(ValueError, "must be finite numbers"):
|
||||
validate_condition(ParameterCondition("mode", (float("nan"),)))
|
||||
with self.assertRaisesRegex(ValueError, "contains duplicate values"):
|
||||
validate_condition(ParameterCondition("mode", (0.0, 0.0)))
|
||||
|
||||
malformed_visibility = ParameterDefinition(
|
||||
"gain",
|
||||
1.0,
|
||||
label="增益",
|
||||
visible_when=[], # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
class MalformedVisibilityComponent(AlgebraicComponent):
|
||||
MODEL_TYPE = "malformed_visibility_component"
|
||||
MODEL_VERSION = "1.0.0"
|
||||
PORTS = ()
|
||||
PARAMETERS = (malformed_visibility,)
|
||||
RESULT_VARIABLES = ()
|
||||
DISPLAY = ComponentDisplaySpec(
|
||||
label="错误可见性测试组件",
|
||||
library_id="experimental",
|
||||
category_id="flow",
|
||||
symbol="generic",
|
||||
ports=(),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def create(cls, *, name, medium, parameters):
|
||||
del medium, parameters
|
||||
return cls(name)
|
||||
|
||||
with self.assertRaisesRegex(ValueError, "visible_when must use a tuple"):
|
||||
validate_component_model_class(
|
||||
MalformedVisibilityComponent,
|
||||
library=LIBRARY,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user