Exemple #1
0
def q_err(q_t, q_r):
    # """
    # Compute angular error between two unit quaternions.

    # :param q_t: New quaternion
    # :type q_t: ca.DM, list, np.array
    # :param q_r: Reference quaternion
    # :type q_r: ca.DM, list, np.array
    # :return: vector corresponding to SK matrix
    # :rtype: ca.DM

    # """
    q_upper_t = [q_r[3], -q_r[0], -q_r[1], -q_r[2]]
    q_lower_t = [q_t[3], q_t[0], q_t[1], q_t[2]]

    qd_t = [
        q_upper_t[0] * q_lower_t[0] - q_upper_t[1] * q_lower_t[1] -
        q_upper_t[2] * q_lower_t[2] - q_upper_t[3] * q_lower_t[3],
        q_upper_t[1] * q_lower_t[0] + q_upper_t[0] * q_lower_t[1] +
        q_upper_t[2] * q_lower_t[3] - q_upper_t[3] * q_lower_t[2],
        q_upper_t[0] * q_lower_t[2] - q_upper_t[1] * q_lower_t[3] +
        q_upper_t[2] * q_lower_t[0] + q_upper_t[3] * q_lower_t[1],
        q_upper_t[0] * q_lower_t[3] + q_upper_t[1] * q_lower_t[2] -
        q_upper_t[2] * q_lower_t[1] + q_upper_t[3] * q_lower_t[0]
    ]

    phi_t = ca.atan2(2 * (qd_t[0] * qd_t[1] + qd_t[2] * qd_t[3]),
                     1 - 2 * (qd_t[1]**2 + qd_t[2]**2))
    theta_t = ca.asin(2 * (qd_t[0] * qd_t[2] - qd_t[3] * qd_t[1]))
    psi_t = ca.atan2(2 * (qd_t[0] * qd_t[3] + qd_t[1] * qd_t[2]),
                     1 - 2 * (qd_t[2]**2 + qd_t[3]**2))

    return ca.vertcat(phi_t, theta_t, psi_t)
Exemple #2
0
 def from_quat(cls, q):
     assert q.shape == (4, 1) or q.shape == (4, )
     e = ca.SX(3, 1)
     a = q[0]
     b = q[1]
     c = q[2]
     d = q[3]
     e[0] = ca.atan2(2 * (a * b + c * d), 1 - 2 * (b**2 + c**2))
     e[1] = ca.asin(2 * (a * c - d * b))
     e[2] = ca.atan2(2 * (a * d + b * c), 1 - 2 * (c**2 + d**2))
     return e
def solar_elevation_angle(latitude, day_of_year, time):
    """
    Elevation angle of the sun [degrees] for a local observer.
    :param latitude: Latitude [degrees]
    :param day_of_year: Julian day (1 == Jan. 1, 365 == Dec. 31)
    :param time: Time after local solar noon [seconds]
    :return: Solar elevation angle [degrees] (angle between horizon and sun). Returns 0 if the sun is below the horizon.
    """

    # Solar elevation angle (including seasonality, latitude, and time of day)
    # Source: https://www.pveducation.org/pvcdrom/properties-of-sunlight/elevation-angle
    declination = declination_angle(day_of_year)

    solar_elevation_angle = cas.asin(
        sind(declination) * sind(latitude) + cosd(declination) *
        cosd(latitude) * cosd(time / 86400 * 360)) * 180 / cas.pi  # in degrees
    solar_elevation_angle = cas.fmax(solar_elevation_angle, 0)
    return solar_elevation_angle
