完成求解器雅可比矩阵首轮优化,增加更新目录,整理了文档文件夹,增加了服务启动脚本
This commit is contained in:
1 parent
6bb0591d32
commit
16a7eb2d6c
48 files changed
+8172
-217
No files matched your search
@@ -0,0 +1,474 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from math import exp
|
||||
|
||||
from app.simulation.components.amesim.flow.pipes import AmesimPnl0001
|
||||
from app.simulation.components.amesim.media.mediums import (
|
||||
AmesimHeliumPengRobinsonMedium,
|
||||
)
|
||||
from app.simulation.components.amesim.mechanical.pistons import AmesimPnrp17
|
||||
from app.simulation.components.amesim.mechanical.translational import (
|
||||
AmesimLstp00a,
|
||||
AmesimMecmas21,
|
||||
)
|
||||
from app.simulation.components.amesim.storage.chambers import AmesimPnch012
|
||||
from app.simulation.core.medium import IdealGasMedium
|
||||
|
||||
|
||||
class AnalyticTangentPrimitiveTests(unittest.TestCase):
|
||||
def assert_tangent_close(
|
||||
self,
|
||||
actual: float,
|
||||
expected: float,
|
||||
*,
|
||||
relative: float = 5.0e-5,
|
||||
absolute: float = 1.0e-8,
|
||||
) -> None:
|
||||
self.assertAlmostEqual(
|
||||
actual,
|
||||
expected,
|
||||
delta=max(absolute, relative * max(abs(actual), abs(expected))),
|
||||
)
|
||||
|
||||
def test_peng_robinson_m_u_v_linearization_matches_centered_difference(self) -> None:
|
||||
medium = AmesimHeliumPengRobinsonMedium()
|
||||
pressure = 15.3e6
|
||||
temperature = 293.15
|
||||
volume = 0.01
|
||||
mass = medium.density(pressure, temperature) * volume
|
||||
energy = mass * medium.specific_internal_energy_at_pressure(
|
||||
pressure,
|
||||
temperature,
|
||||
)
|
||||
linearization = medium.linearize_properties_from_mU(
|
||||
mass,
|
||||
energy,
|
||||
volume,
|
||||
(1.0, 0.0, 0.0),
|
||||
(0.0, 1.0, 0.0),
|
||||
(0.0, 0.0, 1.0),
|
||||
)
|
||||
self.assertTrue(linearization.valid, linearization.reason)
|
||||
|
||||
arguments = (mass, energy, volume)
|
||||
steps = (mass * 1.0e-6, abs(energy) * 1.0e-6, volume * 1.0e-6)
|
||||
for direction, step in enumerate(steps):
|
||||
lower = list(arguments)
|
||||
upper = list(arguments)
|
||||
lower[direction] -= step
|
||||
upper[direction] += step
|
||||
lower_props = medium.properties_from_mU(*lower)
|
||||
upper_props = medium.properties_from_mU(*upper)
|
||||
for field in ("p", "T", "rho", "u", "h"):
|
||||
finite_difference = (
|
||||
getattr(upper_props, field) - getattr(lower_props, field)
|
||||
) / (2.0 * step)
|
||||
tangent = getattr(linearization.tangents, field)[direction]
|
||||
self.assert_tangent_close(
|
||||
tangent,
|
||||
finite_difference,
|
||||
relative=2.0e-4,
|
||||
absolute=1.0e-6,
|
||||
)
|
||||
|
||||
def test_pnrp17_geometry_and_force_tangent_is_exact(self) -> None:
|
||||
piston = AmesimPnrp17("piston", IdealGasMedium(), dp=0.2, dr=0.01, x0=0.1)
|
||||
piston.port_4.x = 0.02
|
||||
piston.port_5.x = 0.05
|
||||
piston.port_4.v = -0.2
|
||||
piston.port_5.v = 0.3
|
||||
piston.port_1.p = 2.0e5
|
||||
result = piston.linearize_geometry_and_force(
|
||||
(1.0, 0.0),
|
||||
(0.0, 2.0),
|
||||
(3.0, 0.0),
|
||||
(0.0, 4.0),
|
||||
(5.0, 6.0),
|
||||
)
|
||||
area = piston.effective_area
|
||||
self.assertTrue(result.valid)
|
||||
self.assertEqual(result.volume_tangent, (-area, 2.0 * area))
|
||||
self.assertEqual(result.volume_flow_tangent, (-3.0 * area, 4.0 * area))
|
||||
self.assertEqual(result.pressure_force_tangent, (5.0 * area, 6.0 * area))
|
||||
|
||||
def test_pnch012_balance_tangent_matches_directional_difference(self) -> None:
|
||||
chamber = AmesimPnch012(
|
||||
"chamber",
|
||||
IdealGasMedium(),
|
||||
cvol0=0.02,
|
||||
kth=3.0,
|
||||
sth=0.4,
|
||||
p0=2.0e5,
|
||||
T0=300.0,
|
||||
)
|
||||
chamber.port_1.volume = 0.003
|
||||
chamber.port_1.volume_flow = 2.0e-4
|
||||
flows = (0.02, -0.01, 0.005, -0.004)
|
||||
enthalpies = {
|
||||
"port_1": 330000.0,
|
||||
"port_2": 310000.0,
|
||||
"port_3": 320000.0,
|
||||
"port_4": 300000.0,
|
||||
}
|
||||
for port_name, flow in zip(
|
||||
("port_1", "port_2", "port_3", "port_4"),
|
||||
flows,
|
||||
strict=True,
|
||||
):
|
||||
chamber.get_port(port_name).m_flow = flow
|
||||
|
||||
dm = (1.0e-5,)
|
||||
dU = (2.0,)
|
||||
dV = (3.0e-6,)
|
||||
dVdt = (-4.0e-5,)
|
||||
dq = {
|
||||
"port_1": (2.0e-3,),
|
||||
"port_2": (-1.0e-3,),
|
||||
"port_3": (3.0e-3,),
|
||||
"port_4": (-2.0e-3,),
|
||||
}
|
||||
dh = {
|
||||
"port_1": (20.0,),
|
||||
"port_2": (-10.0,),
|
||||
"port_3": (30.0,),
|
||||
"port_4": (-20.0,),
|
||||
}
|
||||
result = chamber.linearize_state_derivative(
|
||||
enthalpies,
|
||||
state_mass_tangent=dm,
|
||||
state_energy_tangent=dU,
|
||||
external_volume_tangent=dV,
|
||||
external_volume_rate_tangent=dVdt,
|
||||
port_mass_flow_tangents=dq,
|
||||
connected_h_tangents=dh,
|
||||
)
|
||||
self.assertTrue(result.valid, result.reason)
|
||||
|
||||
original_state = chamber.get_state_vector()
|
||||
original_volume = chamber.port_1.volume
|
||||
original_volume_flow = chamber.port_1.volume_flow
|
||||
epsilon = 1.0e-4
|
||||
|
||||
def evaluate(sign: float) -> list[float]:
|
||||
chamber.set_state_vector(
|
||||
[
|
||||
original_state[0] + sign * epsilon * dm[0],
|
||||
original_state[1] + sign * epsilon * dU[0],
|
||||
]
|
||||
)
|
||||
chamber.port_1.volume = original_volume + sign * epsilon * dV[0]
|
||||
chamber.port_1.volume_flow = (
|
||||
original_volume_flow + sign * epsilon * dVdt[0]
|
||||
)
|
||||
perturbed_h = {}
|
||||
for port_name in enthalpies:
|
||||
port = chamber.get_port(port_name)
|
||||
base_flow = flows[int(port_name[-1]) - 1]
|
||||
port.m_flow = base_flow + sign * epsilon * dq[port_name][0]
|
||||
perturbed_h[port_name] = (
|
||||
enthalpies[port_name] + sign * epsilon * dh[port_name][0]
|
||||
)
|
||||
return chamber.state_derivative_from_ports(perturbed_h)
|
||||
|
||||
lower = evaluate(-1.0)
|
||||
upper = evaluate(1.0)
|
||||
chamber.set_state_vector(original_state)
|
||||
chamber.port_1.volume = original_volume
|
||||
chamber.port_1.volume_flow = original_volume_flow
|
||||
for port_name, flow in zip(
|
||||
("port_1", "port_2", "port_3", "port_4"),
|
||||
flows,
|
||||
strict=True,
|
||||
):
|
||||
chamber.get_port(port_name).m_flow = flow
|
||||
for row in range(2):
|
||||
finite_difference = (upper[row] - lower[row]) / (2.0 * epsilon)
|
||||
self.assert_tangent_close(
|
||||
result.tangents[row][0],
|
||||
finite_difference,
|
||||
relative=1.0e-5,
|
||||
absolute=1.0e-6,
|
||||
)
|
||||
|
||||
def test_pnl0001_local_flow_slope_and_balance_tangent(self) -> None:
|
||||
medium = IdealGasMedium()
|
||||
pipe = AmesimPnl0001(
|
||||
"pipe",
|
||||
medium,
|
||||
diam=0.02,
|
||||
le=0.5,
|
||||
kth=2.0,
|
||||
p0=2.0e5,
|
||||
T0=300.0,
|
||||
)
|
||||
slope = pipe.linearize_mass_flow(2.2e5, 1.8e5, 300.0)
|
||||
self.assertTrue(slope.valid, slope.reason)
|
||||
self.assertGreater(slope.partial_p_1, 0.0)
|
||||
self.assertLess(slope.partial_p_2, 0.0)
|
||||
self.assertLess(slope.partial_temperature, 0.0)
|
||||
boundary = pipe.linearize_mass_flow(2.0e5, 2.0e5, 300.0)
|
||||
self.assertFalse(boundary.valid)
|
||||
self.assertEqual(boundary.reason, "flow_direction_boundary")
|
||||
|
||||
pipe.port_1.m_flow = 0.02
|
||||
pipe.port_2.m_flow = -0.01
|
||||
connected_h = {"port_1": 330000.0, "port_2": 310000.0}
|
||||
dm = (1.0e-6,)
|
||||
dU = (0.3,)
|
||||
dq = {"port_1": (2.0e-3,), "port_2": (-1.0e-3,)}
|
||||
dh = {"port_1": (20.0,), "port_2": (-10.0,)}
|
||||
result = pipe.linearize_state_derivative(
|
||||
connected_h,
|
||||
state_mass_tangent=dm,
|
||||
state_energy_tangent=dU,
|
||||
port_mass_flow_tangents=dq,
|
||||
connected_h_tangents=dh,
|
||||
)
|
||||
self.assertTrue(result.valid, result.reason)
|
||||
|
||||
original_state = pipe.get_state_vector()
|
||||
epsilon = 1.0e-4
|
||||
|
||||
def evaluate(sign: float) -> list[float]:
|
||||
pipe.set_state_vector(
|
||||
[
|
||||
original_state[0] + sign * epsilon * dm[0],
|
||||
original_state[1] + sign * epsilon * dU[0],
|
||||
]
|
||||
)
|
||||
pipe.port_1.m_flow = 0.02 + sign * epsilon * dq["port_1"][0]
|
||||
pipe.port_2.m_flow = -0.01 + sign * epsilon * dq["port_2"][0]
|
||||
return pipe.state_derivative_from_ports(
|
||||
{
|
||||
name: value + sign * epsilon * dh[name][0]
|
||||
for name, value in connected_h.items()
|
||||
}
|
||||
)
|
||||
|
||||
lower = evaluate(-1.0)
|
||||
upper = evaluate(1.0)
|
||||
for row in range(2):
|
||||
finite_difference = (upper[row] - lower[row]) / (2.0 * epsilon)
|
||||
self.assert_tangent_close(
|
||||
result.tangents[row][0],
|
||||
finite_difference,
|
||||
relative=1.0e-5,
|
||||
absolute=1.0e-6,
|
||||
)
|
||||
|
||||
def test_lstp_fixed_mode_tangent_and_boundary_guard(self) -> None:
|
||||
contact = AmesimLstp00a(
|
||||
"contact",
|
||||
IdealGasMedium(),
|
||||
gap0=0.001,
|
||||
kcont=2000.0,
|
||||
rcont=10.0,
|
||||
Pdis=0.0005,
|
||||
)
|
||||
contact.port_1.x = 0.002
|
||||
contact.port_2.x = 0.0
|
||||
contact.port_1.v = 0.3
|
||||
contact.port_2.v = -0.1
|
||||
result = contact.linearize_contact_force(
|
||||
(0.4,),
|
||||
(-0.2,),
|
||||
(0.3,),
|
||||
(-0.1,),
|
||||
)
|
||||
self.assertTrue(result.valid, result.reason)
|
||||
epsilon = 1.0e-7
|
||||
originals = (
|
||||
contact.port_1.x,
|
||||
contact.port_2.x,
|
||||
contact.port_1.v,
|
||||
contact.port_2.v,
|
||||
)
|
||||
|
||||
def evaluate(sign: float) -> float:
|
||||
contact.port_1.x = originals[0] + sign * epsilon * 0.4
|
||||
contact.port_2.x = originals[1] + sign * epsilon * -0.2
|
||||
contact.port_1.v = originals[2] + sign * epsilon * 0.3
|
||||
contact.port_2.v = originals[3] + sign * epsilon * -0.1
|
||||
return contact.contact_force
|
||||
|
||||
finite_difference = (evaluate(1.0) - evaluate(-1.0)) / (2.0 * epsilon)
|
||||
self.assert_tangent_close(result.force_tangent[0], finite_difference)
|
||||
|
||||
contact.clear_causal_contact()
|
||||
contact.port_1.x = contact.gap0
|
||||
contact.port_2.x = 0.0
|
||||
boundary = contact.linearize_contact_force(
|
||||
(1.0,),
|
||||
(0.0,),
|
||||
(0.0,),
|
||||
(0.0,),
|
||||
)
|
||||
self.assertFalse(boundary.valid)
|
||||
self.assertEqual(boundary.reason, "contact_mode_boundary")
|
||||
|
||||
def test_lstp_causal_contact_retains_a_differentiable_local_mode(self) -> None:
|
||||
contact = AmesimLstp00a(
|
||||
"causal_contact",
|
||||
IdealGasMedium(),
|
||||
gap0=0.0,
|
||||
kcont=3000.0,
|
||||
rcont=12.0,
|
||||
Pdis=0.001,
|
||||
)
|
||||
contact.port_1.x = 1000.0
|
||||
contact.port_2.x = 1000.0
|
||||
contact.port_1.v = 0.2
|
||||
contact.port_2.v = -0.1
|
||||
penetration = 0.002
|
||||
damping_fraction = 1.0 - exp(-penetration / contact.Pdis)
|
||||
force = (
|
||||
contact.kcont * penetration
|
||||
+ damping_fraction * contact.rcont * contact.penetration_velocity
|
||||
)
|
||||
contact.set_causal_contact(penetration=penetration, force=force)
|
||||
result = contact.linearize_contact_force(
|
||||
(0.3,),
|
||||
(-0.2,),
|
||||
(0.4,),
|
||||
(-0.1,),
|
||||
)
|
||||
self.assertTrue(result.valid, result.reason)
|
||||
epsilon = 1.0e-6
|
||||
originals = (
|
||||
contact.port_1.x,
|
||||
contact.port_2.x,
|
||||
contact.port_1.v,
|
||||
contact.port_2.v,
|
||||
)
|
||||
|
||||
def evaluate(sign: float) -> float:
|
||||
contact.port_1.x = originals[0] + sign * epsilon * 0.3
|
||||
contact.port_2.x = originals[1] + sign * epsilon * -0.2
|
||||
contact.port_1.v = originals[2] + sign * epsilon * 0.4
|
||||
contact.port_2.v = originals[3] + sign * epsilon * -0.1
|
||||
return contact.contact_force
|
||||
|
||||
finite_difference = (evaluate(1.0) - evaluate(-1.0)) / (2.0 * epsilon)
|
||||
self.assert_tangent_close(
|
||||
result.force_tangent[0],
|
||||
finite_difference,
|
||||
relative=2.0e-5,
|
||||
)
|
||||
|
||||
def test_mecmas_soft_endstop_tangents_match_centered_difference(self) -> None:
|
||||
cases = (
|
||||
{
|
||||
"name": "lower",
|
||||
"x0": -0.02,
|
||||
"xmin": 0.0,
|
||||
"xmax": 1.0,
|
||||
},
|
||||
{
|
||||
"name": "upper",
|
||||
"x0": 1.02,
|
||||
"xmin": 0.0,
|
||||
"xmax": 1.0,
|
||||
},
|
||||
)
|
||||
for case in cases:
|
||||
with self.subTest(side=case["name"]):
|
||||
mass = AmesimMecmas21(
|
||||
f"mass_{case['name']}",
|
||||
IdealGasMedium(),
|
||||
mass=2.0,
|
||||
useFriction=1.0,
|
||||
stoptype=2.0,
|
||||
discContactOption=1.0,
|
||||
Kbmin=1000.0,
|
||||
Dbmin=20.0,
|
||||
Pdmin=0.1,
|
||||
Kbmax=1200.0,
|
||||
Dbmax=30.0,
|
||||
Pdmax=0.1,
|
||||
v0=0.3,
|
||||
x0=case["x0"],
|
||||
xmin=case["xmin"],
|
||||
xmax=case["xmax"],
|
||||
)
|
||||
mass.port_1.f = 5.0
|
||||
mass.port_2.f = -1.0
|
||||
result = mass.linearize_state_derivative(
|
||||
(0.2,),
|
||||
(-0.1,),
|
||||
(-0.25,),
|
||||
(0.4,),
|
||||
constraint_mode="free",
|
||||
)
|
||||
self.assertTrue(result.valid, result.reason)
|
||||
epsilon = 1.0e-7
|
||||
original_v = mass.v
|
||||
original_x = mass.x
|
||||
|
||||
def evaluate(sign: float) -> float:
|
||||
mass.v = original_v + sign * epsilon * -0.25
|
||||
mass.x = original_x + sign * epsilon * 0.4
|
||||
mass.port_1.f = 5.0 + sign * epsilon * 0.2
|
||||
mass.port_2.f = -1.0 + sign * epsilon * -0.1
|
||||
return mass.unconstrained_acceleration()
|
||||
|
||||
finite_difference = (
|
||||
evaluate(1.0) - evaluate(-1.0)
|
||||
) / (2.0 * epsilon)
|
||||
self.assert_tangent_close(
|
||||
result.tangents[0][0],
|
||||
finite_difference,
|
||||
relative=1.0e-6,
|
||||
)
|
||||
|
||||
def test_mecmas_free_and_fixed_mode_tangents(self) -> None:
|
||||
mass = AmesimMecmas21(
|
||||
"mass",
|
||||
IdealGasMedium(),
|
||||
mass=2.0,
|
||||
useFriction=2.0,
|
||||
rvisc=3.0,
|
||||
wind=0.5,
|
||||
fcoul=0.0,
|
||||
stoptype=4.0,
|
||||
v0=2.0,
|
||||
)
|
||||
mass.port_1.f = 10.0
|
||||
mass.port_2.f = -2.0
|
||||
result = mass.linearize_state_derivative(
|
||||
(0.3,),
|
||||
(-0.1,),
|
||||
(0.2,),
|
||||
(0.4,),
|
||||
constraint_mode="free",
|
||||
)
|
||||
self.assertTrue(result.valid, result.reason)
|
||||
epsilon = 1.0e-6
|
||||
original_v = mass.v
|
||||
original_x = mass.x
|
||||
|
||||
def evaluate(sign: float) -> float:
|
||||
mass.v = original_v + sign * epsilon * 0.2
|
||||
mass.x = original_x + sign * epsilon * 0.4
|
||||
mass.port_1.f = 10.0 + sign * epsilon * 0.3
|
||||
mass.port_2.f = -2.0 + sign * epsilon * -0.1
|
||||
return mass.unconstrained_acceleration()
|
||||
|
||||
finite_difference = (evaluate(1.0) - evaluate(-1.0)) / (2.0 * epsilon)
|
||||
self.assert_tangent_close(result.tangents[0][0], finite_difference)
|
||||
self.assertEqual(result.tangents[1], (0.2,))
|
||||
|
||||
mass.set_constraint_motion(0.0, velocity=0.0)
|
||||
fixed = mass.linearize_state_derivative(
|
||||
(1.0,),
|
||||
(1.0,),
|
||||
(1.0,),
|
||||
(1.0,),
|
||||
constraint_mode="lower",
|
||||
)
|
||||
self.assertTrue(fixed.valid, fixed.reason)
|
||||
self.assertEqual(fixed.tangents, ((0.0,), (0.0,)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user