Skip to content

Commit 3b45aba

Browse files
committed
programmatically generate solar panel config instead of reading from file to allow parameter sweeps
1 parent f4925f1 commit 3b45aba

File tree

3 files changed

+75
-52
lines changed

3 files changed

+75
-52
lines changed

config.yaml

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -19,50 +19,6 @@ satellite:
1919
N_mtb : 3 # number of magnetorquers
2020
mtb_orientation : [[1,0,0], [0,1,0], [0,0,1]] # alignment axis of each magnetorquer in the body frame
2121

22-
solar:
23-
- is_solar_panel : true
24-
center : [0.1, 0, 0]
25-
normal : [1, 0, 0]
26-
x_dir : [0, 1, 0]
27-
y_dir : [0, 0, 1]
28-
width : 0.1
29-
height : 0.1
30-
- is_solar_panel : true
31-
center : [-0.1, 0, 0]
32-
normal : [-1, 0, 0]
33-
x_dir : [0, 1, 0]
34-
y_dir : [0, 0, 1]
35-
width : 0.1
36-
height : 0.1
37-
- is_solar_panel : true
38-
center : [0, 0.1, 0]
39-
normal : [0, 1, 0]
40-
x_dir : [1, 0, 0]
41-
y_dir : [0, 0, 1]
42-
width : 0.1
43-
height : 0.1
44-
- is_solar_panel : true
45-
center : [0, -0.1, 0]
46-
normal : [0, -1, 0]
47-
x_dir : [1, 0, 0]
48-
y_dir : [0, 0, 1]
49-
width : 0.1
50-
height : 0.1
51-
- is_solar_panel : true
52-
center : [0, 0, 0.1]
53-
normal : [0, 0, 1]
54-
x_dir : [1, 0, 0]
55-
y_dir : [0, 1, 0]
56-
width : 0.1
57-
height : 0.1
58-
- is_solar_panel : true
59-
center : [0, 0, -0.1]
60-
normal : [0, 0, -1]
61-
x_dir : [1, 0, 0]
62-
y_dir : [0, 1, 0]
63-
width : 0.1
64-
height : 0.1
65-
6622

6723
solver:
6824
world_update_rate : 100 #Hz

world/physics/models/solar_generation.py

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from dataclasses import dataclass
22
import numpy as np
3+
from scipy.spatial.transform import Rotation
34

45
import brahe
56
from brahe.epoch import Epoch
@@ -59,16 +60,82 @@ class SolarGeneration:
5960
SOLAR_FLUX = 1373 # The solar power flux at the Earth's orbital radius in W/m^2.
6061
PANEL_EFFICIENCY = 0.15 # The efficiency of the solar panels, as a fraction in [0, 1].
6162

62-
def __init__(self, config: dict) -> None:
63-
self.surfaces = SolarGeneration.parse_solar_config(config["satellite"]["solar"])
63+
def __init__(self, deployables_dir: np.ndarray, deployables_tilt_angle: float) -> None:
64+
self.surfaces = SolarGeneration.get_solar_config(deployables_dir, deployables_tilt_angle)
6465
self.sample_resolution = 0.05 # The resolution of the occlusion sampling grid, in meters.
6566

