Skip to content

Commit 53b8e55

Browse files
committed
Issue #0: AEOS example environment
1 parent ab1326c commit 53b8e55

File tree

3 files changed

+785
-0
lines changed

3 files changed

+785
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Agile Earth-Observing Satellite Environment\n",
8+
"\n",
9+
"This example demonstrates the environment configuration for a power-free and power-constrained agile Earth-observing satellite. These environments reflect the configuration and values from an upcoming journal paper."
10+
]
11+
},
12+
{
13+
"cell_type": "code",
14+
"execution_count": 7,
15+
"metadata": {},
16+
"outputs": [],
17+
"source": [
18+
"import numpy as np\n",
19+
"from Basilisk.architecture import bskLogging\n",
20+
"from Basilisk.utilities import orbitalMotion\n",
21+
"\n",
22+
"from bsk_rl import SatelliteTasking, act, data, obs, sats, scene\n",
23+
"from bsk_rl.sim import fsw\n",
24+
"from bsk_rl.utils.orbital import random_orbit, rv2HN\n",
25+
"\n",
26+
"bskLogging.setDefaultLogLevel(bskLogging.BSK_WARNING)"
27+
]
28+
},
29+
{
30+
"cell_type": "markdown",
31+
"metadata": {},
32+
"source": [
33+
"## Power-Free Environment\n",
34+
"\n",
35+
"First, a function for generating satellite types is introduced. This function can generate one of three different observation types, and can choose to include the time through episode in the observation."
36+
]
37+
},
38+
{
39+
"cell_type": "code",
40+
"execution_count": 8,
41+
"metadata": {},
42+
"outputs": [],
43+
"source": [
44+
"def satellite_generator(observation, n_ahead=32, include_time=False):\n",
45+
" \"\"\"_summary_\n",
46+
"\n",
47+
" Args:\n",
48+
" observation: Pick from \"S1\", \"S2\", \"S3\"\n",
49+
" n_ahead: Number of requests to include in the observation and action spaces\n",
50+
" include_time: Whether to include time through episode in the observation\n",
51+
" \"\"\"\n",
52+
"\n",
53+
" assert observation in [\"S1\", \"S2\", \"S3\"]\n",
54+
"\n",
55+
" class CustomSatellite(sats.ImagingSatellite):\n",
56+
" action_spec = [act.Image(n_ahead_image=n_ahead)]\n",
57+
" if observation == \"S1\":\n",
58+
" observation_spec = [\n",
59+
" obs.SatProperties(\n",
60+
" dict(prop=\"omega_BP_P\", norm=0.03),\n",
61+
" dict(prop=\"c_hat_P\"),\n",
62+
" dict(prop=\"r_BN_P\", norm=orbitalMotion.REQ_EARTH * 1e3),\n",
63+
" dict(prop=\"v_BN_P\", norm=7616.5),\n",
64+
" ),\n",
65+
" obs.OpportunityProperties(\n",
66+
" dict(prop=\"priority\"),\n",
67+
" dict(prop=\"r_LP_P\", norm=orbitalMotion.REQ_EARTH * 1e3),\n",
68+
" type=\"target\",\n",
69+
" n_ahead_observe=n_ahead,\n",
70+
" ),\n",
71+
" ]\n",
72+
" elif observation == \"S2\":\n",
73+
" observation_spec = [\n",
74+
" obs.SatProperties(\n",
75+
" dict(prop=\"omega_BH_H\", norm=0.03),\n",
76+
" dict(prop=\"c_hat_H\"),\n",
77+
" dict(prop=\"r_BN_P\", norm=orbitalMotion.REQ_EARTH * 1e3),\n",
78+
" dict(prop=\"v_BN_P\", norm=7616.5),\n",
79+
" ),\n",
80+
" obs.OpportunityProperties(\n",
81+
" dict(prop=\"priority\"),\n",
82+
" dict(prop=\"r_LB_H\", norm=orbitalMotion.REQ_EARTH * 1e3),\n",
83+
" type=\"target\",\n",
84+
" n_ahead_observe=n_ahead,\n",
85+
" ),\n",
86+
" ]\n",
87+
" elif observation == \"S3\":\n",
88+
" observation_spec = [\n",
89+
" obs.SatProperties(\n",
90+
" dict(prop=\"omega_BH_H\", norm=0.03),\n",
91+
" dict(prop=\"c_hat_H\"),\n",
92+
" dict(prop=\"r_BN_P\", norm=orbitalMotion.REQ_EARTH * 1e3),\n",
93+
" dict(prop=\"v_BN_P\", norm=7616.5),\n",
94+
" ),\n",
95+
" obs.OpportunityProperties(\n",
96+
" dict(prop=\"priority\"),\n",
97+
" dict(prop=\"r_LB_H\", norm=800 * 1e3),\n",
98+
" dict(prop=\"target_angle\", norm=np.pi / 2),\n",
99+
" dict(prop=\"target_angle_rate\", norm=0.03),\n",
100+
" dict(prop=\"opportunity_open\", norm=300.0),\n",
101+
" dict(prop=\"opportunity_close\", norm=300.0),\n",
102+
" type=\"target\",\n",
103+
" n_ahead_observe=n_ahead,\n",
104+
" ),\n",
105+
" ]\n",
106+
"\n",
107+
" if include_time:\n",
108+
" observation_spec.append(obs.Time())\n",
109+
" fsw_type = fsw.SteeringImagerFSWModel\n",
110+
"\n",
111+
" return CustomSatellite"
112+
]
113+
},
114+
{
115+
"cell_type": "markdown",
116+
"metadata": {},
117+
"source": [
118+
"Next, the parameters for the satellite are defined."
119+
]
120+
},
121+
{
122+
"cell_type": "code",
123+
"execution_count": 9,
124+
"metadata": {},
125+
"outputs": [],
126+
"source": [
127+
"SAT_ARGS = dict(\n",
128+
" imageAttErrorRequirement=0.01,\n",
129+
" imageRateErrorRequirement=0.01,\n",
130+
" batteryStorageCapacity=80.0 * 3600 * 100,\n",
131+
" storedCharge_Init=80.0 * 3600 * 100.0,\n",
132+
" dataStorageCapacity=200 * 8e6 * 100,\n",
133+
" u_max=0.4,\n",
134+
" imageTargetMinimumElevation=np.arctan(800 / 500),\n",
135+
" K1=0.25,\n",
136+
" K3=3.0,\n",
137+
" omega_max=np.radians(5),\n",
138+
" servo_Ki=5.0,\n",
139+
" servo_P=150 / 5,\n",
140+
" oe=lambda: random_orbit(alt=800),\n",
141+
")"
142+
]
143+
},
144+
{
145+
"cell_type": "markdown",
146+
"metadata": {},
147+
"source": [
148+
"Finally, the environment can be initialized."
149+
]
150+
},
151+
{
152+
"cell_type": "code",
153+
"execution_count": null,
154+
"metadata": {},
155+
"outputs": [],
156+
"source": [
157+
"duration = 5700.0 * 5 # 5 orbits\n",
158+
"target_distribution = \"uniform\"\n",
159+
"n_targets = 3000\n",
160+
"n_ahead = 32\n",
161+
"\n",
162+
"if target_distribution == \"uniform\":\n",
163+
" targets = scene.UniformTargets(n_targets)\n",
164+
"elif target_distribution == \"cities\":\n",
165+
" targets = scene.CityTargets(n_targets)\n",
166+
"\n",
167+
"env = SatelliteTasking(\n",
168+
" satellite=satellite_generator(observation=\"S3\", n_ahead=32, include_time=False)(\n",
169+
" name=\"EO1\",\n",
170+
" sat_args=SAT_ARGS,\n",
171+
" ),\n",
172+
" scenario=targets,\n",
173+
" rewarder=data.UniqueImageReward(),\n",
174+
" sim_rate=0.5,\n",
175+
" max_step_duration=300.0,\n",
176+
" time_limit=duration,\n",
177+
" failure_penalty=0.0,\n",
178+
" terminate_on_time_limit=True,\n",
179+
" log_level=\"INFO\",\n",
180+
")\n",
181+
"_ = env.reset()\n",
182+
"for i in range(5):\n",
183+
" env.step(env.action_space.sample())"
184+
]
185+
},
186+
{
187+
"cell_type": "markdown",
188+
"metadata": {},
189+
"source": [
190+
"## Power-Constrained Environment\n",
191+
"\n",
192+
"The power-constrained environment is like the power-free environment, but with an additional battery management requirement. The satellite has additional observation elements to be able to account for power.\n",
193+
"\n",
194+
"First, the upcoming reward density observation is defined."
195+
]
196+
},
197+
{
198+
"cell_type": "code",
199+
"execution_count": 11,
200+
"metadata": {},
201+
"outputs": [],
202+
"source": [
203+
"class Density(obs.Observation):\n",
204+
" def __init__(\n",
205+
" self,\n",
206+
" interval_duration=60 * 3,\n",
207+
" intervals=10,\n",
208+
" norm=3,\n",
209+
" ):\n",
210+
" self.satellite: \"sats.AccessSatellite\"\n",
211+
" super().__init__()\n",
212+
" self.interval_duration = interval_duration\n",
213+
" self.intervals = intervals\n",
214+
" self.norm = norm\n",
215+
"\n",
216+
" def get_obs(self):\n",
217+
" if self.intervals == 0:\n",
218+
" return []\n",
219+
"\n",
220+
" self.satellite.calculate_additional_windows(\n",
221+
" self.simulator.sim_time\n",
222+
" + (self.intervals + 1) * self.interval_duration\n",
223+
" - self.satellite.window_calculation_time\n",
224+
" )\n",
225+
" soonest = self.satellite.upcoming_opportunities_dict(types=\"target\")\n",
226+
" rewards = np.array([opportunity.priority for opportunity in soonest])\n",
227+
" times = np.array([opportunities[0][1] for opportunities in soonest.values()])\n",
228+
" time_bins = np.floor((times - self.simulator.sim_time) / self.interval_duration)\n",
229+
" densities = [sum(rewards[time_bins == i]) for i in range(self.intervals)]\n",
230+
" return np.array(densities) / self.norm"
231+
]
232+
},
233+
{
234+
"cell_type": "markdown",
235+
"metadata": {},
236+
"source": [
237+
"The satellite generator function is then defined, along with some additional observations."
238+
]
239+
},
240+
{
241+
"cell_type": "code",
242+
"execution_count": 12,
243+
"metadata": {},
244+
"outputs": [],
245+
"source": [
246+
"def wheel_speed_3(sat):\n",
247+
" return np.array(sat.dynamics.wheel_speeds[0:3]) / 630\n",
248+
"\n",
249+
"\n",
250+
"def s_hat_H(sat):\n",
251+
" r_SN_N = (\n",
252+
" sat.simulator.world.gravFactory.spiceObject.planetStateOutMsgs[\n",
253+
" sat.simulator.world.sun_index\n",
254+
" ]\n",
255+
" .read()\n",
256+
" .PositionVector\n",
257+
" )\n",
258+
" r_BN_N = sat.dynamics.r_BN_N\n",
259+
" r_SB_N = np.array(r_SN_N) - np.array(r_BN_N)\n",
260+
" r_SB_H = rv2HN(r_BN_N, sat.dynamics.v_BN_N) @ r_SB_N\n",
261+
" return r_SB_H / np.linalg.norm(r_SB_H)\n",
262+
"\n",
263+
"\n",
264+
"def power_sat_generator(n_ahead=32, include_time=False):\n",
265+
" class PowerSat(sats.ImagingSatellite):\n",
266+
" action_spec = [act.Image(n_ahead_image=n_ahead), act.Charge()]\n",
267+
" observation_spec = [\n",
268+
" obs.SatProperties(\n",
269+
" dict(prop=\"omega_BH_H\", norm=0.03),\n",
270+
" dict(prop=\"c_hat_H\"),\n",
271+
" dict(prop=\"r_BN_P\", norm=orbitalMotion.REQ_EARTH * 1e3),\n",
272+
" dict(prop=\"v_BN_P\", norm=7616.5),\n",
273+
" dict(prop=\"battery_charge_fraction\"),\n",
274+
" dict(prop=\"wheel_speed_3\", fn=wheel_speed_3),\n",
275+
" dict(prop=\"s_hat_H\", fn=s_hat_H),\n",
276+
" ),\n",
277+
" obs.OpportunityProperties(\n",
278+
" dict(prop=\"priority\"),\n",
279+
" dict(prop=\"r_LB_H\", norm=800 * 1e3),\n",
280+
" dict(prop=\"target_angle\", norm=np.pi / 2),\n",
281+
" dict(prop=\"target_angle_rate\", norm=0.03),\n",
282+
" dict(prop=\"opportunity_open\", norm=300.0),\n",
283+
" dict(prop=\"opportunity_close\", norm=300.0),\n",
284+
" type=\"target\",\n",
285+
" n_ahead_observe=n_ahead,\n",
286+
" ),\n",
287+
" obs.Eclipse(norm=5700),\n",
288+
" Density(intervals=20, norm=5),\n",
289+
" ]\n",
290+
"\n",
291+
" if include_time:\n",
292+
" observation_spec.append(obs.Time())\n",
293+
"\n",
294+
" fsw_type = fsw.SteeringImagerFSWModel\n",
295+
"\n",
296+
" return PowerSat"
297+
]
298+
},
299+
{
300+
"cell_type": "markdown",
301+
"metadata": {},
302+
"source": [
303+
"Satellite parameters are also modified for the power-constrained environment."
304+
]
305+
},
306+
{
307+
"cell_type": "code",
308+
"execution_count": 13,
309+
"metadata": {},
310+
"outputs": [],
311+
"source": [
312+
"SAT_ARGS_POWER = {}\n",
313+
"SAT_ARGS_POWER.update(SAT_ARGS)\n",
314+
"SAT_ARGS_POWER.update(\n",
315+
" dict(\n",
316+
" batteryStorageCapacity=120.0 * 3600,\n",
317+
" storedCharge_Init=lambda: 120.0 * 3600 * np.random.uniform(0.4, 1.0),\n",
318+
" rwBasePower=20.4,\n",
319+
" instrumentPowerDraw=-10,\n",
320+
" thrusterPowerDraw=-30,\n",
321+
" nHat_B=np.array([0, 0, -1]),\n",
322+
" wheelSpeeds=lambda: np.random.uniform(-2000, 2000, 3),\n",
323+
" desatAttitude=\"nadir\",\n",
324+
" )\n",
325+
")"
326+
]
327+
},
328+
{
329+
"cell_type": "markdown",
330+
"metadata": {},
331+
"source": [
332+
"Finally, the environment can be initialized."
333+
]
334+
},
335+
{
336+
"cell_type": "code",
337+
"execution_count": null,
338+
"metadata": {},
339+
"outputs": [],
340+
"source": [
341+
"duration = 5700.0 * 5 # 5 orbits\n",
342+
"target_distribution = \"uniform\"\n",
343+
"n_targets = 3000\n",
344+
"n_ahead = 32\n",
345+
"\n",
346+
"if target_distribution == \"uniform\":\n",
347+
" targets = scene.UniformTargets(n_targets)\n",
348+
"elif target_distribution == \"cities\":\n",
349+
" targets = scene.CityTargets(n_targets)\n",
350+
"\n",
351+
"env = SatelliteTasking(\n",
352+
" satellite=power_sat_generator(n_ahead=32, include_time=False)(\n",
353+
" name=\"EO1-power\",\n",
354+
" sat_args=SAT_ARGS_POWER,\n",
355+
" ),\n",
356+
" scenario=targets,\n",
357+
" rewarder=data.UniqueImageReward(),\n",
358+
" sim_rate=0.5,\n",
359+
" max_step_duration=300.0,\n",
360+
" time_limit=duration,\n",
361+
" failure_penalty=0.0,\n",
362+
" terminate_on_time_limit=True,\n",
363+
" log_level=\"INFO\",\n",
364+
")\n",
365+
"_ = env.reset()\n",
366+
"for i in range(5):\n",
367+
" env.step(env.action_space.sample())"
368+
]
369+
}
370+
],
371+
"metadata": {
372+
"kernelspec": {
373+
"display_name": ".venv",
374+
"language": "python",
375+
"name": "python3"
376+
},
377+
"language_info": {
378+
"codemirror_mode": {
379+
"name": "ipython",
380+
"version": 3
381+
},
382+
"file_extension": ".py",
383+
"mimetype": "text/x-python",
384+
"name": "python",
385+
"nbconvert_exporter": "python",
386+
"pygments_lexer": "ipython3",
387+
"version": "3.10.11"
388+
}
389+
},
390+
"nbformat": 4,
391+
"nbformat_minor": 2
392+
}

0 commit comments

Comments
 (0)