Skip to content

Commit 5588995

Browse files
Modularized simulator
1 parent 8fef1d5 commit 5588995

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2596
-1334
lines changed

acsl_pychrono/config/config.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from dataclasses import dataclass
2+
3+
@dataclass
4+
class SimulationConfig:
5+
# Total simulation duration in seconds
6+
simulation_duration_seconds: float = 21.5
7+
# Run the simulator in Wrapper mode (more simulations automatically run sequentially)
8+
wrapper_flag: bool = False
9+
# If True, perform real-time rendering of the simulation with Irrlicht
10+
visualization_flag: bool = True
11+
# Dynamic camera options:
12+
# "fixed"
13+
# "default",
14+
# "side",
15+
# "front",
16+
# "follow",
17+
# "fpv"
18+
camera_mode: str = "fixed"
19+
# Simulation timestep used by Chrono
20+
timestep: float = 0.005
21+
22+
# Controller types:
23+
# "PID",
24+
# "MRAC",
25+
controller_type: str = "PID"
26+
27+
# User-defined trajectory types:
28+
# "circular_trajectory",
29+
# "hover_trajectory",
30+
# "square_trajectory",
31+
# "rounded_rectangle_trajectory",
32+
# "piecewise_polynomial_trajectory"
33+
trajectory_type: str = "piecewise_polynomial_trajectory"
34+
35+
# If the trajectory_type is "piecewise_polynomial_trajectory", then choose the trajectory file to run
36+
# Path relative to 'current_working_directory/params/user_defined_trajectory'
37+
trajectory_data_path: str = "bean_trajectory0p6.json"
38+
39+
# Flag to add or remove the payload from the simulation
40+
add_payload_flag: bool = True
41+
# Payload types:
42+
# "two_steel_balls"
43+
# "ten_steel_balls_in_two_lines"
44+
# "many_steel_balls_in_random_position"
45+
payload_type: str = "two_steel_balls"
46+
47+
@dataclass
48+
class VehicleConfig:
49+
# Path relative to 'current_working_directory/assets/vehicles'
50+
model_relative_path: str = "x8copter/x8copter.py"
51+
52+
@dataclass
53+
class EnvironmentConfig:
54+
# Include external environment in the simulation
55+
include: bool = False
56+
# Path relative to 'current_working_directory/assets/environments'
57+
model_relative_path: str = "environmentA/environmentA.py"
58+
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import math
2+
import numpy as np
3+
4+
class M_MRAC:
5+
@staticmethod
6+
def computeAdaptiveLaw(Gamma_gain, pi_vector, eTranspose_P_B):
7+
K_hat_state_dot = Gamma_gain * pi_vector * eTranspose_P_B
8+
9+
return K_hat_state_dot
10+
11+
def reshapeAdaptiveGainsToMatrices(self):
12+
"""
13+
Reshapes all gain parameters to their correct (row, col) shape and converts them to np.matrix.
14+
This is intended to be called once after loading or updating gains stored as flat arrays.
15+
"""
16+
self.K_hat_x_tran = np.matrix(self.K_hat_x_tran.reshape(6,3))
17+
self.K_hat_r_tran = np.matrix(self.K_hat_r_tran.reshape(3,3))
18+
self.Theta_hat_tran = np.matrix(self.Theta_hat_tran.reshape(6,3))
19+
self.K_hat_x_rot = np.matrix(self.K_hat_x_rot.reshape(3,3))
20+
self.K_hat_r_rot = np.matrix(self.K_hat_r_rot.reshape(3,3))
21+
self.Theta_hat_rot = np.matrix(self.Theta_hat_rot.reshape(6,3))
22+
23+
def compute_eTransposePB_OuterLoop(self):
24+
eTranspose_P_B_tran = self.e_tran.T * self.gains.P_tran * self.gains.B_tran
25+
26+
return eTranspose_P_B_tran
27+
28+
def computeAllAdaptiveLawsOuterLoop(self, eTranspose_P_B_tran):
29+
self.K_hat_x_tran_dot = self.computeAdaptiveLaw(
30+
-self.gains.Gamma_x_tran,
31+
self.x_tran,
32+
eTranspose_P_B_tran
33+
)
34+
self.K_hat_r_tran_dot = self.computeAdaptiveLaw(
35+
-self.gains.Gamma_r_tran,
36+
self.r_tran,
37+
eTranspose_P_B_tran
38+
)
39+
self.Theta_hat_tran_dot = self.computeAdaptiveLaw(
40+
self.gains.Gamma_Theta_tran,
41+
self.Phi_adaptive_tran_augmented,
42+
eTranspose_P_B_tran
43+
)
44+
45+
def computeAllAdaptiveLawsInnerLoop(self, eTranspose_P_B_rot):
46+
self.K_hat_x_rot_dot = self.computeAdaptiveLaw(
47+
-self.gains.Gamma_x_rot,
48+
self.odein.angular_velocity,
49+
eTranspose_P_B_rot
50+
)
51+
self.K_hat_r_rot_dot = self.computeAdaptiveLaw(
52+
-self.gains.Gamma_r_rot,
53+
self.r_rot,
54+
eTranspose_P_B_rot
55+
)
56+
self.Theta_hat_rot_dot = self.computeAdaptiveLaw(
57+
self.gains.Gamma_Theta_rot,
58+
self.Phi_adaptive_rot_augmented,
59+
eTranspose_P_B_rot
60+
)

