完成求解器雅可比矩阵首轮优化,增加更新目录,整理了文档文件夹,增加了服务启动脚本

This commit is contained in:
lujingze committed 2026-08-17 07:33:31 +00:00
1 parent 6bb0591d32
commit 16a7eb2d6c
48 files changed
+8172 -217

No files matched your search

@@ -1,7 +1,8 @@
from __future__ import annotations
from collections.abc import Mapping
from math import pi
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from math import isfinite, pi
from app.simulation.components.amesim.gases import (
AMESIM_GAS_INDEX_PARAMETER,
@@ -18,6 +19,18 @@ from app.simulation.core.ports import PortDefinition
AMESIM_REFERENCE_PRESSURE_PA = 101300.0
@dataclass(frozen=True)
class Pnrp17Linearization:
volume: float
volume_flow: float
pressure_force: float
volume_tangent: tuple[float, ...]
volume_flow_tangent: tuple[float, ...]
pressure_force_tangent: tuple[float, ...]
valid: bool = True
reason: str | None = None
class AmesimPnrp17(AlgebraicComponent):
"""AMESim PNRP17 pneumatic piston with two mechanical faces.
@@ -230,6 +243,53 @@ class AmesimPnrp17(AlgebraicComponent):
def pneumatic_volume_outputs(self) -> Mapping[str, tuple[float, float]]:
return {"port_1": (self.chamber_volume, self.chamber_volume_flow)}
def linearize_geometry_and_force(
self,
port_4_x_tangent: Sequence[float],
port_5_x_tangent: Sequence[float],
port_4_v_tangent: Sequence[float],
port_5_v_tangent: Sequence[float],
port_1_pressure_tangent: Sequence[float],
) -> Pnrp17Linearization:
"""Return exact piston geometry and pressure-force tangents."""
vectors = tuple(
tuple(float(value) for value in values)
for values in (
port_4_x_tangent,
port_5_x_tangent,
port_4_v_tangent,
port_5_v_tangent,
port_1_pressure_tangent,
)
)
widths = {len(values) for values in vectors}
if len(widths) != 1:
raise ValueError("PNRP17 tangent vectors must have equal lengths.")
valid = all(isfinite(value) for values in vectors for value in values)
area = self.effective_area
volume_tangent = tuple(
area * (right - left)
for left, right in zip(vectors[0], vectors[1], strict=True)
)
volume_flow_tangent = tuple(
area * (right - left)
for left, right in zip(vectors[2], vectors[3], strict=True)
)
pressure_force_tangent = tuple(
area * value for value in vectors[4]
)
return Pnrp17Linearization(
volume=self.chamber_volume,
volume_flow=self.chamber_volume_flow,
pressure_force=self.pressure_force,
volume_tangent=volume_tangent,
volume_flow_tangent=volume_flow_tangent,
pressure_force_tangent=pressure_force_tangent,
valid=valid,
reason=None if valid else "non_finite_tangent_input",
)
def update_stream_outflows(self, connected_h: Mapping[str, float]) -> None:
self.port_1.h_outflow = connected_h.get(
"port_1",
@@ -1,7 +1,8 @@
from __future__ import annotations
from collections.abc import Mapping
from math import expm1
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from math import expm1, isfinite
from app.simulation.core.base import AlgebraicComponent, DynamicComponent
from app.simulation.core.catalog import (
@@ -20,6 +21,24 @@ from app.simulation.core.medium import IdealGasMedium
from app.simulation.core.ports import PortDefinition
@dataclass(frozen=True)
class Mecmas21DerivativeLinearization:
derivative: tuple[float, float]
tangents: tuple[tuple[float, ...], tuple[float, ...]]
mode: str
valid: bool = True
reason: str | None = None
@dataclass(frozen=True)
class LstpContactForceLinearization:
force: float
force_tangent: tuple[float, ...]
mode: str
valid: bool = True
reason: str | None = None
_MECMAS21_FRICTION_ENABLED = ParameterCondition("useFriction", (2.0,))
_MECMAS21_NON_RESTITUTION = ParameterCondition("stoptype", (1.0, 2.0, 4.0))
_MECMAS21_LIMITS_ENABLED = ParameterCondition("stoptype", (1.0, 2.0, 3.0))
@@ -723,6 +742,204 @@ class AmesimMecmas21(DynamicComponent):
)
return [self.acceleration(), velocity]
def linearize_state_derivative(
self,
port_1_force_tangent: Sequence[float],
port_2_force_tangent: Sequence[float],
velocity_tangent: Sequence[float],
position_tangent: Sequence[float],
*,
constraint_mode: str = "current",
boundary_tolerance: float = 1.0e-12,
) -> Mecmas21DerivativeLinearization:
"""Linearize one inertia in a declared fixed mechanical mode."""
vectors = tuple(
tuple(float(value) for value in values)
for values in (
port_1_force_tangent,
port_2_force_tangent,
velocity_tangent,
position_tangent,
)
)
widths = {len(values) for values in vectors}
if len(widths) != 1:
raise ValueError("MECMAS21 tangent vectors must have equal lengths.")
width = len(vectors[0])
invalid_reason: str | None = None
if not all(isfinite(value) for values in vectors for value in values):
invalid_reason = "non_finite_tangent_input"
requested_mode = constraint_mode
if requested_mode == "current":
fixed = (
self._constraint_acceleration == 0.0
and self._constraint_velocity == 0.0
)
mode = "fixed" if fixed else "free"
if self._constraint_acceleration is not None and not fixed:
invalid_reason = invalid_reason or (
"group_acceleration_requires_aggregate"
)
elif requested_mode == "free":
mode = "free"
elif requested_mode in {"lower", "upper"}:
mode = requested_mode
fixed = (
self._constraint_acceleration == 0.0
and self._constraint_velocity == 0.0
)
if not fixed:
invalid_reason = invalid_reason or (
"constraint_mode_not_statically_fixed"
)
elif requested_mode == "uninitialized":
mode = requested_mode
invalid_reason = invalid_reason or "constraint_mode_uninitialized"
else:
raise ValueError(
"MECMAS21 constraint_mode must be current, free, lower, upper, "
"or uninitialized."
)
if mode in {"fixed", "lower", "upper"}:
return Mecmas21DerivativeLinearization(
derivative=(self.acceleration(), 0.0),
tangents=((0.0,) * width, (0.0,) * width),
mode=mode,
valid=invalid_reason is None,
reason=invalid_reason,
)
force_1_tangent, force_2_tangent, dv, dx = vectors
acceleration_tangent = [
force_1_tangent[index] + force_2_tangent[index]
for index in range(width)
]
if self.use_friction:
for index in range(width):
acceleration_tangent[index] += (
-self.rvisc * dv[index]
- 2.0 * self.wind * abs(self.v) * dv[index]
)
if (
self.fcoul != 0.0
and abs(self.v) <= boundary_tolerance
and any(value != 0.0 for value in dv)
):
invalid_reason = invalid_reason or "dry_friction_direction_boundary"
def add_limit_tangent(
*,
side: str,
stiffness: float,
damping: float,
damping_penetration: float,
bound: float,
damping_sign: float,
force_sign: float,
) -> None:
nonlocal invalid_reason
if int(self.stoptype) != 2:
return
penetration = (
bound - self.x if side == "lower" else self.x - bound
)
penetration_tangent = tuple(
(-value if side == "lower" else value) for value in dx
)
scale = max(abs(bound), abs(self.x), 1.0)
if penetration <= 0.0:
if (
abs(penetration) <= boundary_tolerance * scale
and any(value != 0.0 for value in penetration_tangent)
):
invalid_reason = invalid_reason or (
f"soft_endstop_mode_boundary:{side}"
)
return
if damping_penetration > 0.0:
fraction = min(penetration / damping_penetration, 1.0)
if penetration < damping_penetration:
fraction_tangent = tuple(
value / damping_penetration
for value in penetration_tangent
)
else:
fraction_tangent = (0.0,) * width
if (
abs(penetration - damping_penetration)
<= boundary_tolerance
* max(abs(damping_penetration), 1.0)
and any(value != 0.0 for value in penetration_tangent)
):
invalid_reason = invalid_reason or (
f"soft_endstop_damping_boundary:{side}"
)
else:
fraction = 1.0
fraction_tangent = (0.0,) * width
raw_force = (
stiffness * penetration
+ damping_sign * fraction * damping * self.v
)
raw_tangent = tuple(
stiffness * penetration_tangent[index]
+ damping_sign
* damping
* (
fraction * dv[index]
+ self.v * fraction_tangent[index]
)
for index in range(width)
)
if int(self.discContactOption) != 1 and raw_force <= 0.0:
if (
abs(raw_force)
<= boundary_tolerance
* max(abs(stiffness * penetration), 1.0)
and any(value != 0.0 for value in raw_tangent)
):
invalid_reason = invalid_reason or (
f"soft_endstop_force_boundary:{side}"
)
return
for index in range(width):
acceleration_tangent[index] += (
force_sign * raw_tangent[index]
)
add_limit_tangent(
side="lower",
stiffness=self.Kbmin,
damping=self.Dbmin,
damping_penetration=self.Pdmin,
bound=self.xmin,
damping_sign=-1.0,
force_sign=1.0,
)
add_limit_tangent(
side="upper",
stiffness=self.Kbmax,
damping=self.Dbmax,
damping_penetration=self.Pdmax,
bound=self.xmax,
damping_sign=1.0,
force_sign=-1.0,
)
acceleration_tangent = tuple(
value / self.mass for value in acceleration_tangent
)
return Mecmas21DerivativeLinearization(
derivative=(self.unconstrained_acceleration(), self.v),
tangents=(acceleration_tangent, tuple(dv)),
mode=mode,
valid=invalid_reason is None,
reason=invalid_reason,
)
def component_result_values(self) -> Mapping[str, float]:
return {
"a": self.acceleration(),
@@ -979,6 +1196,112 @@ class AmesimLstp00a(AlgebraicComponent):
)
return force if int(self.discContactOption) == 1 else max(force, 0.0)
def linearize_contact_force(
self,
port_1_x_tangent: Sequence[float],
port_2_x_tangent: Sequence[float],
port_1_velocity_tangent: Sequence[float],
port_2_velocity_tangent: Sequence[float],
*,
boundary_tolerance: float = 1.0e-12,
) -> LstpContactForceLinearization:
"""Linearize the elastic contact in its current unilateral mode."""
vectors = tuple(
tuple(float(value) for value in values)
for values in (
port_1_x_tangent,
port_2_x_tangent,
port_1_velocity_tangent,
port_2_velocity_tangent,
)
)
widths = {len(values) for values in vectors}
if len(widths) != 1:
raise ValueError("LSTP00A tangent vectors must have equal lengths.")
width = len(vectors[0])
if not all(isfinite(value) for values in vectors for value in values):
return LstpContactForceLinearization(
force=self.contact_force,
force_tangent=(0.0,) * width,
mode="invalid",
valid=False,
reason="non_finite_tangent_input",
)
dx_1, dx_2, dv_1, dv_2 = vectors
penetration_tangent = tuple(
left - right for left, right in zip(dx_1, dx_2, strict=True)
)
velocity_tangent = tuple(
left - right for left, right in zip(dv_1, dv_2, strict=True)
)
overlap = -self.gap
force = self.contact_force
scale = max(abs(self.gap0), abs(self.port_1.x), abs(self.port_2.x), 1.0)
if overlap <= 0.0:
on_boundary = abs(overlap) <= boundary_tolerance * scale
crossing = any(value != 0.0 for value in penetration_tangent)
return LstpContactForceLinearization(
force=force,
force_tangent=(0.0,) * width,
mode="boundary" if on_boundary else "inactive",
valid=not (on_boundary and crossing),
reason=(
"contact_mode_boundary"
if on_boundary and crossing
else None
),
)
penetration = overlap
if self.Pdis > 0.0:
damping_fraction = -expm1(-penetration / self.Pdis)
damping_fraction_tangent = tuple(
(1.0 - damping_fraction) * value / self.Pdis
for value in penetration_tangent
)
else:
damping_fraction = 1.0
damping_fraction_tangent = (0.0,) * width
relative_velocity = self.penetration_velocity
raw_force = (
self.kcont * penetration
+ damping_fraction * self.rcont * relative_velocity
)
raw_tangent = tuple(
self.kcont * penetration_tangent[index]
+ self.rcont
* (
damping_fraction * velocity_tangent[index]
+ relative_velocity * damping_fraction_tangent[index]
)
for index in range(width)
)
if int(self.discContactOption) != 1 and raw_force <= 0.0:
on_boundary = (
abs(raw_force)
<= boundary_tolerance
* max(abs(self.kcont * penetration), 1.0)
)
crossing = any(value != 0.0 for value in raw_tangent)
return LstpContactForceLinearization(
force=force,
force_tangent=(0.0,) * width,
mode="force_boundary" if on_boundary else "clamped",
valid=not (on_boundary and crossing),
reason=(
"contact_force_boundary"
if on_boundary and crossing
else None
),
)
return LstpContactForceLinearization(
force=force,
force_tangent=raw_tangent,
mode="active",
)
def clear_causal_contact(self) -> None:
self._causal_penetration = None
self._causal_contact_force = None