Exemple #4
0
def create_rocket_stage_phase(mocp, phase_name, p, **kwargs):
    is_powered = kwargs['is_powered']
    has_heating = kwargs['has_heating']
    drag_area = kwargs['drag_area']
    maxQ = kwargs['maxQ']
    init_mass = kwargs['init_mass']
    init_position = kwargs['init_position']
    init_velocity = kwargs['init_velocity']

    if is_powered:
        init_steering = kwargs['init_steering']

        engine_type = kwargs['engine_type']
        engine_count = kwargs['engine_count']
        mdot_max = kwargs['mdot_max']
        mdot_min = kwargs['mdot_min']

        if engine_type == 'vacuum':
            effective_exhaust_velocity = kwargs['effective_exhaust_velocity']
        else:
            exhaust_velocity = kwargs['exhaust_velocity']
            exit_area = kwargs['exit_area']
            exit_pressure = kwargs['exit_pressure']

    duration = mocp.create_phase(phase_name, init=0.001, n_intervals=2)

    # Total vessel mass
    mass = mocp.add_trajectory(phase_name, 'mass', init=init_mass)

    # ECEF position vector
    rx = mocp.add_trajectory(phase_name, 'rx', init=init_position[0])
    ry = mocp.add_trajectory(phase_name, 'ry', init=init_position[1])
    rz = mocp.add_trajectory(phase_name, 'rz', init=init_position[2])
    r_vec = casadi.vertcat(rx, ry, rz)

    # ECEF velocity vector
    vx = mocp.add_trajectory(phase_name, 'vx', init=init_velocity[0])
    vy = mocp.add_trajectory(phase_name, 'vy', init=init_velocity[1])
    vz = mocp.add_trajectory(phase_name, 'vz', init=init_velocity[2])
    v_vec = casadi.vertcat(vx, vy, vz)

    if is_powered:

        # ECEF steering unit vector
        ux = mocp.add_trajectory(phase_name, 'ux', init=init_steering[0])
        uy = mocp.add_trajectory(phase_name, 'uy', init=init_steering[1])
        uz = mocp.add_trajectory(phase_name, 'uz', init=init_steering[2])

        u_vec = casadi.vertcat(ux, uy, uz)

        # Engine mass flow for a SINGLE engine
        mdot = mocp.add_trajectory(phase_name, 'mdot', init=mdot_max)
        mdot_rate = mocp.add_trajectory(phase_name, 'mdot_rate', init=0.0)

    if has_heating:
        heat = mocp.add_trajectory(phase_name, 'heat', init=0.0)

    ## Dynamics

    r_squared = rx**2 + ry**2 + rz**2 + 1e-8
    v_squared = vx**2 + vy**2 + vz**2 + 1e-8
    airspeed = v_squared**0.5
    r = r_squared**0.5
    altitude = r - 1
    r3_inv = r_squared**(-1.5)

    mocp.add_path_output(
        'downrange_distance',
        casadi.asin(
            casadi.norm_2(
                casadi.cross(
                    r_vec / r,
                    casadi.SX(
                        [p.launch_site_x, p.launch_site_y,
                         p.launch_site_z])))))
    mocp.add_path_output('altitude', altitude)
    mocp.add_path_output('airspeed', airspeed)
    mocp.add_path_output('vertical_speed', (r_vec.T / r) @ v_vec)
    mocp.add_path_output(
        'horizontal_speed_ECEF',
        casadi.norm_2(v_vec - ((r_vec.T / r) @ v_vec) * (r_vec / r)))

    # Gravitational acceleration (mu=1 is omitted)
    gx = -r3_inv * rx
    gy = -r3_inv * ry
    gz = -r3_inv * rz

    # Atmosphere
    atmosphere_fraction = casadi.exp(-altitude / p.scale_height)
    air_pressure = p.air_pressure_MSL * atmosphere_fraction
    air_density = p.air_density_MSL * atmosphere_fraction
    dynamic_pressure = 0.5 * air_density * v_squared
    mocp.add_path_output('dynamic_pressure', dynamic_pressure)

    if is_powered:
        lateral_airspeed = casadi.sqrt(
            casadi.sumsqr(v_vec - ((v_vec.T @ u_vec) * u_vec)) + 1e-8)
        lateral_dynamic_pressure = 0.5 * air_density * lateral_airspeed * airspeed  # Same as Q * sin(alpha), but without trigonometry
        mocp.add_path_output('lateral_dynamic_pressure',
                             lateral_dynamic_pressure)

        mocp.add_path_output(
            'alpha',
            casadi.acos((v_vec.T @ u_vec) / airspeed) * 180.0 / pi)
        mocp.add_path_output(
            'pitch', 90.0 - casadi.acos((r_vec.T / r) @ u_vec) * 180.0 / pi)

    # Thrust force
    if is_powered:
        if engine_type == 'vacuum':
            engine_thrust = effective_exhaust_velocity * mdot
        else:
            engine_thrust = exhaust_velocity * mdot + exit_area * (
                exit_pressure - air_pressure)

        total_thrust = engine_thrust * engine_count
        mocp.add_path_output('total_thrust', total_thrust)

        Tx = total_thrust * ux
        Ty = total_thrust * uy
        Tz = total_thrust * uz
    else:
        Tx = 0.0
        Ty = 0.0
        Tz = 0.0

    # Drag force
    drag_factor = (-0.5 * drag_area) * air_density * airspeed
    Dx = drag_factor * vx
    Dy = drag_factor * vy
    Dz = drag_factor * vz

    # Coriolis Acceleration
    ax_Coriolis = 2 * vy * p.body_rotation_speed
    ay_Coriolis = -2 * vx * p.body_rotation_speed
    az_Coriolis = 0.0

    # Centrifugal Acceleration
    ax_Centrifugal = rx * p.body_rotation_speed**2
    ay_Centrifugal = ry * p.body_rotation_speed**2
    az_Centrifugal = 0.0

    # Acceleration
    ax = gx + ax_Coriolis + ax_Centrifugal + (Dx + Tx) / mass
    ay = gy + ay_Coriolis + ay_Centrifugal + (Dy + Ty) / mass
    az = gz + az_Coriolis + az_Centrifugal + (Dz + Tz) / mass

    if is_powered:
        mocp.set_derivative(mass, -engine_count * mdot)
        mocp.set_derivative(mdot, mdot_rate)
    else:
        mocp.set_derivative(mass, 0.0)

    mocp.set_derivative(rx, vx)
    mocp.set_derivative(ry, vy)
    mocp.set_derivative(rz, vz)
    mocp.set_derivative(vx, ax)
    mocp.set_derivative(vy, ay)
    mocp.set_derivative(vz, az)

    # Heat flow
    heat_flow = 1e-4 * (air_density**0.5) * (airspeed**3.15)
    mocp.add_path_output('heat_flow', heat_flow)
    if has_heating:
        mocp.set_derivative(heat, heat_flow)

    ## Constraints

    # Duration is positive and bounded
    mocp.add_constraint(duration > 0.006)
    mocp.add_constraint(duration < 10.0)

    # Mass is positive
    mocp.add_path_constraint(mass > 1e-6)

    # maxQ soft constraint
    weight_dynamic_pressure_penalty = mocp.get_parameter(
        'weight_dynamic_pressure_penalty')
    slack_maxQ = mocp.add_trajectory(phase_name, 'slack_maxQ', init=10.0)
    mocp.add_path_constraint(slack_maxQ > 0.0)
    mocp.add_mean_objective(weight_dynamic_pressure_penalty * slack_maxQ)
    mocp.add_path_constraint(dynamic_pressure / maxQ < 1.0 + slack_maxQ)

    if is_powered:
        # u is a unit vector
        mocp.add_path_constraint(ux**2 + uy**2 + uz**2 == 1.0)

        # Do not point down
        mocp.add_path_constraint((r_vec / r).T @ u_vec > -0.2)

        # Engine flow limits
        mocp.add_path_constraint(mdot_min < mdot)
        mocp.add_path_constraint(mdot < mdot_max)

        # Throttle rate reduction
        mocp.add_mean_objective(1e-6 * mdot_rate**2)

        # Lateral dynamic pressure penalty
        weight_lateral_dynamic_pressure_penalty = mocp.get_parameter(
            'weight_lateral_dynamic_pressure_penalty')
        mocp.add_mean_objective(
            weight_lateral_dynamic_pressure_penalty *
            (lateral_dynamic_pressure / p.maxQ_sin_alpha)**8)

    variables = locals().copy()
    RocketStagePhase = collections.namedtuple('RocketStagePhase',
                                              sorted(list(variables.keys())))
    return RocketStagePhase(**variables)