acsl_pychrono/control/MRAC/mrac.py

Lines changed: 26 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,41 @@
66
from acsl_pychrono.flight_params import FlightParams
77
from acsl_pychrono.control.control import Control
88
from acsl_pychrono.control.base_mrac import BaseMRAC
9+
from acsl_pychrono.control.MRAC.m_mrac import M_MRAC
910

10-
class MRAC(BaseMRAC, Control):
11-
def __init__(self, gains: MRACGains, ode_input: OdeInput, flight_params: FlightParams):
11+
class MRAC(M_MRAC, BaseMRAC, Control):
12+
def __init__(self, gains: MRACGains, ode_input: OdeInput, flight_params: FlightParams, timestep: float):
13+
super().__init__(odein=ode_input)
1214
self.gains = gains
13-
self.odein = ode_input
1415
self.fp = flight_params
16+
self.timestep = timestep
1517
self.safety_mechanism = OuterLoopSafetyMechanism(gains, self.fp.G_acc)
1618
self.dy = np.zeros((self.gains.number_of_states, 1))
19+
# Initial conditions
20+
self.y = np.zeros((self.gains.number_of_states, 1))
1721

18-
def computeControlAlgorithm(self, y, ode_input: OdeInput):
22+
def computeControlAlgorithm(self, ode_input: OdeInput):
1923
"""
2024
Compute all intermediate variables and control inputs once per RK4 step to compute the dy for RK4.
2125
"""
2226
# Update the vehicle state and user-defined trajectory
2327
self.odein = ode_input
2428

