def __init__(self, mu_planet, spacecraft, atmosphere, V0, X0, sim_time, R, console_output=True, dt=0.1): # Pull in spacecraft and atmosphere classes for convenient variable access self.spacecraft = spacecraft self.atmosphere = atmosphere # Verbose solver output flag self.console_output = console_output # Miscellaneous initial conditions and constant variables self.mu = mu_planet self.InitalV = V0 self.initalpos = X0 self.simtime = sim_time self.dt = dt self.N = np.int(self.simtime / dt) self.R = R # Create empty arrays for storage of results from ODE solver self.sol = np.zeros([self.N, 6]) self.V_xyz = np.zeros([self.N, 3]) self.pos_xyz = np.zeros([self.N, 3]) self.V_mag = np.zeros(self.N) self.pos_mag = np.zeros(self.N) self.theta = np.zeros(self.N) self.theta = np.zeros(self.N) self.h = np.zeros(self.N) # Forces in inertial frame self.F_x = np.zeros([self.N]) self.F_y = np.zeros([self.N]) self.F_z = np.zeros([self.N]) self.F_mag = np.zeros(self.N) self.V_xyz[0, :] = V0 self.pos_xyz[0, :] = X0 # Velocity and position vector magnitudes self.V_mag[0] = np.linalg.norm(self.V_xyz[0, :]) self.pos_mag[0] = np.linalg.norm(self.pos_xyz[0, :]) # Interpolated atmospheric variables self.solver_rho = np.zeros(self.N) self.solver_mu = np.zeros(self.N) self.solver_a = np.zeros(self.N) self.solver_p = np.zeros(self.N) self.solver_T = np.zeros(self.N) self.solver_time = np.zeros(self.N) # Altitude above surface of planet (assumed to be perfectly spherical) self.h[0] = np.linalg.norm(self.pos_xyz[0, :]) - self.R self.lift = np.zeros(self.N) self.drag = np.zeros(self.N) self.side_force = np.zeros(self.N) self.forces_rotating = np.zeros([self.N, 3]) self.forces_inertial = np.zeros([self.N, 3]) self.alpha = np.zeros(self.N) self.theta = np.zeros(self.N) # Interpolate atmospheric variables self.solver_rho[0], self.solver_a[0], \ self.solver_p[0], self.solver_T[0], \ self.solver_mu[0] = interpolate_atmosphere(self, self.h[0]) self.lift[0] = fcl.aero_force(self.solver_rho[0], self.V_mag[0], self.spacecraft.Cl, self.spacecraft.A) self.drag[0] = fcl.aero_force(self.solver_rho[0], self.V_mag[0], self.spacecraft.Cd, self.spacecraft.A) self.side_force[0] = fcl.aero_force(self.solver_rho[0], self.V_mag[0], self.spacecraft.Cs, self.spacecraft.A) self.forces_rotating[0, :] = np.array([self.lift[0], self.drag[0], self.side_force[0]]) # Calculate Euler angles (pitch and yaw; roll is assumed to be zero) self.alpha[0], self.theta[0] = rotate_lib.vector_to_euler(self.pos_xyz[0, 0], self.pos_xyz[0, 1], self.pos_xyz[0, 2]) # Transform aero forces from rotating to inertial frame self.forces_inertial[0, :] = rotate_lib.roty(self.forces_rotating[0, :], self.alpha[0], mode='rad') self.forces_inertial[0, :] = rotate_lib.rotz(self.forces_rotating[0, :], self.theta[0], mode='rad') # Split forces into components self.F_x[0] = self.forces_inertial[0, 0] self.F_y[0] = self.forces_inertial[0, 1] self.F_z[0] = self.forces_inertial[0, 2] # Calculate magnitude of forces self.F_mag[0] = np.abs(np.linalg.norm([self.F_x[0], self.F_y[0], self.F_z[0]])) # Define list of keys for obejct dict (self.__dict__) # To be used by truncation, event interpolation, and variable # assignment functions self.l = ['V_xyz', 'V_mag', 'F_mag', 'F_x', 'F_y', 'F_z', 'pos_xyz', 'pos_mag', 'alpha', 'theta', 'drag', 'lift', 'side_force', 'h', 'solver_time', 'solver_rho', 'solver_p', 'solver_T', 'solver_mu', 'solver_a', 'forces_inertial', 'forces_rotating'] if self.console_output == True: print 'MODEL INITIALISED. INITIAL STEP COUNT: %i' % self.N return None
def simulate_dopri(self, rtol=1E-4, nsteps=1E8): # Store ODE solver variables #self.dt = dt self.rtol = rtol self.nsteps = nsteps # Set up ODE solver self.eq = integrate.ode(orbit_xyz).set_integrator('dop853', nsteps=self.nsteps, rtol=self.rtol) y_init = [self.InitalV[0], self.InitalV[1], self.InitalV[2], self.initalpos[0], self.initalpos[1], self.initalpos[2]] self.y_init = y_init self.eq.set_initial_value(y_init, t=0) #set inital values params = [self.F_x[0], self.F_y[0], self.F_z[0], self.spacecraft.m, self.mu] self.eq.set_f_params(params) for i in range(1, self.N): # Update stored counter self.i = i # Update ODE solver params update_params = [self.F_x[i-1], self.F_y[i-1], self.F_z[i-1], self.spacecraft.m, self.mu] self.eq.set_f_params(update_params) # Solve ODE system self.sol[i, :] = self.eq.integrate(self.eq.t + self.dt) self.solver_time[i] = self.eq.t # Unpack ODE solver results self.V_xyz[i, :] = self.sol[i, 0:3] self.V_mag[i] = np.linalg.norm(self.V_xyz[i, :]) self.pos_xyz[i, :] = self.sol[i, 3:6] self.pos_mag[i] = np.linalg.norm(self.pos_xyz[i, :]) # Update altitude self.h[i] = np.linalg.norm(self.pos_xyz[i, :]) - self.R # Interpolate atmospheric variables self.solver_rho[i], self.solver_a[i], \ self.solver_p[i], self.solver_T[i], \ self.solver_mu[i] = interpolate_atmosphere(self, self.h[i]) # Axial aero forces self.lift[i] = fcl.aero_force(self.solver_rho[i], self.V_mag[i], self.spacecraft.Cl, self.spacecraft.A) self.drag[i] = fcl.aero_force(self.solver_rho[i], self.V_mag[i], self.spacecraft.Cd, self.spacecraft.A) self.side_force[i] = fcl.aero_force(self.solver_rho[i], self.V_mag[i], self.spacecraft.Cs, self.spacecraft.A) self.forces_rotating[i, :] = np.array([self.lift[i], self.drag[i], self.side_force[i]]) # Calculate Euler angles (pitch and yaw; roll is assumed to be zero) self.alpha[i], self.theta[i] = rotate_lib.vector_to_euler(self.sol[i, 3], self.sol[i, 4], self.sol[i, 5]) # Transform aero forces from rotating to inertial frame self.forces_inertial[i, :] = rotate_lib.roty(self.forces_rotating[i, :], self.alpha[i], mode='rad') self.forces_inertial[i, :] = rotate_lib.rotz(self.forces_rotating[i, :], self.theta[i], mode='rad') # Split forces into components self.F_x[i] = -self.forces_inertial[i, 0] self.F_y[i] = -self.forces_inertial[i, 1] self.F_z[i] = -self.forces_inertial[i, 2] # Calculate magnitude of forces self.F_mag[i] = np.abs(np.linalg.norm([self.F_x[i], self.F_y[i], self.F_z[i]])) # Print integration progress if self.console_output == True: if np.mod(i, self.N/100) == 0: print '%3.1f%%; ITERATION: %i; ALTITUDE: %f km' % \ (100*(np.float(i)/self.N), i, self.h[i]/1000) # Check for ground strike if self.h[i] <= 0: print 'GROUND STRIKE EVENT (ALTITUDE = 0) DETECTED BETWEEN ' \ 'INDEXES %i AND %i' % (i-1, i) break # Check for atmospheric model interpolation errors # (OUT_OF_BOUNDS error) if np.isnan(self.solver_rho[i]) == True: print 'ERROR: ATMOSPHERIC INTERPOLATION OUT OF BOUNDS AT ' \ 'INDEX %i, TRY EXPANDING ALTITUDE RANGE' % i break self.final_step_event() return self.sol