6667
@staticmethod
67-
def parse_solar_config(surfaces: list[dict[str, bool | list[float] | float]]) -> list[Surface]:
68-
def to_np(surface: dict[str, bool | list[float] | float]) -> dict[str, bool | np.ndarray | float]:
69-
return {key: np.array(value) if isinstance(value, list) else value
70-
for key, value in surface.items()}
71-
return [Surface(**to_np(surface)) for surface in surfaces]
68+
def get_solar_config(deployables_dir: np.ndarray, deployables_tilt_angle: float) -> list[Surface]:
69+
"""
70+
Returns a list of Surface objects representing the solar panels on the satellite.
71+
72+
:param deployables_dir: A 3-element numpy array representing the direction of the deployable solar panels.
73+
Must be a unit vector with exactly one non-zero element.
74+
:param deployables_tilt_angle: The angle in radians by which the deployable solar panels are tilted.
75+
See the diagram below for the definition of the tilt angle.
76+
:return: A list of Surface objects representing the configuration of the satellite.
77+
"""
78+
assert deployables_dir.shape == (3,), "deployables_dir must be a 3-element numpy array."
79+
assert np.sum(np.abs(deployables_dir) == 1) == 1 and np.sum(deployables_dir == 0) == 2, \
80+
"deployables_dir must be a unit vector with exactly one non-zero element."
81+
82+
R = 0.05 # Half the side length of the cubesat in meters
83+
84+
cube_surfaces = [Surface(is_solar_panel=True,
85+
center=np.array([R, 0, 0]),
86+
normal=np.array([1, 0, 0]),
87+
x_dir=np.array([0, 1, 0]),
88+
y_dir=np.array([0, 0, 1]),
89+
width=0.1,
90+
height=0.1)] # +x face
91+
cube_surfaces.append(cube_surfaces[0].transform(np.array([[0, 1, 0],
92+
[1, 0, 0],
93+
[0, 0, 1]]))) # +y face
94+
cube_surfaces.append(cube_surfaces[0].transform(np.array([[0, 0, 1],
95+
[0, 1, 0],
96+
[1, 0, 0]]))) # +z face
97+
cube_surfaces.append(cube_surfaces[0].transform(np.array([[-1, 0, 0],
98+
[0, 1, 0],
99+
[0, 0, 1]]))) # -x face
100+
cube_surfaces.append(cube_surfaces[1].transform(np.array([[1, 0, 0],
101+
[0, -1, 0],
102+
[0, 0, 1]]))) # -y face
103+
cube_surfaces.append(cube_surfaces[2].transform(np.array([[1, 0, 0],
104+
[0, 1, 0],
105+
[0, 0, -1]]))) # -z face
106+
107+
"""
108+
deployables_tilt_angle
109+
| / deployables_dir
110+
| / ^
111+
________|/ |
112+
| | |
113+
| | --> deployables_offset_dir
114+
|_______|
115+
deployables_tilt_dir is into the page
116+
"""
117+
deployables_tilt_dir = np.roll(deployables_dir, 1) # doesn't matter which of the 4 perpendiculars we choose
118+
deployables_offset_dir = np.cross(deployables_tilt_dir, deployables_dir)
119+
deployables_tilt = Rotation.from_rotvec(deployables_tilt_angle * deployables_tilt_dir).as_matrix()
120+
121+
deployable_surfaces = [Surface(is_solar_panel=True,
122+
center=(np.eye(3) + deployables_tilt) * R * deployables_dir + R * deployables_offset_dir,
123+
normal=deployables_tilt @ deployables_offset_dir,
124+
x_dir=deployables_tilt_dir,
125+
y_dir=deployables_tilt @ deployables_dir,
126+
width=0.1,
127+
height=0.1)]
128+
129+
# add the 3 other rotated copies of the deployable surfaces
130+
rot_90 = Rotation.from_rotvec((np.pi / 2) * deployables_dir).as_matrix()
131+
for i in range(0, 4):
132+
deployable_surfaces.append(deployable_surfaces[0].transform(rot_90 ** i))
133+
134+
# add deployable surfaces with flipped normals
135+
for deployable_surface in deployable_surfaces:
136+
deployable_surfaces.append(deployable_surface.flip_normal())
137+
138+
return cube_surfaces + deployable_surfaces
72139

73140
@staticmethod
74141
def get_intersections(surface: Surface, ray_starts: np.ndarray, ray_dir: np.ndarray) -> np.ndarray:

world/physics/models/test_solar_generation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def main():
119119
period = 2 * np.pi * np.sqrt((R_EARTH + 600e3) ** 3 / GM_EARTH)
120120
N = 1000
121121
dt = period / N
122-
solar_generation = SolarGeneration(config)
122+
solar_generation = SolarGeneration(deployables_dir=np.array([0, 0, 1]), deployables_tilt_angle=np.pi / 4)
123123
states, generated_power = propagate_orbit_and_solar(get_estimated_orbital_state(initial_epoch), solar_generation,
124124
initial_epoch, dt, N)
125125

0 commit comments

Comments
 (0)