2529
# ODE state
26-
self.state_phi_ref_diff = y[0:2] # State of the differentiator for phi_ref (roll_ref)
27-
self.state_theta_ref_diff = y[2:4] # State of the differentiator for theta_ref (pitch_ref)
28-
self.x_ref_tran = y[4:10] # Reference model state
29-
self.integral_position_tracking_ref = y[10:13] # Integral of ('translational_position_in_I_ref' - 'translational_position_in_I_user')
30-
self.K_hat_x_tran = y[13:31] # \hat{K}_x (translational)
31-
self.K_hat_r_tran = y[31:40] # \hat{K}_r (translational)
32-
self.Theta_hat_tran = y[40:58] # \hat{\Theta} (translational)
33-
self.omega_ref = y[58:61] # Reference model rotational dynamics
34-
self.K_hat_x_rot = y[61:70] # \hat{K}_x (rotational)
35-
self.K_hat_r_rot = y[70:79] # \hat{K}_r (rotational)
36-
self.Theta_hat_rot = y[79:97] # \hat{\Theta} (rotational)
37-
self.integral_e_rot = y[97:100] # Integral of 'e_rot' = (angular_velocity - omega_ref)
38-
self.integral_angular_error = y[100:103] # Integral of angular_error = attitude - attitude_ref
39-
self.integral_e_omega_ref_cmd = y[103:106] #Integral of (omega_ref - omega_cmd)
30+
self.state_phi_ref_diff = self.y[0:2] # State of the differentiator for phi_ref (roll_ref)
31+
self.state_theta_ref_diff = self.y[2:4] # State of the differentiator for theta_ref (pitch_ref)
32+
self.x_ref_tran = self.y[4:10] # Reference model state
33+
self.integral_position_tracking_ref = self.y[10:13] # Integral of ('translational_position_in_I_ref' - 'translational_position_in_I_user')
34+
self.K_hat_x_tran = self.y[13:31] # \hat{K}_x (translational)
35+
self.K_hat_r_tran = self.y[31:40] # \hat{K}_r (translational)
36+
self.Theta_hat_tran = self.y[40:58] # \hat{\Theta} (translational)
37+
self.omega_ref = self.y[58:61] # Reference model rotational dynamics
38+
self.K_hat_x_rot = self.y[61:70] # \hat{K}_x (rotational)
39+
self.K_hat_r_rot = self.y[70:79] # \hat{K}_r (rotational)
40+
self.Theta_hat_rot = self.y[79:97] # \hat{\Theta} (rotational)
41+
self.integral_e_rot = self.y[97:100] # Integral of 'e_rot' = (angular_velocity - omega_ref)
42+
self.integral_angular_error = self.y[100:103] # Integral of angular_error = attitude - attitude_ref
43+
self.integral_e_omega_ref_cmd = self.y[103:106] #Integral of (omega_ref - omega_cmd)
4044

4145
# Reshapes all adaptive gains to their correct (row, col) shape as matrices
4246
self.reshapeAdaptiveGainsToMatrices()
@@ -136,13 +140,14 @@ def computeControlAlgorithm(self, y, ode_input: OdeInput):
136140

137141
(self.u2,
138142
self.u3,
139-
self.u4
143+
self.u4,
144+
_
140145
) = self.computeU2_U3_U4()
141146

142147
# Compute individual motor thrusts
143148
self.motor_thrusts = Control.computeMotorThrusts(self.fp, self.u1, self.u2, self.u3, self.u4)
144149

