In [2]:
import numpy as np
inf = np.inf
isclose = np.isclose
π = np.pi
arctan = np.arctan
sign = np.sign
In [3]:
class Device(object):
def __init__(self):
# position, velocity, acceleration
self.x = 0
self.v = 0
# mass, drag, spring-constant, spring equilibrium point
self.m = 1 # mass, inertial, moment of inertia
self.b = 0 # drag
self.k = 0 # spring
self.x0 = 0 # equilibrium point
# gain (maximum control), sensitivity
self.g = 1
self.s = 1
self.t_step = 1/100
def control_force(self,c):
'''represents physical limitation of the control'''
# bring instance variables into local scope
g = self.g
s = self.s
def _control_force(g,s):
if s == 0:
def _fn(c):
return g * min(max(c,-1),1)
elif s > 0:
def _fn(c):
return g * arctan(s*c) / arctan(s)
else:
def _fn(c):
return sign(c) * g * (1 - arctan(s*(1-abs(c))) / arctan(s))
return _fn
return _control_force(g,s)(c)
def external_force(self):
'''external forces (drag, etc)'''
# bring instance variables into local scope
x = self.x
v = self.v
b = self.b
k = self.k
x0 = self.x0
Fdrag = int(v<0) * b * v**2
Fspring = -k * (x - x0)
return Fdrag + Fspring
def __call__(self,c,Δt):
'''set the control to c for Δt time
return the position and velocity after Δt has passed
This device has a characteristic "mass" which gives
it a certain momentum that the control must fight against
ΣF = Δp / Δt
ΣF = m * Δv / Δt
v = v + (ΣF/m)*Δt
x = x + v*Δt
'''
# bring instance variables into local scope
x = self.x
v = self.v
m = self.m
t_step = self.t_step
Fcontrol = self.control_force(c)
t = 0
while t < Δt:
# sum of forces
ΣF = Fcontrol + self.external_force()
# change in velocity and position
x = x + v*t_step
v = v + (ΣF/m)*t_step
t += t_step
# save parameters to class instance
self.x = x
self.v = v
return x,v