This repository has been archived by the owner on Sep 28, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsim.py
182 lines (133 loc) · 4.38 KB
/
sim.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
from math import sin, cos, tan, pi
import numpy as np
from thermal import thermal
#
g = 9.81
# air density
ρ = 1.225
# airplane mass
m = 0.5
# wing area
S = 0.1
# lilienthal polar constants c0+c_l*c2
c0 = 0.06
c2 = 0.04
class Simulation:
"""A thermal gliding simulation.
"""
@classmethod
def generate_state(x, y, z, μ, φ):
"""Pack state variables into state vector
"""
return np.array([x, y, z, μ, φ])
def __init__(
self, initialstate, μstep=(15 * np.pi / 360), dt=0.01, max_iterations=1000
):
self.allstates = np.empty((max_iterations + 1, len(initialstate)))
self.allstates[0, :] = initialstate
self.iteration = 0
self.max_iterations = max_iterations
self.μstep = μstep
self.dt = dt
self.v = 10
self.upper_bounds = [5000, 5000, 2000, pi / 2, pi]
self.lower_bounds = [-5000, -5000, 0, -pi / 2, -pi]
self.diff_upper_bounds = [
self.v,
self.v,
self.v,
μstep / dt,
g * tan(μstep) * dt / self.v ** 2,
]
self.diff_lower_bounds = -np.array(self.diff_upper_bounds)
@property
def laststate(self):
if self.iteration > 0:
return self.allstates[self.iteration - 1]
else:
return None
@property
def state(self):
return self.allstates[self.iteration]
@state.setter
def state(self, newstate):
""" Returns the current state. """
self.allstates[self.iteration] = newstate
@property
def dstate(self):
""" Returns the gradient of the current state """
return (self.state - self.laststate) / self.dt
@property
def full_state(self):
""" Returns the normalised state and the normalised dstate. """
norm_state = [
_interp(statevar, self.lower_bounds[i], self.upper_bounds[i])
for i, statevar in enumerate(self.state)
]
norm_dstate = [
_interp(statevar, self.diff_lower_bounds[i], self.diff_upper_bounds[i])
for i, statevar in enumerate(self.dstate)
]
return norm_state + norm_dstate
def step(self, action: int):
"""Do simulation step taking agent's action into account.
action - 1: decrease bank angle, 2: hold bank angle, 3: increase bank angle
bank angle is changed by μstep.
The observation has the full state first, then it's gradient an then the liftgradient.
Returns obersvation, reward, done
"""
self._do_step(action)
done = self.iteration >= self.max_iterations
b = 1.0
liftgradient = self.get_liftgradient(self.full_state)
observation = np.concatenate([self.state, self.dstate, [liftgradient]])
return observation, done
def _do_step(self, action: int):
# increment iteration
self.iteration += 1
# unpack last state
x, y, z, μ, φ = self.laststate
# calculate thermal velocity
w = thermal(x, y, z) * 10
#
if action == 0:
μ_new = μ - self.μstep
elif action == 1:
μ_new = μ
else:
μ_new = μ + self.μstep
μ_new = np.clip(μ_new, np.deg2rad(-45), np.deg2rad(45))
v = self.v
l = v * self.dt
c_d = 0.03
z_new = z + w * self.dt - 0.5 * ρ * v ** 2 * c_d * S * l / (m * g)
x_new = x + l * cos(φ)
y_new = y + l * sin(φ)
if μ_new != 0.0:
r = v ** 2 / (g * np.tan(μ_new))
φ_new = (φ + l / r) % (2 * pi)
else:
φ_new = φ
self.state = x_new, y_new, z_new, μ_new, φ_new
def get_liftgradient(self, state):
x0, y0, z = state[:3]
phi = state[4]
x1, y1 = x0 + sin(phi), y0 + cos(phi)
l0 = thermal(x0, y0, z)
l1 = thermal(x1, y1, z)
return l1 - l0
def _interp(x, xp1, xp2, yp1=0, yp2=1):
if x < xp1:
return yp1
elif x > xp2:
return yp2
return (x - xp1) / (xp2 - xp1) * (yp2 - yp1)
def simple_thermal(x, y, z, *args, **kwargs):
if np.sqrt(x ** 2 + y ** 2) < 20:
return 2.0
else:
return 0.0
def drag(v, μ):
return c0 + c2 * (2 * m * g) / (ρ * S * np.cos(μ))
def drag_differential(v, μ):
return c0 - 4 * c2 * ((2 * m * g) / (ρ * S * np.cos(μ))) ** 2 / v ** 5