145-
def ode(self, t, y, ode_input: OdeInput):
150+
def ode(self, t, y):
146151
"""
147152
Function called by RK4. Assumes `computeControlAlgorithm` was called
148153
at the beginning of the integration step to update internal state.
@@ -162,50 +167,4 @@ def ode(self, t, y, ode_input: OdeInput):
162167
self.dy[100:103] = self.angular_error
163168
self.dy[103:106] = self.omega_ref - self.omega_cmd
164169

165-
return np.array(self.dy)
166-
167-
def reshapeAdaptiveGainsToMatrices(self):
168-
"""
169-
Reshapes all gain parameters to their correct (row, col) shape and converts them to np.matrix.
170-
This is intended to be called once after loading or updating gains stored as flat arrays.
171-
"""
172-
self.K_hat_x_tran = np.matrix(self.K_hat_x_tran.reshape(6,3))
173-
self.K_hat_r_tran = np.matrix(self.K_hat_r_tran.reshape(3,3))
174-
self.Theta_hat_tran = np.matrix(self.Theta_hat_tran.reshape(6,3))
175-
self.K_hat_x_rot = np.matrix(self.K_hat_x_rot.reshape(3,3))
176-
self.K_hat_r_rot = np.matrix(self.K_hat_r_rot.reshape(3,3))
177-
self.Theta_hat_rot = np.matrix(self.Theta_hat_rot.reshape(6,3))
178-
179-
def computeAllAdaptiveLawsOuterLoop(self, eTranspose_P_B_tran):
180-
self.K_hat_x_tran_dot = self.computeAdaptiveLawMRAC(
181-
-self.gains.Gamma_x_tran,
182-
self.x_tran,
183-
eTranspose_P_B_tran
184-
)
185-
self.K_hat_r_tran_dot = self.computeAdaptiveLawMRAC(
186-
-self.gains.Gamma_r_tran,
187-
self.r_tran,
188-
eTranspose_P_B_tran
189-
)
190-
self.Theta_hat_tran_dot = self.computeAdaptiveLawMRAC(
191-
self.gains.Gamma_Theta_tran,
192-
self.Phi_adaptive_tran_augmented,
193-
eTranspose_P_B_tran
194-
)
195-
196-
def computeAllAdaptiveLawsInnerLoop(self, eTranspose_P_B_rot):
197-
self.K_hat_x_rot_dot = self.computeAdaptiveLawMRAC(
198-
-self.gains.Gamma_x_rot,
199-
self.odein.angular_velocity,
200-
eTranspose_P_B_rot
201-
)
202-
self.K_hat_r_rot_dot = self.computeAdaptiveLawMRAC(
203-
-self.gains.Gamma_r_rot,
204-
self.r_rot,
205-
eTranspose_P_B_rot
206-
)
207-
self.Theta_hat_rot_dot = self.computeAdaptiveLawMRAC(
208-
self.gains.Gamma_Theta_rot,
209-
self.Phi_adaptive_rot_augmented,
210-
eTranspose_P_B_rot
211-
)
170+
return np.array(self.dy)

acsl_pychrono/control/MRAC/mrac_gains.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import math
22
import numpy as np
33
from numpy import linalg as LA
4-
import scipy
54
from scipy import linalg
65
from acsl_pychrono.flight_params import FlightParams
76

acsl_pychrono/control/MRAC/mrac_logger.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
1-
import math
21
import numpy as np
32
from acsl_pychrono.control.MRAC.mrac_gains import MRACGains
43
from acsl_pychrono.control.MRAC.mrac import MRAC
5-
from acsl_pychrono.ode_input import OdeInput
6-
from acsl_pychrono.flight_params import FlightParams
74

85
class MRACLogger:
96
def __init__(self, gains: MRACGains) -> None:
107
self.gains = gains
118
self.data_list = []
129

13-
def collect_data(self, controller: MRAC, time_now: float, simulation_time: float):
10+
def collectData(self, controller: MRAC, simulation_time: float):
1411
DATA_vector = np.zeros((self.gains.size_DATA, 1))
1512

16-
DATA_vector[0] = time_now
13+
DATA_vector[0] = controller.odein.time_now
1714
DATA_vector[1] = simulation_time
1815
DATA_vector[2:5] = controller.odein.translational_position_in_I
1916
DATA_vector[5:8] = controller.odein.translational_velocity_in_I
@@ -58,7 +55,7 @@ def collect_data(self, controller: MRAC, time_now: float, simulation_time: float
5855

5956
self.data_list.append(DATA_vector.flatten())
6057

61-
def to_dictionary(self):
58+
def toDictionary(self):
6259
DATA_np = np.array(self.data_list)
6360

6461
log_dict = {

acsl_pychrono/control/PID/pid.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,28 @@
77
from acsl_pychrono.control.control import Control
88

99
class PID(Control):
10-
def __init__(self, gains: PIDGains, ode_input: OdeInput, flight_params: FlightParams):
10+
def __init__(self, gains: PIDGains, ode_input: OdeInput, flight_params: FlightParams, timestep: float):
11+
super().__init__(odein=ode_input)
1112
self.gains = gains
12-
self.odein = ode_input
1313
self.fp = flight_params
14+
self.timestep = timestep
1415
self.safety_mechanism = OuterLoopSafetyMechanism(gains, self.fp.G_acc)
1516
self.dy = np.zeros((self.gains.number_of_states, 1))
17+
# Initial conditions
18+
self.y = np.zeros((self.gains.number_of_states, 1))
1619

17-
def computeControlAlgorithm(self, y, ode_input: OdeInput):
20+
def computeControlAlgorithm(self, ode_input: OdeInput):
1821
"""
1922
Compute all intermediate variables and control inputs once per RK4 step to compute the dy for RK4.
2023
"""
2124
# Update the vehicle state and user-defined trajectory
2225
self.odein = ode_input
2326

2427
# ODE state
25-
self.state_phi_ref_diff = y[0:2]
26-
self.state_theta_ref_diff = y[2:4]
27-
self.integral_position_tracking = y[4:7]
28-
self.integral_angular_error = y[7:10]
28+
self.state_phi_ref_diff = self.y[0:2]
29+
self.state_theta_ref_diff = self.y[2:4]
30+
self.integral_position_tracking = self.y[4:7]
31+
self.integral_angular_error = self.y[7:10]
2932

3033
# Compute translational position error
3134
self.translational_position_error = Control.computeTranslationalPositionError(
@@ -86,7 +89,7 @@ def computeControlAlgorithm(self, y, ode_input: OdeInput):
8689
# Compute individual motor thrusts
8790
self.motor_thrusts = Control.computeMotorThrusts(self.fp, self.u1, self.u2, self.u3, self.u4)
8891

89-
def ode(self, t, y, ode_input: OdeInput):
92+
def ode(self, t, y):
9093
"""
9194
Function called by RK4. Assumes `computeControlAlgorithm` was called
9295
at the beginning of the integration step to update internal state.

