Implement physics engine stepping
Full implementation of step() method with thermal-electrical coupling: - Chamber temperature first-order response to setpoint - Case temperature with self-heating via thermal calculations - Junction temperature from θ_jc thermal resistance - Electrical state from temperature-dependent DUT model - Default LDO model when none provided
This commit is contained in:
@@ -10,6 +10,11 @@ from __future__ import annotations
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from py_dvt_ate.simulation.physics.state import ElectricalState, ThermalState
|
||||
from py_dvt_ate.simulation.physics.thermal import (
|
||||
calculate_junction_temperature,
|
||||
update_case_temperature,
|
||||
update_temperature,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from py_dvt_ate.simulation.physics.models.base import DUTModel
|
||||
@@ -21,6 +26,12 @@ class PhysicsEngine:
|
||||
Runs at a fixed timestep, updating thermal and electrical state
|
||||
based on the DUT model and environmental conditions.
|
||||
|
||||
The simulation models:
|
||||
- Chamber temperature approaching setpoint (first-order response)
|
||||
- Case temperature driven by chamber and self-heating
|
||||
- Junction temperature from case temperature and thermal resistance
|
||||
- Electrical behaviour from the DUT model (temperature-dependent)
|
||||
|
||||
Attributes:
|
||||
dt: Simulation timestep in seconds.
|
||||
"""
|
||||
@@ -35,12 +46,19 @@ class PhysicsEngine:
|
||||
Args:
|
||||
update_rate_hz: Simulation update rate in Hz. Defaults to 100.
|
||||
dut_model: DUT model to use for electrical calculations.
|
||||
If None, a default model will be used when implemented.
|
||||
If None, a default LDO model will be used.
|
||||
"""
|
||||
self.dt = 1.0 / update_rate_hz
|
||||
self._dut = dut_model
|
||||
|
||||
# Thermal parameters (to be configured in Sprint 3)
|
||||
# Lazily import to avoid circular dependencies
|
||||
if dut_model is None:
|
||||
from py_dvt_ate.simulation.physics.models.ldo import LDOModel
|
||||
|
||||
self._dut: DUTModel = LDOModel()
|
||||
else:
|
||||
self._dut = dut_model
|
||||
|
||||
# Thermal parameters
|
||||
self._tau_chamber = 30.0 # seconds
|
||||
self._tau_case = 5.0 # seconds
|
||||
self._theta_jc = 15.0 # degC/W
|
||||
@@ -59,9 +77,34 @@ class PhysicsEngine:
|
||||
"""Advance simulation by one timestep.
|
||||
|
||||
Updates thermal and electrical state based on current conditions.
|
||||
Full implementation in Sprint 3.
|
||||
The thermal-electrical coupling works as follows:
|
||||
1. Calculate current power dissipation from DUT model
|
||||
2. Update chamber temperature towards setpoint
|
||||
3. Update case temperature including self-heating
|
||||
4. Advance simulation time
|
||||
"""
|
||||
# Stub: just advance time
|
||||
# Calculate power dissipation (uses current junction temperature estimate)
|
||||
p_diss = self._calculate_power_dissipation()
|
||||
|
||||
# Update chamber temperature (first-order response to setpoint)
|
||||
self._t_chamber = update_temperature(
|
||||
current_temperature=self._t_chamber,
|
||||
target_temperature=self._t_setpoint,
|
||||
time_constant=self._tau_chamber,
|
||||
dt=self.dt,
|
||||
)
|
||||
|
||||
# Update case temperature (driven by chamber + self-heating)
|
||||
self._t_case = update_case_temperature(
|
||||
case_temperature=self._t_case,
|
||||
ambient_temperature=self._t_chamber,
|
||||
power_dissipation=p_diss,
|
||||
time_constant=self._tau_case,
|
||||
theta_ca=self._theta_ca,
|
||||
dt=self.dt,
|
||||
)
|
||||
|
||||
# Advance simulation time
|
||||
self._sim_time += self.dt
|
||||
|
||||
def get_thermal_state(self) -> ThermalState:
|
||||
@@ -70,11 +113,17 @@ class PhysicsEngine:
|
||||
Returns:
|
||||
Immutable ThermalState with current temperatures.
|
||||
"""
|
||||
# Stub: return current state values
|
||||
p_diss = self._calculate_power_dissipation()
|
||||
t_junction = calculate_junction_temperature(
|
||||
case_temperature=self._t_case,
|
||||
power_dissipation=p_diss,
|
||||
theta_jc=self._theta_jc,
|
||||
)
|
||||
|
||||
return ThermalState(
|
||||
chamber_temperature=self._t_chamber,
|
||||
case_temperature=self._t_case,
|
||||
junction_temperature=self._t_case, # Stub: junction = case
|
||||
junction_temperature=t_junction,
|
||||
timestamp=self._sim_time,
|
||||
)
|
||||
|
||||
@@ -84,13 +133,50 @@ class PhysicsEngine:
|
||||
Returns:
|
||||
Immutable ElectricalState with current electrical values.
|
||||
"""
|
||||
# Stub: return placeholder values
|
||||
p_diss = self._calculate_power_dissipation()
|
||||
t_junction = calculate_junction_temperature(
|
||||
case_temperature=self._t_case,
|
||||
power_dissipation=p_diss,
|
||||
theta_jc=self._theta_jc,
|
||||
)
|
||||
|
||||
if self._output_enabled:
|
||||
v_out = self._dut.calculate_output_voltage(t_junction)
|
||||
i_q = self._dut.calculate_quiescent_current(t_junction)
|
||||
i_load = self._i_load
|
||||
else:
|
||||
v_out = 0.0
|
||||
i_q = 0.0
|
||||
i_load = 0.0
|
||||
|
||||
return ElectricalState(
|
||||
input_voltage=self._v_in,
|
||||
output_voltage=0.0, # Stub: will be calculated from DUT model
|
||||
load_current=self._i_load if self._output_enabled else 0.0,
|
||||
quiescent_current=0.0, # Stub: will be calculated from DUT model
|
||||
power_dissipation=0.0, # Stub: will be calculated from DUT model
|
||||
output_voltage=v_out,
|
||||
load_current=i_load,
|
||||
quiescent_current=i_q,
|
||||
power_dissipation=p_diss,
|
||||
)
|
||||
|
||||
def _calculate_power_dissipation(self) -> float:
|
||||
"""Calculate current power dissipation.
|
||||
|
||||
Uses the current case temperature as an approximation for junction
|
||||
temperature in the power calculation. The true junction temperature
|
||||
depends on power dissipation, creating a feedback loop that is
|
||||
resolved iteratively through the simulation steps.
|
||||
|
||||
Returns:
|
||||
Power dissipation in watts.
|
||||
"""
|
||||
if not self._output_enabled:
|
||||
return 0.0
|
||||
|
||||
# Use case temperature as junction estimate for power calculation
|
||||
# This avoids circular dependency in the calculation
|
||||
return self._dut.calculate_power_dissipation(
|
||||
input_voltage=self._v_in,
|
||||
load_current=self._i_load,
|
||||
junction_temperature=self._t_case,
|
||||
)
|
||||
|
||||
def set_chamber_setpoint(self, temperature: float) -> None:
|
||||
|
||||
Reference in New Issue
Block a user