Skip to content

Commit 2a44de4

Browse files
committed
Issue #0: AEOS example environment
1 parent ab1326c commit 2a44de4

File tree

3 files changed

+777
-0
lines changed

3 files changed

+777
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
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()"
182+
]
183+
},
184+
{
185+
"cell_type": "markdown",
186+
"metadata": {},
187+
"source": [
188+
"## Power-Constrained Environment\n",
189+
"\n",
190+
"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",
191+
"\n",
192+
"First, the upcoming reward density observation is defined."
193+
]
194+
},
195+
{
196+
"cell_type": "code",
197+
"execution_count": 11,
198+
"metadata": {},
199+
"outputs": [],
200+
"source": [
201+
"class Density(obs.Observation):\n",
202+
" def __init__(\n",
203+
" self,\n",
204+
" interval_duration=60 * 3,\n",
205+
" intervals=10,\n",
206+
" norm=3,\n",
207+
" ):\n",
208+
" self.satellite: \"sats.AccessSatellite\"\n",
209+
" super().__init__()\n",
210+
" self.interval_duration = interval_duration\n",
211+
" self.intervals = intervals\n",
212+
" self.norm = norm\n",
213+
"\n",
214+
" def get_obs(self):\n",
215+
" if self.intervals == 0:\n",
216+
" return []\n",
217+
"\n",
218+
" self.satellite.calculate_additional_windows(\n",
219+
" self.simulator.sim_time\n",
220+
" + (self.intervals + 1) * self.interval_duration\n",
221+
" - self.satellite.window_calculation_time\n",
222+
" )\n",
223+
" soonest = self.satellite.upcoming_opportunities_dict(types=\"target\")\n",
224+
" rewards = np.array([opportunity.priority for opportunity in soonest])\n",
225+
" times = np.array([opportunities[0][1] for opportunities in soonest.values()])\n",
226+
" time_bins = np.floor((times - self.simulator.sim_time) / self.interval_duration)\n",
227+
" densities = [sum(rewards[time_bins == i]) for i in range(self.intervals)]\n",
228+
" return np.array(densities) / self.norm"
229+
]
230+
},
231+
{
232+
"cell_type": "markdown",
233+
"metadata": {},
234+
"source": [
235+
"The satellite generator function is then defined, along with some additional observations."
236+
]
237+
},
238+
{
239+
"cell_type": "code",
240+
"execution_count": 12,
241+
"metadata": {},
242+
"outputs": [],
243+
"source": [
244+
"def wheel_speed_3(sat):\n",
245+
" return np.array(sat.dynamics.wheel_speeds[0:3]) / 630\n",
246+
"\n",
247+
"\n",
248+
"def s_hat_H(sat):\n",
249+
" r_SN_N = (\n",
250+
" sat.simulator.world.gravFactory.spiceObject.planetStateOutMsgs[\n",
251+
" sat.simulator.world.sun_index\n",
252+
" ]\n",
253+
" .read()\n",
254+
" .PositionVector\n",
255+
" )\n",
256+
" r_BN_N = sat.dynamics.r_BN_N\n",
257+
" r_SB_N = np.array(r_SN_N) - np.array(r_BN_N)\n",
258+
" r_SB_H = rv2HN(r_BN_N, sat.dynamics.v_BN_N) @ r_SB_N\n",
259+
" return r_SB_H / np.linalg.norm(r_SB_H)\n",
260+
"\n",
261+
"\n",
262+
"def power_sat_generator(n_ahead=32, include_time=False):\n",
263+
" class PowerSat(sats.ImagingSatellite):\n",
264+
" action_spec = [act.Image(n_ahead_image=n_ahead), act.Charge()]\n",
265+
" observation_spec = [\n",
266+
" obs.SatProperties(\n",
267+
" dict(prop=\"omega_BH_H\", norm=0.03),\n",
268+
" dict(prop=\"c_hat_H\"),\n",
269+
" dict(prop=\"r_BN_P\", norm=orbitalMotion.REQ_EARTH * 1e3),\n",
270+
" dict(prop=\"v_BN_P\", norm=7616.5),\n",
271+
" dict(prop=\"battery_charge_fraction\"),\n",
272+
" dict(prop=\"wheel_speed_3\", fn=wheel_speed_3),\n",
273+
" dict(prop=\"s_hat_H\", fn=s_hat_H),\n",
274+
" ),\n",
275+
" obs.OpportunityProperties(\n",
276+
" dict(prop=\"priority\"),\n",
277+
" dict(prop=\"r_LB_H\", norm=800 * 1e3),\n",
278+
" dict(prop=\"target_angle\", norm=np.pi / 2),\n",
279+
" dict(prop=\"target_angle_rate\", norm=0.03),\n",
280+
" dict(prop=\"opportunity_open\", norm=300.0),\n",
281+
" dict(prop=\"opportunity_close\", norm=300.0),\n",
282+
" type=\"target\",\n",
283+
" n_ahead_observe=n_ahead,\n",
284+
" ),\n",
285+
" obs.Eclipse(norm=5700),\n",
286+
" Density(intervals=20, norm=5),\n",
287+
" ]\n",
288+
"\n",
289+
" if include_time:\n",
290+
" observation_spec.append(obs.Time())\n",
291+
"\n",
292+
" fsw_type = fsw.SteeringImagerFSWModel\n",
293+
"\n",
294+
" return PowerSat"
295+
]
296+
},
297+
{
298+
"cell_type": "markdown",
299+
"metadata": {},
300+
"source": [
301+
"Satellite parameters are also modified for the power-constrained environment."
302+
]
303+
},
304+
{
305+
"cell_type": "code",
306+
"execution_count": 13,
307+
"metadata": {},
308+
"outputs": [],
309+
"source": [
310+
"SAT_ARGS_POWER = {}\n",
311+
"SAT_ARGS_POWER.update(SAT_ARGS)\n",
312+
"SAT_ARGS_POWER.update(\n",
313+
" dict(\n",
314+
" batteryStorageCapacity=120.0 * 3600,\n",
315+
" storedCharge_Init=lambda: 120.0 * 3600 * np.random.uniform(0.4, 1.0),\n",
316+
" rwBasePower=20.4,\n",
317+
" instrumentPowerDraw=-10,\n",
318+
" thrusterPowerDraw=-30,\n",
319+
" nHat_B=np.array([0, 0, -1]),\n",
320+
" wheelSpeeds=lambda: np.random.uniform(-2000, 2000, 3),\n",
321+
" desatAttitude=\"nadir\",\n",
322+
" )\n",
323+
")"
324+
]
325+
},
326+
{
327+
"cell_type": "markdown",
328+
"metadata": {},
329+
"source": [
330+
"Finally, the environment can be initialized."
331+
]
332+
},
333+
{
334+
"cell_type": "code",
335+
"execution_count": null,
336+
"metadata": {},
337+
"outputs": [],
338+
"source": [
339+
"duration = 5700.0 * 5 # 5 orbits\n",
340+
"target_distribution = \"uniform\"\n",
341+
"n_targets = 3000\n",
342+
"n_ahead = 32\n",
343+
"\n",
344+
"if target_distribution == \"uniform\":\n",
345+
" targets = scene.UniformTargets(n_targets)\n",
346+
"elif target_distribution == \"cities\":\n",
347+
" targets = scene.CityTargets(n_targets)\n",
348+
"\n",
349+
"env = SatelliteTasking(\n",
350+
" satellite=power_sat_generator(n_ahead=32, include_time=False)(\n",
351+
" name=\"EO1-power\",\n",
352+
" sat_args=SAT_ARGS_POWER,\n",
353+
" ),\n",
354+
" scenario=targets,\n",
355+
" rewarder=data.UniqueImageReward(),\n",
356+
" sim_rate=0.5,\n",
357+
" max_step_duration=300.0,\n",
358+
" time_limit=duration,\n",
359+
" failure_penalty=0.0,\n",
360+
" terminate_on_time_limit=True,\n",
361+
" log_level=\"INFO\",\n",
362+
")\n",
363+
"_ = env.reset()"
364+
]
365+
}
366+
],
367+
"metadata": {
368+
"kernelspec": {
369+
"display_name": ".venv",
370+
"language": "python",
371+
"name": "python3"
372+
},
373+
"language_info": {
374+
"codemirror_mode": {
375+
"name": "ipython",
376+
"version": 3
377+
},
378+
"file_extension": ".py",
379+
"mimetype": "text/x-python",
380+
"name": "python",
381+
"nbconvert_exporter": "python",
382+
"pygments_lexer": "ipython3",
383+
"version": "3.10.11"
384+
}
385+
},
386+
"nbformat": 4,
387+
"nbformat_minor": 2
388+
}

examples/_default.rst

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Environments
1010
satellite_configuration
1111
multiagent_envs
1212
cloud_environment
13+
aeos
1314

1415

1516
Training

0 commit comments

Comments
 (0)