Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,11 @@ pythontex-files-*/
# easy-todo
*.lod

# CVT plotting artifacts
cvtModel/generated_graphs/
cvtModel/ramp_plots/
cvtModel/theta_ramp_plots/

# xcolor
*.xcp

Expand Down
22 changes: 14 additions & 8 deletions backend/app/api/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,13 @@ def get_constants():
@router.post("/run", response_model=FormattedResultModel)
def run(payload: SimulationArgsInput | None = None): # type: ignore
"""Run CVT simulation with optional custom parameters."""
args = payload.model_dump(exclude_none=True) if payload else {}
args = SimulationArgs.from_mapping(args)
result = simulate_cvt_model(args)
return result
try:
args = payload.model_dump(exclude_none=True) if payload else {}
args = SimulationArgs.from_mapping(args)
result = simulate_cvt_model(args)
return result
except ValueError as e:
raise HTTPException(status_code=400, detail=f"Invalid simulation input: {e}")


@router.post("/run/stream", responses={200: {"model": StreamMessage}})
Expand Down Expand Up @@ -168,11 +171,14 @@ def run_simulation_thread():

@router.post("/solvers", response_model=AllSolverResultsModel)
def run_solvers(payload: SimulationArgsInput | None = None): # type: ignore
args = payload.model_dump(exclude_none=True) if payload else {}
args = SimulationArgs.from_mapping(args)
try:
args = payload.model_dump(exclude_none=True) if payload else {}
args = SimulationArgs.from_mapping(args)

# Run all solvers in one call
return solve_all(args)
# Run all solvers in one call
return solve_all(args)
except ValueError as e:
raise HTTPException(status_code=400, detail=f"Invalid solver input: {e}")


# TODO: Remove this logic from endpoints / bake into cvtModel simulator
Expand Down
17 changes: 17 additions & 0 deletions cvtModel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,20 @@ flake8 src/ test/
```bash
pre-commit install
```

## Local Simulation + Graph Validation

1. Generate a simulation CSV
```bash
python -c "from cvt_simulator import simulate_cvt_model, SimulationArgs; simulate_cvt_model(SimulationArgs(), out_csv='simulation_output.csv')"
```

2. Generate validation graphs from the CSV
```bash
python src/cvt_simulator/utils/generate_graphs.py --csv simulation_output.csv --out-dir generated_graphs
```

