def test_maneuver_total_time(): dt1 = 10.0 * u.s dt2 = 100.0 * u.s _v = np.zeros(3) * u.km / u.s # Unused velocity expected_total_time = 110.0 * u.s man = Maneuver((dt1, _v), (dt2, _v)) assert_quantity_allclose(man.get_total_time(), expected_total_time)
def test_maneuver_raises_error_if_units_are_wrong(): wrong_dt = 1.0 _v = np.zeros(3) * u.km / u.s # Unused velocity with pytest.raises(u.UnitsError) as excinfo: Maneuver([wrong_dt, _v]) assert ("UnitsError: Argument 'dts' to function '_initialize' must be in units convertible to 's'." in excinfo.exconly())
def test_maneuver_raises_error_if_dvs_are_not_vectors(): dt = 1 * u.s wrong_dv = 1 * u.km / u.s with pytest.raises(ValueError) as excinfo: Maneuver((dt, wrong_dv)) assert "ValueError: Delta-V must be three dimensions vectors" in excinfo.exconly( )
def test_maneuver_total_time(): dt1 = 10.0 * u.s dt2 = 100.0 * u.s _v = np.zeros(3) * u.km / u.s # Unused velocity expected_total_time = 110.0 * u.s man = Maneuver((dt1, _v), (dt2, _v)) assert_almost_equal(man.get_total_time().to(u.s).value, expected_total_time.value)
def _split_burn_to_impulse_list(self, dv): split_impulse = dv / self.simulation_ratio # applying these maneuvers takes a long time; reduce simulation ratio? impulse = [] for x in range(0, self.simulation_ratio): impulse.append((self.simulation_step, split_impulse)) maneuvers = Maneuver(*impulse) return maneuvers
def test_maneuver_constructor_raises_error_if_invalid_delta_v(): dv1 = np.zeros(3) * u.km / u.s dv2 = np.ones(2) * u.km / u.s # Incorrect dv with pytest.raises(ValueError) as excinfo: with warnings.catch_warnings(): # Different length numpy arrays generate a deprecation warning. warnings.simplefilter("ignore", category=np.VisibleDeprecationWarning) Maneuver((0 * u.s, dv1), (2 * u.s, dv2)) assert "Delta-V must be three dimensions vectors" in excinfo.exconly()
def Earth_to_Jupiter(delta_v): """ Calculates the manuever and the launch windows from Earth to Jupiter, assuming tangential impulsive maneuver from Earth orbit around the Sun Earth_to_Jupiter(dv): 'dv' is given with velocity units (u.m/u.s) Returns: ss_transfer = trasfer orbit object t = time to the first next launch window T = period between possible launch windows t_transfer = time of transfer from launch to Jupiter arrival """ dv = delta_v * v_Earth_dir # Initial transfer orbit calculation (to obtain transfer time) man_tmp = Maneuver.impulse(dv) transfer = ss_Earth.apply_maneuver(man_tmp) # Angles calculation # nu_Jupiter: nu of transfer orbit where it crosses Jupiter nu_Jupiter = (np.math.acos((transfer.p/R_J - 1)/transfer.ecc)*u.rad) ang_vel_Earth = (norm(v_Earth).value / R_E.value)*u.rad/u.s ang_vel_Jupiter = (norm(v_Jupiter).value / R_J.value)*u.rad/u.s # Transfer time M = nu_to_M(nu_Jupiter, transfer.ecc) t_transfer = M/transfer.n # Delta_nu at launch (Final nu - nu that Jupiter walks during transfer) delta_nu_at_launch = nu_Jupiter - ang_vel_Jupiter * t_transfer # Angles in radians nu_init = angle(r_Earth, r_Jupiter)*u.rad # Next launch window in t: t = (nu_init - delta_nu_at_launch) / (ang_vel_Earth - ang_vel_Jupiter) # Time between windows T = (2*np.pi*u.rad) / (ang_vel_Earth - ang_vel_Jupiter) # If time to the windows is negative (in the past), we should wait until next window while t < 0: t = t + T # Earth at launch ss_Earth_launch = ss_Earth.propagate(t) launch_dir = ss_Earth_launch.v / norm(ss_Earth_launch.v) dv = delta_v * launch_dir # Maneuver calculation man = Maneuver((t, dv)) ss_transfer = ss_Earth.apply_maneuver(man) return ss_transfer, t, T, t_transfer
def test_apply_maneuver_correct_dimensions(): orb = Orbit.from_vectors( Moon, [-22681.58976181, 942.47776988, 0] * u.km, [-0.04578917, -0.19408599, 0.0] * u.km / u.s, Time("2023-08-30 23:14", scale="tdb"), ) man = Maneuver((1 * u.s, [0.01, 0, 0] * u.km / u.s)) new_orb = orb.apply_maneuver(man, intermediate=False) assert new_orb.r.ndim == 1 assert new_orb.v.ndim == 1
def prograde_maneuver(o: Orbit, dv, delay): """ Generates maneuver in the prograde direction. dv - expressed as dimensionless float (in m/s) delay - delay expressed as u.s (in seconds) """ if delay is None: delay = 0 * u.s # If the units used are km rather than m, then we need to shrink the dv value by 3 orders of magnitude if o.v[0].unit == u.km / u.s: dv /= 1000 # normalize v len = np.linalg.norm(o.v) vnorm = o.v / len.value v = vnorm * dv man = Maneuver((delay, v)) return man
iss_30m = iss.propagate(30 * u.min) iss_30m.epoch iss_30m.nu.to(u.deg) #plot (iss_30m) earth = Orbit.from_body_ephem(Earth) #plot(earth) earth_30d = earth.propagate(30 * u.day) #plot(earth_30d) from poliastro.maneuver import Maneuver dv = [5, 0, 0] * u.m / u.s man = Maneuver.impulse(dv) man = Maneuver((0 * u.s, dv)) ss_i = Orbit.circular(Earth, alt=700 * u.km) ss_i #plot(ss_i) hoh = Maneuver.hohmann(ss_i, 36000 * u.km) hoh.get_total_cost() hoh.get_total_time() print(hoh.get_total_cost()) print(hoh.get_total_time()) hoh.impulses[0] hoh[0] tuple(val.decompose([u.km, u.s]) for val in hoh[1]) ss_f = ss_i.apply_maneuver(hoh)
def plane_change_maneuver(o: Orbit, theta): """ Generates inclination change maneuver. This changes the inclination by theta degress. o - initial orbit theta - expressed in plain float as degrees or as u.deg. This maneuver in general should be performed at either ascending or descending node. returns: maneuver that can be applied on the initial orbit. To perform this calculation, a local coords system is needed. (i,j,k) unit vectors are defined. The coords system is oriented as follows: j^ (normal vector) | | | +----------> (velocity vector) ----------- orbital plane k i (towards viewer = r vector, from Earth center to spacecraft) In this coords system, the inclination change is done in i,j plane. _^ vf__/ \ __/ \ dv = vf - v __/ \ / \ +----------------> v First we need to calculate the expected final velocity (vf). The get the delta-v vector as a difference of vf minus the original velocity. """ # Let's assume the delay is not configurable and the maneuver always takes place immediately. delay = 0 * u.s if type(theta) == float: theta = theta * u.deg # Get the v (current velocity) and r (distance from Earth center) as vectors and make both of them dimiensionless # Also convert them to km first. We want to get rid of the units, because they complicate the cross product. v = o.v.to(u.km / u.s) / u.km * u.s r = o.r.to(u.km) / u.km # This is a normal vector. It is perpendicular to the plane defined by r (connected Earth center with the object) and # v (velocity). This has the right direction. normal = np.cross(v, r) # Now we need to calculate the right vectors magnitude normal = normal / np.linalg.norm(normal).value # Let's get 3 unit vectors and the r,v,n mangitudes (lenghts) expressed as floats v_len = np.linalg.norm(v).value n_len = np.linalg.norm(normal).value r_len = np.linalg.norm(r).value # get the unit vectors i = v / v_len j = normal / n_len k = r / r_len #print("i=%s" % i) #print("j=%s" % j) #print("k=%s" % k) # final velocity after maneuver vf = i * v_len * np.cos(theta) + j * v_len * np.sin(theta) #print("vf=%s" % vf) # The maneuver dv is a difference between original and final velocity dv = vf - v dv = dv * u.km / u.s # Ok, turn this into maneuver and we're done! man = Maneuver((delay, dv)) return man
def change_periapsis(conn: Client, node_ut: float, new_periapsis_alt: float): """Execute to change periapsis Execute to change periapsis. To be simple, burn only horizontal, so it might be not optimal Args: conn: kRPC connection new_periapsis_alt: new periapsis altitude node_ut: schedule burn at specific time, if not specified burn at next apoapsis Returns: return nothing, return when procedure finished """ vessel = conn.space_center.active_vessel attractor = vessel.orbit.body reference_frame = attractor.non_rotating_reference_frame attractor_surface_radius = ( vessel.orbit.apoapsis - vessel.orbit.apoapsis_altitude ) new_periapsis = new_periapsis_alt + attractor_surface_radius if not vessel.orbit.apoapsis < 0 and new_periapsis >= vessel.orbit.apoapsis: return krpc_bodies, poliastro_bodies = krpc_poliastro_bodies(conn) ut = conn.space_center.ut time_to_burn = node_ut - ut is_raising = new_periapsis > vessel.orbit.periapsis prograde_vector_at_node = prograde_vector_at_ut( vessel.orbit, node_ut, reference_frame ) anti_radial_vector_at_node = anti_radial_vector_at_ut( vessel.orbit, node_ut, reference_frame ) normal_vector_at_node = normal_vector_at_ut( vessel.orbit, node_ut, reference_frame ) horizontal_vector_at_node = horizontal_vector_at_ut( vessel.orbit, node_ut, reference_frame ) burn_direction = 1 if is_raising else -1 burn_vector = burn_direction * horizontal_vector_at_node r_i = vessel.position(reference_frame) * AstropyUnit.m v_i = vessel.velocity(reference_frame) * AstropyUnit.m / AstropyUnit.s ss_i = PoliastroOrbit.from_vectors( poliastro_bodies[attractor.name], r_i, v_i ) # get upper bound of deltaV min_dv = 0 max_dv = 0 if is_raising: max_dv = 0.25 tmp_new_pe = vessel.orbit.periapsis while tmp_new_pe < new_periapsis: max_dv *= 2 tmp_burn = max_dv * burn_vector * AstropyUnit.m / AstropyUnit.s tmp_maneuver = Maneuver((time_to_burn * AstropyUnit.s, tmp_burn)) tmp_new_ss = ss_i.apply_maneuver(tmp_maneuver) tmp_new_pe = abs(tmp_new_ss.r_p.to(AstropyUnit.m).value) if max_dv > 100000: break else: # horizontal speed should be max_dv for lowering periapsis max_dv = vessel.flight(reference_frame).horizontal_speed # binary search while max_dv - min_dv > 0.01: tmp_dv = (max_dv + min_dv) / 2.0 tmp_burn = tmp_dv * burn_vector * AstropyUnit.m / AstropyUnit.s tmp_maneuver = Maneuver((time_to_burn * AstropyUnit.s, tmp_burn)) tmp_new_ss = ss_i.apply_maneuver(tmp_maneuver) tmp_new_pe = abs(tmp_new_ss.r_p.to(AstropyUnit.m).value) if (is_raising and tmp_new_pe > new_periapsis) or ( not is_raising and tmp_new_pe < new_periapsis ): max_dv = tmp_dv else: min_dv = tmp_dv dv_vector = burn_vector * (max_dv + min_dv) / 2.0 dv_prograde = dot(dv_vector, prograde_vector_at_node) * norm(dv_vector) dv_anti_radial = dot(dv_vector, anti_radial_vector_at_node) * norm( dv_vector ) dv_normal = dot(dv_vector, normal_vector_at_node) * norm(dv_vector) vessel.control.add_node( node_ut, prograde=dv_prograde, radial=dv_anti_radial, normal=dv_normal ) # TODO: replace this logic to burn for dynamic change periapsis? # instead of just executing node, dynamically update direction execute_next_node(conn)