接入 AMESim 机械接触和节点组件
This commit is contained in:
1 parent
2ddaaeb56a
commit
e87df789b2
8 files changed
+441
-9
No files matched your search
@@ -21,8 +21,6 @@ class AmesimComponentMigrationMatrixTests(unittest.TestCase):
|
||||
def test_unsupported_domains_are_not_marked_as_public_candidates(self) -> None:
|
||||
text = MATRIX_PATH.read_text(encoding="utf-8")
|
||||
unsupported = {
|
||||
"LMECHN1",
|
||||
"LSTP00A",
|
||||
"PNRP17",
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ from app.simulation.components.amesim.mechanical.translational import (
|
||||
AmesimF000,
|
||||
AmesimForc,
|
||||
AmesimMecmas21,
|
||||
AmesimLstp00a,
|
||||
AmesimLmechn1,
|
||||
)
|
||||
from app.simulation.core.medium import IdealGasMedium
|
||||
from app.simulation.registry import COMPONENT_MODEL_REGISTRY
|
||||
@@ -56,6 +58,70 @@ class AmesimMechanicalPublicComponentTests(unittest.TestCase):
|
||||
self.assertAlmostEqual(mass.port_1.x, 0.1)
|
||||
self.assertAlmostEqual(mass.port_2.v, 0.2)
|
||||
|
||||
|
||||
|
||||
def test_lmechn1_ties_active_port_kinematics_and_balances_force(self) -> None:
|
||||
node = AmesimLmechn1("node_1", self.medium, v1=2.0, sum=1.0)
|
||||
node.port_1.x = 0.2
|
||||
node.port_1.v = 0.3
|
||||
node.port_1.f = 4.0
|
||||
node.port_2.x = 0.2
|
||||
node.port_2.v = 0.3
|
||||
node.port_2.f = 6.0
|
||||
node.port_9.x = 0.2
|
||||
node.port_9.v = 0.3
|
||||
node.port_9.f = -10.0
|
||||
|
||||
residuals = node.pressure_flow_equation_residuals()
|
||||
|
||||
self.assertEqual(node.active_ports, ("port_1", "port_2", "port_9"))
|
||||
self.assertAlmostEqual(node.total_force, 0.0)
|
||||
self.assertEqual(len(residuals), 5)
|
||||
self.assertTrue(all(abs(residual.value) <= 1.0e-12 for residual in residuals))
|
||||
self.assertEqual(node.component_result_values(), {"tforce": 0.0})
|
||||
|
||||
def test_lmechn1_registry_rejects_fractional_integer_options(self) -> None:
|
||||
with self.assertRaisesRegex(ValueError, "v1 must be an integer"):
|
||||
COMPONENT_MODEL_REGISTRY["amesim_lmechn1"].create(
|
||||
"node_1",
|
||||
self.medium,
|
||||
{"v1": 2.5},
|
||||
)
|
||||
|
||||
def test_lstp00a_generates_opposing_contact_forces(self) -> None:
|
||||
contact = AmesimLstp00a(
|
||||
"contact_1",
|
||||
self.medium,
|
||||
gap0=0.0,
|
||||
kcont=1000.0,
|
||||
rcont=10.0,
|
||||
)
|
||||
contact.port_1.x = 0.0
|
||||
contact.port_2.x = 0.002
|
||||
contact.port_1.v = 0.0
|
||||
contact.port_2.v = 0.1
|
||||
contact.port_1.f = -3.0
|
||||
contact.port_2.f = 3.0
|
||||
|
||||
self.assertAlmostEqual(contact.gap, -0.002)
|
||||
self.assertAlmostEqual(contact.penetration, 0.002)
|
||||
self.assertAlmostEqual(contact.contact_force, 3.0)
|
||||
self.assertEqual(
|
||||
contact.component_result_values(),
|
||||
{"gap": -0.002, "penetration": 0.002, "force": 3.0},
|
||||
)
|
||||
residuals = contact.pressure_flow_equation_residuals()
|
||||
self.assertAlmostEqual(residuals[0].value, 0.0)
|
||||
self.assertAlmostEqual(residuals[1].value, 0.0)
|
||||
|
||||
def test_lstp00a_returns_zero_before_contact(self) -> None:
|
||||
contact = AmesimLstp00a("contact_1", self.medium, gap0=0.001, kcont=1000.0)
|
||||
contact.port_1.x = 0.0
|
||||
contact.port_2.x = 0.0005
|
||||
|
||||
self.assertAlmostEqual(contact.gap, 0.0005)
|
||||
self.assertAlmostEqual(contact.contact_force, 0.0)
|
||||
|
||||
def test_mecmas21_registry_rejects_fractional_integer_options(self) -> None:
|
||||
with self.assertRaisesRegex(ValueError, "useFriction must be an integer"):
|
||||
COMPONENT_MODEL_REGISTRY["amesim_mecmas21"].create(
|
||||
|
||||
@@ -14,6 +14,20 @@ from tests.test_generic_system_xml_simulation import component_node, physical_ed
|
||||
from tests.test_system_xml_protocol import physical_port
|
||||
|
||||
|
||||
LSTP00A_DEFAULTS = {
|
||||
"na": 10.0,
|
||||
"gap0": 0.0,
|
||||
"kcont": 1000.0,
|
||||
"G": 8.57e10,
|
||||
"sdiam": 0.02,
|
||||
"wdiam": 0.002,
|
||||
"rcont": 0.0,
|
||||
"Pdis": 1.0e-7,
|
||||
"stiffmode": 1.0,
|
||||
"discContactOption": 1.0,
|
||||
}
|
||||
|
||||
|
||||
MECMAS21_DEFAULTS = {
|
||||
"mass": 2.0,
|
||||
"fstick": 0.0,
|
||||
@@ -130,7 +144,127 @@ def signal_force_mass_project() -> ReactFlowProjectPayload:
|
||||
)
|
||||
|
||||
|
||||
def elastic_contact_project() -> ReactFlowProjectPayload:
|
||||
left_parameters = dict(MECMAS21_DEFAULTS)
|
||||
left_parameters["x0"] = 0.0
|
||||
right_parameters = dict(MECMAS21_DEFAULTS)
|
||||
right_parameters["x0"] = 0.001
|
||||
return ReactFlowProjectPayload(
|
||||
name="amesim-mechanical-elastic-contact-smoke",
|
||||
nodes=[
|
||||
component_node("zero_left", "amesim_f000", [mechanical_port("port_1", "right")]),
|
||||
component_node(
|
||||
"mass_left",
|
||||
"amesim_mecmas21",
|
||||
[mechanical_port("port_1", "left"), mechanical_port("port_2", "right")],
|
||||
left_parameters,
|
||||
),
|
||||
component_node(
|
||||
"contact_1",
|
||||
"amesim_lstp00a",
|
||||
[mechanical_port("port_1", "left"), mechanical_port("port_2", "right")],
|
||||
LSTP00A_DEFAULTS,
|
||||
),
|
||||
component_node(
|
||||
"mass_right",
|
||||
"amesim_mecmas21",
|
||||
[mechanical_port("port_1", "left"), mechanical_port("port_2", "right")],
|
||||
right_parameters,
|
||||
),
|
||||
component_node("zero_right", "amesim_f000", [mechanical_port("port_1", "left")]),
|
||||
],
|
||||
edges=[
|
||||
physical_edge("edge-1", "zero_left", "port_1", "mass_left", "port_1"),
|
||||
physical_edge("edge-2", "mass_left", "port_2", "contact_1", "port_1"),
|
||||
physical_edge("edge-3", "contact_1", "port_2", "mass_right", "port_1"),
|
||||
physical_edge("edge-4", "mass_right", "port_2", "zero_right", "port_1"),
|
||||
],
|
||||
simulation={"t_start": 0.0, "t_stop": 0.02, "step": 0.01, "max_step": 0.005, "method": "BDF"},
|
||||
)
|
||||
|
||||
|
||||
def force_node_mass_project() -> ReactFlowProjectPayload:
|
||||
node_ports = [mechanical_port(f"port_{index}", "left") for index in range(1, 9)]
|
||||
node_ports.append(mechanical_port("port_9", "right"))
|
||||
zero_nodes = [
|
||||
component_node(f"zero_{index}", "amesim_f000", [mechanical_port("port_1", "right")])
|
||||
for index in range(2, 9)
|
||||
]
|
||||
zero_edges = [
|
||||
physical_edge(f"edge-zero-{index}", f"zero_{index}", "port_1", "node_1", f"port_{index}")
|
||||
for index in range(2, 9)
|
||||
]
|
||||
return ReactFlowProjectPayload(
|
||||
name="amesim-mechanical-node-smoke",
|
||||
nodes=[
|
||||
component_node(
|
||||
"step_1",
|
||||
"amesim_step0",
|
||||
[signal_port("out", "output", "right")],
|
||||
{"initial": 10.0, "final": 10.0, "time": 0.0},
|
||||
),
|
||||
component_node(
|
||||
"force_1",
|
||||
"amesim_forc",
|
||||
[signal_port("res", "input", "left"), mechanical_port("port_2", "right")],
|
||||
),
|
||||
component_node("node_1", "amesim_lmechn1", node_ports, {"v1": 8.0, "sum": 1.0}),
|
||||
*zero_nodes,
|
||||
component_node(
|
||||
"mass_1",
|
||||
"amesim_mecmas21",
|
||||
[mechanical_port("port_1", "left"), mechanical_port("port_2", "right")],
|
||||
MECMAS21_DEFAULTS,
|
||||
),
|
||||
component_node("zero_right", "amesim_f000", [mechanical_port("port_1", "left")]),
|
||||
],
|
||||
edges=[
|
||||
signal_edge("signal-1", "step_1", "out", "force_1", "res"),
|
||||
physical_edge("edge-force", "force_1", "port_2", "node_1", "port_1"),
|
||||
*zero_edges,
|
||||
physical_edge("edge-mass", "node_1", "port_9", "mass_1", "port_1"),
|
||||
physical_edge("edge-right", "mass_1", "port_2", "zero_right", "port_1"),
|
||||
],
|
||||
simulation={"t_start": 0.0, "t_stop": 0.02, "step": 0.01, "max_step": 0.005, "method": "BDF"},
|
||||
)
|
||||
|
||||
|
||||
class AmesimMechanicalXmlTests(unittest.TestCase):
|
||||
|
||||
|
||||
def test_force_node_mass_project_compiles_and_simulates(self) -> None:
|
||||
xml = build_reactflow_system_xml(force_node_mass_project())
|
||||
report = validate_system_xml_document(xml)
|
||||
|
||||
self.assertTrue(report.valid, report.as_dict())
|
||||
network = compile_reactflow_network(force_node_mass_project())
|
||||
self.assertTrue(network.pressure_flow_structure_dict()["isSquare"])
|
||||
result = run_system_xml_simulation(xml)
|
||||
|
||||
self.assertTrue(result["success"], result["message"])
|
||||
self.assertEqual(result["series"]["step_1.out.signal"], [10.0, 10.0, 10.0])
|
||||
self.assertEqual(result["series"]["force_1.res.signal"], [10.0, 10.0, 10.0])
|
||||
self.assertAlmostEqual(result["series"]["mass_1.a"][0], 5.0)
|
||||
self.assertGreater(result["series"]["mass_1.v"][-1], 0.0)
|
||||
self.assertGreater(result["series"]["mass_1.x"][-1], 0.0)
|
||||
|
||||
def test_elastic_contact_project_compiles_and_simulates(self) -> None:
|
||||
xml = build_reactflow_system_xml(elastic_contact_project())
|
||||
report = validate_system_xml_document(xml)
|
||||
|
||||
self.assertTrue(report.valid, report.as_dict())
|
||||
network = compile_reactflow_network(elastic_contact_project())
|
||||
self.assertTrue(network.pressure_flow_structure_dict()["isSquare"])
|
||||
result = run_system_xml_simulation(xml)
|
||||
|
||||
self.assertTrue(result["success"], result["message"])
|
||||
self.assertEqual(result["series"]["time"], [0.0, 0.01, 0.02])
|
||||
self.assertAlmostEqual(result["series"]["contact_1.force"][0], 1.0)
|
||||
self.assertAlmostEqual(result["series"]["mass_left.a"][0], 0.5)
|
||||
self.assertAlmostEqual(result["series"]["mass_right.a"][0], -0.5)
|
||||
self.assertGreater(result["series"]["mass_left.x"][-1], 0.0)
|
||||
self.assertLess(result["series"]["mass_right.x"][-1], 0.001)
|
||||
|
||||
def test_zero_force_mechanical_project_compiles_and_simulates(self) -> None:
|
||||
xml = build_reactflow_system_xml(zero_force_mass_project())
|
||||
report = validate_system_xml_document(xml)
|
||||
|
||||
@@ -48,7 +48,7 @@ class ComponentCatalogTests(unittest.TestCase):
|
||||
|
||||
self.assertEqual(library["label"], "AMESim 组件库")
|
||||
self.assertEqual([category["id"] for category in library["categories"]], ["storage", "flow", "junctions", "boundary", "signals", "mechanical"])
|
||||
self.assertEqual(set(components), {"amesim_pnpl01", "amesim_step0", "amesim_ud00", "amesim_f000", "amesim_forc", "amesim_mecmas21", "amesim_pnch023", "amesim_pnch012", "amesim_pnor001", "amesim_pnvo001_fixed", "amesim_pnvo001", "amesim_pnl00r", "amesim_pnl0001", "amesim_pnl0002", "amesim_pnl0003", "amesim_pn3node2", "amesim_p4node2"})
|
||||
self.assertEqual(set(components), {"amesim_pnpl01", "amesim_step0", "amesim_ud00", "amesim_f000", "amesim_forc", "amesim_mecmas21", "amesim_lstp00a", "amesim_lmechn1", "amesim_pnch023", "amesim_pnch012", "amesim_pnor001", "amesim_pnvo001_fixed", "amesim_pnvo001", "amesim_pnl00r", "amesim_pnl0001", "amesim_pnl0002", "amesim_pnl0003", "amesim_pn3node2", "amesim_p4node2"})
|
||||
self.assertEqual(
|
||||
[port["name"] for port in components["amesim_p4node2"]["ports"]],
|
||||
["port_1", "port_2", "port_3", "port_4"],
|
||||
@@ -112,6 +112,21 @@ class ComponentCatalogTests(unittest.TestCase):
|
||||
self.assertEqual([port["name"] for port in components["amesim_mecmas21"]["ports"]], ["port_1", "port_2"])
|
||||
self.assertEqual(mecmas_parameters["mass"]["unit"], "kg")
|
||||
self.assertEqual(mecmas_parameters["Kbmin"]["unit"], "N/m")
|
||||
lstp_parameters = {
|
||||
parameter["name"]: parameter
|
||||
for parameter in components["amesim_lstp00a"]["parameters"]
|
||||
}
|
||||
self.assertEqual(components["amesim_lstp00a"]["category"]["id"], "mechanical")
|
||||
self.assertEqual([port["name"] for port in components["amesim_lstp00a"]["ports"]], ["port_1", "port_2"])
|
||||
self.assertEqual(lstp_parameters["gap0"]["unit"], "m")
|
||||
self.assertEqual(lstp_parameters["kcont"]["unit"], "N/m")
|
||||
lmechn_parameters = {
|
||||
parameter["name"]: parameter
|
||||
for parameter in components["amesim_lmechn1"]["parameters"]
|
||||
}
|
||||
self.assertEqual(components["amesim_lmechn1"]["category"]["id"], "mechanical")
|
||||
self.assertEqual([port["name"] for port in components["amesim_lmechn1"]["ports"]], [f"port_{index}" for index in range(1, 10)])
|
||||
self.assertEqual(lmechn_parameters["v1"]["maximum"], 8.0)
|
||||
self.assertEqual(components["amesim_pnvo001"]["category"]["id"], "flow")
|
||||
self.assertEqual([port["name"] for port in components["amesim_pnvo001"]["ports"]], ["res", "port_2", "port_3"])
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ class ComponentRegistryTests(unittest.TestCase):
|
||||
"amesim_f000",
|
||||
"amesim_forc",
|
||||
"amesim_mecmas21",
|
||||
"amesim_lstp00a",
|
||||
"amesim_lmechn1",
|
||||
"amesim_pnch023",
|
||||
"amesim_pnch012",
|
||||
"amesim_pnor001",
|
||||
@@ -66,6 +68,8 @@ class ComponentRegistryTests(unittest.TestCase):
|
||||
self.assertEqual(models["amesim_f000"].library.id, "amesim")
|
||||
self.assertEqual(models["amesim_forc"].library.id, "amesim")
|
||||
self.assertEqual(models["amesim_mecmas21"].library.id, "amesim")
|
||||
self.assertEqual(models["amesim_lstp00a"].library.id, "amesim")
|
||||
self.assertEqual(models["amesim_lmechn1"].library.id, "amesim")
|
||||
self.assertEqual(models["amesim_pnch023"].library.id, "amesim")
|
||||
self.assertEqual(models["amesim_pnch012"].library.id, "amesim")
|
||||
self.assertEqual(models["amesim_pnor001"].library.id, "amesim")
|
||||
|
||||
Reference in new issue
Block a user