3. Optional: show interactive plots while generating files
```bash
python src/cvt_simulator/utils/generate_graphs.py --csv simulation_output.csv --out-dir generated_graphs --show
```
16 changes: 8 additions & 8 deletions cvtModel/src/cvt_simulator/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from .main import simulate_cvt_model
from .utils.simulation_args import SimulationArgs
from .models.dataTypes import (
CvtSystemForceBreakdown,
CarForceBreakdown,
EngineForceBreakdown,
CvtDynamicsBreakdown,
SecondaryPulleyDynamicsBreakdown,
PrimaryPulleyDynamicsBreakdown,
SlipBreakdown,
SystemBreakdown,
DrivetrainBreakdown,
)
from .utils.frontend_output import FormattedSimulationResult
from .models.ramps.ramp_config import PiecewiseRampConfig
Expand All @@ -16,11 +16,11 @@
__all__ = [
"simulate_cvt_model",
"SimulationArgs",
"CvtSystemForceBreakdown",
"CarForceBreakdown",
"EngineForceBreakdown",
"CvtDynamicsBreakdown",
"SecondaryPulleyDynamicsBreakdown",
"PrimaryPulleyDynamicsBreakdown",
"SlipBreakdown",
"SystemBreakdown",
"DrivetrainBreakdown",
"FormattedSimulationResult",
"PiecewiseRampConfig",
"PiecewiseRamp",
Expand Down
19 changes: 17 additions & 2 deletions cvtModel/src/cvt_simulator/constants/car_specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,16 @@ class CarSpecs(BaseModel):
"""

# Inertia values
# TODO: Look into inertia as it should be of all spinning parts
engine_inertia: float = Field(default=0.1, description="Engine inertia in kg*m^2")
secondary_inertia: float = Field(
default=0.1, description="Secondary CVT pulley inertia in kg*m^2"
)
gearbox_inertia: float = Field(
default=0.05, description="Gearbox inertia in kg*m^2"
)
wheel_inertia: float = Field(
default=0.2, description="Wheel inertia in kg*m^2 (all wheels)"
)
driveline_inertia: float = Field(
default=0.5,
description="Driveline inertia in kg*m^2 (includes sec CVT, gearbox, axles, wheels, hubs, etc)",
Expand All @@ -32,14 +40,17 @@ class CarSpecs(BaseModel):
drag_coefficient: float = Field(
default=0.6, description="Drag coefficient (unitless)"
)
rolling_resistance_coefficient: float = Field(
default=0.015, description="Rolling resistance coefficient (unitless)"
)

# Pulley geometry
# TODO: These are all guesses, need to be gotten
sheave_angle: float = Field(
default=deg_to_rad(11.5 * 2), description="Sheave angle in radians"
)
initial_flyweight_radius: float = Field(
default=0.05, description="Initial flyweight radius in meters"
default=0.04878, description="Initial flyweight radius in meters"
)
helix_radius: float = Field(default=0.04445, description="Helix radius in meters")

Expand Down Expand Up @@ -127,10 +138,14 @@ class Config:

# Export constants as module-level variables for backward compatibility
ENGINE_INERTIA = _default_specs.engine_inertia
SECONDARY_INERTIA = _default_specs.secondary_inertia
GEARBOX_INERTIA = _default_specs.gearbox_inertia
WHEEL_INERTIA = _default_specs.wheel_inertia
DRIVELINE_INERTIA = _default_specs.driveline_inertia
GEARBOX_RATIO = _default_specs.gearbox_ratio
FRONTAL_AREA = _default_specs.frontal_area
DRAG_COEFFICIENT = _default_specs.drag_coefficient
ROLLING_RESISTANCE_COEFFICIENT = _default_specs.rolling_resistance_coefficient
WHEEL_RADIUS = _default_specs.wheel_radius
SHEAVE_ANGLE = _default_specs.sheave_angle
INITIAL_FLYWEIGHT_RADIUS = _default_specs.initial_flyweight_radius
Expand Down
2 changes: 1 addition & 1 deletion cvtModel/src/cvt_simulator/constants/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
AIR_DENSITY = 1.225 # kg/m^3
GRAVITY = g # m/s^2
RUBBER_DENSITY = 1100 # kg/m^3
RUBBER_ALUMINUM_STATIC_FRICTION = 0.8 # unitless
RUBBER_ALUMINUM_STATIC_FRICTION = 0.51 # unitless
RUBBER_ALUMINUM_KINETIC_FRICTION = 0.65 # unitless
41 changes: 0 additions & 41 deletions cvtModel/src/cvt_simulator/models/car_model.py

This file was deleted.

26 changes: 12 additions & 14 deletions cvtModel/src/cvt_simulator/models/cvt_shift_model.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from cvt_simulator.models.dataTypes import CvtSystemForceBreakdown
from cvt_simulator.models.dataTypes import CvtDynamicsBreakdown
from cvt_simulator.models.pulley.primary_pulley_interface import PrimaryPulleyModel
from cvt_simulator.models.pulley.secondary_pulley_interface import SecondaryPulleyModel
from cvt_simulator.utils.system_state import SystemState
Expand All @@ -12,8 +12,8 @@ class CvtShiftModel:

This model:
1. Takes generic pulley models (any implementation: physical, PID, lookup, etc.)
2. Calculates radial forces from each pulley using their specific mechanisms
3. Determines net shift force and acceleration from the force balance
2. Uses total axial forces from each pulley
3. Determines net shift force and acceleration from the axial force balance
4. Handles friction and system dynamics

The abstraction allows swapping pulley implementations without changing
Expand All @@ -33,21 +33,21 @@ def __init__(

def get_breakdown(
self, state: SystemState, coupling_torque: float
) -> CvtSystemForceBreakdown:
) -> CvtDynamicsBreakdown:
primary_state, secondary_state = self._get_pulley_states(state, coupling_torque)

prim_radial = primary_state.forces.radial_force
sec_radial = secondary_state.forces.radial_force
net = prim_radial - sec_radial
prim_axial = primary_state.forces.axial_force_total
sec_axial = secondary_state.forces.axial_force_total
net = prim_axial - sec_axial

shift_velocity = state.shift_velocity
friction = self._frictional_force(net, shift_velocity)

acceleration = (net + friction) / self.cvt_moving_mass

cvt_ratio = tm.current_cvt_ratio(state.shift_distance)
cvt_ratio = tm.current_effective_cvt_ratio(state.shift_distance)

return CvtSystemForceBreakdown(
return CvtDynamicsBreakdown(
primary_state,
secondary_state,
friction,
Expand All @@ -71,7 +71,7 @@ def _get_pulley_states(self, state: SystemState, coupling_torque: float):
primary_state = self.primary_pulley.get_pulley_state(state)

# Calculate CVT ratio for torque scaling to secondary
cvt_ratio = tm.current_cvt_ratio(state.shift_distance)
cvt_ratio = tm.current_effective_cvt_ratio(state.shift_distance)

# Get secondary pulley state (torque-reactive, needs scaled torque)
secondary_state = self.secondary_pulley.get_pulley_state(
Expand All @@ -80,11 +80,9 @@ def _get_pulley_states(self, state: SystemState, coupling_torque: float):

return primary_state, secondary_state

def _frictional_force(
self, sum_of_radial_forces: float, shift_velocity: float
) -> float:
def _frictional_force(self, net_axial_force: float, shift_velocity: float) -> float:
raw_friction = 20 # TODO: Update to use calculation
friction_magnitude = min(raw_friction, abs(sum_of_radial_forces))
friction_magnitude = min(raw_friction, abs(net_axial_force))
if shift_velocity > 0:
return -friction_magnitude
return friction_magnitude
Loading
Loading