初版:实现 AMESim 机械因果化与事件求解
初步支持 MECMAS21 刚性质量状态归并、端止事件、恢复系数,以及 LSTP 接触和压力流量显式因果化。 已知问题:显式传播仍会重复扫描全网方程,长时刚性仿真性能待优化;自适应积分器遇到越出物理域的试探状态时,尚未实现恢复并缩步重试。
This commit is contained in:
1 parent
de265cdde6
commit
971e8f2336
9 files changed
+2808
-169
No files matched your search
@@ -0,0 +1,216 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from math import exp
|
||||
import unittest
|
||||
|
||||
from app.simulation.components.amesim.mechanical.translational import (
|
||||
AmesimF000,
|
||||
AmesimLstp00a,
|
||||
AmesimMecmas21,
|
||||
)
|
||||
from app.simulation.core.base import AlgebraicComponent
|
||||
from app.simulation.core.equations import EquationResidual
|
||||
from app.simulation.core.medium import IdealGasMedium
|
||||
from app.simulation.core.ports import PortDefinition
|
||||
from app.simulation.solvers.algebraic import PressureFlowSolver
|
||||
from app.simulation.systems.network import SimulationNetwork
|
||||
|
||||
|
||||
class _PressureCoupledMechanicalLoad(AlgebraicComponent):
|
||||
PORTS = (
|
||||
PortDefinition.mechanical_translational("mechanical"),
|
||||
PortDefinition.pneumatic("pneumatic"),
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
*,
|
||||
initial_force: float,
|
||||
solved_force: float,
|
||||
displacement: float,
|
||||
) -> None:
|
||||
super().__init__(name=name)
|
||||
self.solved_force = float(solved_force)
|
||||
self.mechanical = self.register_declared_port("mechanical")
|
||||
self.pneumatic = self.register_declared_port("pneumatic")
|
||||
self.mechanical.x = float(displacement)
|
||||
self.pneumatic.p = float(initial_force)
|
||||
|
||||
def pressure_flow_equation_residuals(self) -> tuple[EquationResidual, ...]:
|
||||
return (
|
||||
EquationResidual(
|
||||
id=f"{self.name}:velocity_state",
|
||||
owner="component",
|
||||
owner_id=self.name,
|
||||
relation="state",
|
||||
variables=(f"{self.name}.mechanical.v",),
|
||||
role="effort",
|
||||
value=self.mechanical.v,
|
||||
),
|
||||
EquationResidual(
|
||||
id=f"{self.name}:pressure_force",
|
||||
owner="component",
|
||||
owner_id=self.name,
|
||||
relation="constitutive",
|
||||
variables=(
|
||||
f"{self.name}.mechanical.f",
|
||||
f"{self.name}.pneumatic.p",
|
||||
),
|
||||
role="flow",
|
||||
value=self.mechanical.f - self.pneumatic.p,
|
||||
),
|
||||
EquationResidual(
|
||||
id=f"{self.name}:pressure_closure",
|
||||
owner="component",
|
||||
owner_id=self.name,
|
||||
relation="constitutive",
|
||||
variables=(f"{self.name}.pneumatic.p",),
|
||||
role="effort",
|
||||
value=self.pneumatic.p - self.solved_force,
|
||||
),
|
||||
EquationResidual(
|
||||
id=f"{self.name}:zero_mass_flow",
|
||||
owner="component",
|
||||
owner_id=self.name,
|
||||
relation="constitutive",
|
||||
variables=(f"{self.name}.pneumatic.m_flow",),
|
||||
role="flow",
|
||||
value=self.pneumatic.m_flow,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class _PrescribedMechanicalLoad(AlgebraicComponent):
|
||||
PORTS = (PortDefinition.mechanical_translational("port_1"),)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
*,
|
||||
force: float,
|
||||
displacement: float,
|
||||
velocity: float,
|
||||
) -> None:
|
||||
super().__init__(name=name)
|
||||
self.force = float(force)
|
||||
self.velocity = float(velocity)
|
||||
self.port_1 = self.register_declared_port("port_1")
|
||||
self.port_1.x = float(displacement)
|
||||
|
||||
def pressure_flow_equation_residuals(self) -> tuple[EquationResidual, ...]:
|
||||
return (
|
||||
EquationResidual(
|
||||
id=f"{self.name}:velocity_state",
|
||||
owner="component",
|
||||
owner_id=self.name,
|
||||
relation="state",
|
||||
variables=(f"{self.name}.port_1.v",),
|
||||
role="effort",
|
||||
value=self.port_1.v - self.velocity,
|
||||
),
|
||||
EquationResidual(
|
||||
id=f"{self.name}:force",
|
||||
owner="component",
|
||||
owner_id=self.name,
|
||||
relation="constitutive",
|
||||
variables=(f"{self.name}.port_1.f",),
|
||||
role="flow",
|
||||
value=self.port_1.f - self.force,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class ContactSolverCausalizationTests(unittest.TestCase):
|
||||
def test_nonlinear_binding_tracks_sub_ulp_force_change(self) -> None:
|
||||
medium = IdealGasMedium()
|
||||
load = _PressureCoupledMechanicalLoad(
|
||||
"load",
|
||||
initial_force=40.0,
|
||||
solved_force=41.0,
|
||||
displacement=1.0e9,
|
||||
)
|
||||
contact = AmesimLstp00a(
|
||||
"contact",
|
||||
medium,
|
||||
gap0=0.0,
|
||||
kcont=1.0e11,
|
||||
rcont=0.0,
|
||||
Pdis=1.0e-7,
|
||||
discContactOption=1.0,
|
||||
)
|
||||
mass = AmesimMecmas21(
|
||||
"mass",
|
||||
medium,
|
||||
mass=1.0,
|
||||
useFriction=0.0,
|
||||
stoptype=4.0,
|
||||
x0=1.0e9,
|
||||
v0=0.0,
|
||||
)
|
||||
zero = AmesimF000("zero")
|
||||
|
||||
network = SimulationNetwork("sub-ulp-dynamic-contact-binding")
|
||||
for component in (load, contact, mass, zero):
|
||||
network.add_component(component)
|
||||
network.connect("load", "mechanical", "contact", "port_1")
|
||||
network.connect("contact", "port_2", "mass", "port_1")
|
||||
network.connect("mass", "port_2", "zero", "port_1")
|
||||
|
||||
self.assertTrue(network.pressure_flow_structure_dict()["isSquare"])
|
||||
diagnostics = PressureFlowSolver(network).solve()
|
||||
|
||||
self.assertTrue(diagnostics.success, diagnostics.message)
|
||||
self.assertGreater(diagnostics.evaluations, 0)
|
||||
self.assertAlmostEqual(load.pneumatic.p, 41.0, delta=1.0e-3)
|
||||
self.assertAlmostEqual(load.mechanical.f, 41.0, delta=1.0e-3)
|
||||
self.assertAlmostEqual(contact.contact_force, 41.0, delta=1.0e-3)
|
||||
self.assertAlmostEqual(contact.penetration, 4.1e-10, delta=1.0e-14)
|
||||
|
||||
def test_negative_contact_binding_uses_nearest_feasible_root(self) -> None:
|
||||
medium = IdealGasMedium()
|
||||
expected_force = 10.0 - 20.0 * (1.0 - exp(-1.0))
|
||||
load = _PrescribedMechanicalLoad(
|
||||
"load",
|
||||
force=expected_force,
|
||||
displacement=0.0,
|
||||
velocity=0.0,
|
||||
)
|
||||
contact = AmesimLstp00a(
|
||||
"contact",
|
||||
medium,
|
||||
gap0=0.0,
|
||||
kcont=100.0,
|
||||
rcont=10.0,
|
||||
Pdis=0.1,
|
||||
discContactOption=1.0,
|
||||
)
|
||||
mass = AmesimMecmas21(
|
||||
"mass",
|
||||
medium,
|
||||
mass=1.0,
|
||||
useFriction=0.0,
|
||||
stoptype=4.0,
|
||||
x0=0.08,
|
||||
v0=-2.0,
|
||||
)
|
||||
zero = AmesimF000("zero")
|
||||
|
||||
network = SimulationNetwork("negative-contact-force-binding")
|
||||
for component in (load, contact, mass, zero):
|
||||
network.add_component(component)
|
||||
network.connect("load", "port_1", "contact", "port_1")
|
||||
network.connect("contact", "port_2", "mass", "port_1")
|
||||
network.connect("mass", "port_2", "zero", "port_1")
|
||||
|
||||
self.assertTrue(network.pressure_flow_structure_dict()["isSquare"])
|
||||
diagnostics = PressureFlowSolver(network).solve()
|
||||
|
||||
self.assertTrue(diagnostics.success, diagnostics.message)
|
||||
self.assertLess(contact.contact_force, 0.0)
|
||||
self.assertAlmostEqual(contact.contact_force, expected_force, places=10)
|
||||
self.assertAlmostEqual(contact.penetration, 0.1, places=10)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user