acsl_pychrono/control/PID/pid_logger.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ def __init__(self, gains: PIDGains) -> None:
1010
self.gains = gains
1111
self.data_list = []
1212

13-
def collect_data(self, controller: PID, time_now: float, simulation_time: float):
13+
def collectData(self, controller: PID, simulation_time: float):
1414
DATA_vector = np.zeros((self.gains.size_DATA, 1))
1515

16-
DATA_vector[0] = time_now
16+
DATA_vector[0] = controller.odein.time_now
1717
DATA_vector[1] = simulation_time
1818
DATA_vector[2:5] = controller.odein.translational_position_in_I
1919
DATA_vector[5:8] = controller.odein.translational_velocity_in_I
@@ -45,7 +45,7 @@ def collect_data(self, controller: PID, time_now: float, simulation_time: float)
4545

4646
self.data_list.append(DATA_vector.flatten())
4747

48-
def to_dictionary(self):
48+
def toDictionary(self):
4949
DATA_np = np.array(self.data_list)
5050

5151
log_dict = {

acsl_pychrono/control/__init__.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,20 @@
11
# acsl_pychrono/control/__init__.py
22

33
from .PID import PID, PIDGains, PIDLogger
4-
from .MRAC import MRAC, MRACGains, MRACLogger
4+
from .MRAC import MRAC, MRACGains, MRACLogger
5+
6+
controller_classes = {
7+
'PID': (PIDGains, PID, PIDLogger),
8+
'MRAC': (MRACGains, MRAC, MRACLogger),
9+
}
10+
11+
def instantiateController(controller_type: str, ode_input, flight_params, timestep):
12+
if controller_type not in controller_classes:
13+
raise ValueError(f"Unknown controller type: {controller_type}")
14+
15+
GainsClass, ControllerClass, LoggerClass = controller_classes[controller_type]
16+
gains = GainsClass(flight_params)
17+
controller = ControllerClass(gains, ode_input, flight_params, timestep)
18+
logger = LoggerClass(gains)
19+
20+
return gains, controller, logger

0 commit comments

Comments
 (0)