Beispiel #1
0
    def f_vel(self, x, u, f_d):
        """
        Time-derivative of the velocity vector
        :param x: 4-length array of input state with components: 3D pos, quaternion angle, 3D vel, 3D rate
        :param u: control input vector (4-dimensional): [trust_motor_1, ..., thrust_motor_4]
        :param f_d: disturbance force vector (3-dimensional)
        :return: 3D velocity differential increment (vector): d[vel_x; vel_y; vel_z]/dt
        """

        a_thrust = np.array([[0], [0], [np.sum(u)]]) / self.mass

        if self.drag:
            # Transform velocity to body frame
            v_b = v_dot_q(x[2], quaternion_inverse(x[1]))[:, np.newaxis]
            # Compute aerodynamic drag acceleration in world frame
            a_drag = -self.aero_drag * v_b**2 * np.sign(v_b) / self.mass
            # Add rotor drag
            a_drag -= self.rotor_drag * v_b / self.mass
            # Transform drag acceleration to world frame
            a_drag = v_dot_q(a_drag, x[1])
        else:
            a_drag = np.zeros((3, 1))

        angle_quaternion = x[1]

        a_payload = -self.payload_mass * self.g / self.mass

        return np.squeeze(-self.g + a_payload + a_drag +
                          v_dot_q(a_thrust +
                                  f_d / self.mass, angle_quaternion))
    def set_reference_state(self, x_target=None, u_target=None):
        """
        Sets the target state and pre-computes the integration dynamics with cost equations
        :param x_target: 13-dimensional target state (p_xyz, a_wxyz, v_xyz, r_xyz)
        :param u_target: 4-dimensional target control input vector (u_1, u_2, u_3, u_4)
        """

        if x_target is None:
            x_target = [[0, 0, 0], [1, 0, 0, 0], [0, 0, 0], [0, 0, 0]]
        if u_target is None:
            u_target = [0, 0, 0, 0]

        # Set new target state
        self.target = copy(x_target)

        ref = np.concatenate([x_target[i] for i in range(4)])
        #  Transform velocity to body frame
        v_b = v_dot_q(ref[7:10], quaternion_inverse(ref[3:7]))
        ref = np.concatenate((ref[:7], v_b, ref[10:]))

        # Determine which dynamics model to use based on the GP optimal input feature region. Should get one for each
        # output dimension of the GP
        if self.gp_reg_ensemble is not None:
            gp_ind = self.gp_reg_ensemble.select_gp(dim=None, x=ref, u=u_target)
        else:
            gp_ind = 0

        ref = np.concatenate((ref, u_target))

        for j in range(self.N):
            self.acados_ocp_solver[gp_ind].set(j, "yref", ref)
        self.acados_ocp_solver[gp_ind].set(self.N, "yref", ref[:-4])

        return gp_ind
def draw_drone(pos, q_rot, x_f, y_f):

    # Define quadrotor extremities in body reference frame
    x1 = np.array([x_f[0], y_f[0], 0])
    x2 = np.array([x_f[1], y_f[1], 0])
    x3 = np.array([x_f[2], y_f[2], 0])
    x4 = np.array([x_f[3], y_f[3], 0])

    # Convert to world reference frame and add quadrotor center point:
    x1 = v_dot_q(x1, q_rot) + pos
    x2 = v_dot_q(x2, q_rot) + pos
    x3 = v_dot_q(x3, q_rot) + pos
    x4 = v_dot_q(x4, q_rot) + pos

    # Build set of coordinates for plotting
    return ([x1[0], x3[0], pos[0], x2[0],
             x4[0]], [x1[1], x3[1], pos[1], x2[1],
                      x4[1]], [x1[2], x3[2], pos[2], x2[2], x4[2]])
    def v_dynamics(self, rdrv_d):
        """
        :param rdrv_d: a 3x3 diagonal matrix containing the D matrix coefficients for a linear drag model as proposed
        by Faessler et al. None, if no linear compensation is to be used.
        """

        f_thrust = self.u * self.quad.max_thrust
        g = cs.vertcat(0.0, 0.0, 9.81)
        a_thrust = cs.vertcat(0.0, 0.0, f_thrust[0] + f_thrust[1] + f_thrust[2] + f_thrust[3]) / self.quad.mass

        v_dynamics = v_dot_q(a_thrust, self.q) - g

        if rdrv_d is not None:
            # Velocity in body frame:
            v_b = v_dot_q(self.v, quaternion_inverse(self.q))
            rdrv_drag = v_dot_q(cs.mtimes(rdrv_d, v_b), self.q)
            v_dynamics += rdrv_drag

        return v_dynamics
Beispiel #5
0
def world_to_body_velocity_mapping(state_sequence):
    """

    :param state_sequence: N x 13 state array, where N is the number of states in the sequence.
    :return: An N x 13 sequence of states, but where the velocities (assumed to be in positions 7, 8, 9) have been
    rotated from world to body frame. The rotation is made using the quaternion in positions 3, 4, 5, 6.
    """

    p, q, v_w, w = separate_variables(state_sequence)
    v_b = []
    for i in range(len(q)):
        v_b.append(v_dot_q(v_w[i], quaternion_inverse(q[i])))
    v_b = np.stack(v_b)
    return np.concatenate((p, q, v_b, w), 1)
    def set_reference_trajectory(self, x_target, u_target):
        """
        Sets the reference trajectory and pre-computes the cost equations for each point in the reference sequence.
        :param x_target: Nx13-dimensional reference trajectory (p_xyz, angle_wxyz, v_xyz, rate_xyz). It is passed in the
        form of a 4-length list, where the first element is a Nx3 numpy array referring to the position targets, the
        second is a Nx4 array referring to the quaternion, two more Nx3 arrays for the velocity and body rate targets.
        :param u_target: Nx4-dimensional target control input vector (u1, u2, u3, u4)
        """

        if u_target is not None:
            assert x_target[0].shape[0] == (u_target.shape[0] + 1) or x_target[0].shape[0] == u_target.shape[0]

        # If not enough states in target sequence, append last state until required length is met
        while x_target[0].shape[0] < self.N + 1:
            x_target = [np.concatenate((x, np.expand_dims(x[-1, :], 0)), 0) for x in x_target]
            if u_target is not None:
                u_target = np.concatenate((u_target, np.expand_dims(u_target[-1, :], 0)), 0)

        stacked_x_target = np.concatenate([x for x in x_target], 1)

        #  Transform velocity to body frame
        x_mean = stacked_x_target[int(self.N / 2)]
        v_b = v_dot_q(x_mean[7:10], quaternion_inverse(x_mean[3:7]))
        x_target_mean = np.concatenate((x_mean[:7], v_b, x_mean[10:]))

        # Determine which dynamics model to use based on the GP optimal input feature region
        if self.gp_reg_ensemble is not None:
            gp_ind = self.gp_reg_ensemble.select_gp(dim=None, x=x_target_mean, u=u_target[int(self.N / 2)])
        else:
            gp_ind = 0

        self.target = copy(x_target)

        for j in range(self.N):
            ref = stacked_x_target[j, :]
            ref = np.concatenate((ref, u_target[j, :]))
            self.acados_ocp_solver[gp_ind].set(j, "yref", ref)
        # the last MPC node has only a state reference but no input reference
        self.acados_ocp_solver[gp_ind].set(self.N, "yref", stacked_x_target[self.N, :])
        return gp_ind
Beispiel #7
0
    def odometry_callback(self, msg):
        """
        Callback function for Odometry subscriber
        :param msg: message from subscriber.
        :type msg: Odometry
        """

        if self.controller_off:
            return

        p, q, v, w = odometry_parse(msg)

        # Change velocity to world frame if in gazebo environment
        if self.environment == "gazebo":
            v_w = v_dot_q(np.array(v), np.array(q)).tolist()
        else:
            v_w = v

        self.x = p + q + v_w + w

        try:
            # Update the state estimate of the quad
            self.gp_mpc.set_state(self.x)

            # If an estimate specifically for the GP's is available, also update it
            if self.gp_odom is not None:
                self.gp_mpc.set_gp_state(self.gp_odom)

        except AttributeError:
            # The GP MPC object instantiation is still not finished
            return

        if self.override_land:
            return

        # If the above try passed then MPC is ready
        self.odom_available = True

        # We only optimize once every two odometry messages
        if not self.optimize_next:
            self.mpc_thread.join()

            # If currently on trajectory tracking, pay close attention to any skipped messages.
            if self.x_initial_reached:

                # Count how many messages were skipped (ideally 0)
                skipped_messages = int(msg.header.seq - self.last_odom_seq_number - 1)
                if skipped_messages > 0:
                    warn_msg = "Recording time skipped messages: %d" % skipped_messages
                    rospy.logwarn(warn_msg)

                # Adjust current index in trajectory
                self.current_idx += divmod(skipped_messages, 2)[0]
                # If odd number of skipped messages, do optimization
                if skipped_messages > 0 and skipped_messages % 2 == 1:

                    if self.recording_options["recording"]:
                        self.check_out_initial_state(msg)

                    # Run MPC now
                    self.run_mpc(msg)
                    self.last_odom_seq_number = msg.header.seq
                    self.optimize_next = False
                    return

            self.optimize_next = True
            if self.recording_options["recording"] and self.x_initial_reached:
                self.check_out_initial_state(msg)
            return

        # Run MPC
        if msg.header.seq > self.last_odom_seq_number + 2 and self.last_odom_seq_number > 0 and self.x_initial_reached:
            # If one message was skipped at this point, then the reference is already late. Compensate by
            # optimizing twice in a row and hope to do it fast...
            if self.recording_options["recording"] and self.x_initial_reached:
                self.check_out_initial_state(msg)
            self.run_mpc(msg)
            self.optimize_next = True
            self.last_odom_seq_number = msg.header.seq
            odometry_skipped_warning(self.last_odom_seq_number, msg.header.seq, "optimization")
            return

        def _thread_func():
            self.run_mpc(msg)
        self.mpc_thread = threading.Thread(target=_thread_func(), args=(), daemon=True)
        self.mpc_thread.start()

        self.last_odom_seq_number = msg.header.seq
        self.optimize_next = False
Beispiel #8
0
def main(model_name,
         features,
         quad_sim_options,
         dataset_name,
         x_cap,
         hist_bins,
         hist_thresh,
         plot=False):

    df_train = read_dataset(dataset_name, True, quad_sim_options)
    gp_dataset = GPDataset(df_train,
                           features, [],
                           features,
                           cap=x_cap,
                           n_bins=hist_bins,
                           thresh=hist_thresh,
                           visualize_data=False)

    # Get X,Y datasets for the specified regression dimensions (subset of [7, 8, 9])
    a_err_b = gp_dataset.y
    v_b = gp_dataset.x

    drag_coeffs = linear_rdrv_fitting(v_b, a_err_b, np.array(features) - 7)

    # Get git commit hash for saving the model
    git_version = ''
    try:
        git_version = subprocess.check_output(['git', 'describe', '--always'
                                               ]).strip().decode("utf-8")
    except subprocess.CalledProcessError as e:
        print(e.returncode, e.output)
    gp_name_dict = {
        "git": git_version,
        "model_name": model_name,
        "params": quad_sim_options
    }
    save_file_path, save_file_name = get_model_dir_and_file(gp_name_dict)
    safe_mknode_recursive(save_file_path,
                          save_file_name + '.pkl',
                          overwrite=True)
    file_name = os.path.join(save_file_path, save_file_name + '.pkl')
    joblib.dump(drag_coeffs, file_name)

    if not plot:
        return drag_coeffs

    # Get X,Y datasets for velocity dimensions [7, 8, 9]
    a_err_b = gp_dataset.get_y(pruned=True, raw=True)[:, 7:10]
    v_b = gp_dataset.get_x(pruned=True, raw=True)[:, 7:10]

    # Compute predictions with RDRv model
    preds = []
    for i in range(len(a_err_b)):
        preds.append(np.matmul(drag_coeffs, v_b[i]))
    preds = np.stack(preds)

    ax_names = [r'$v_B^x$', r'$v_B^y$', r'$v_B^z$']
    y_dim_name = [r'drag $a_B^x$', 'drag $a_B^y$', 'drag $a_B^z$']
    fig, ax = plt.subplots(len(features), 1, sharex='all')
    for i in range(len(features)):
        ax[i].scatter(v_b[:, i], a_err_b[:, i], label='data')
        ax[i].scatter(v_b[:, i], preds[:, i], label='RDRv')
        ax[i].legend()
        ax[i].set_ylabel(y_dim_name[features[i] - 7])
        ax[i].set_xlabel(ax_names[features[i] - 7])
    ax[0].set_title('Body reference frame')

    # Remap error to world coordinates and predictions too
    q = gp_dataset.get_x(pruned=True, raw=True)[:, 3:7]
    for i in range(len(a_err_b)):
        a_err_b[i] = v_dot_q(a_err_b[i], q[i])
        preds[i] = v_dot_q(preds[i], q[i])

    ax_names = [r'$v_W^x$', r'$v_W^y$', r'$v_W^z$']
    y_dim_name = [r'drag $a_W^x$', 'drag $a_W^y$', 'drag $a_W^z$']
    fig, ax = plt.subplots(len(features), 1, sharex='all')
    for i in range(len(features)):
        ax[i].scatter(v_b[:, i], a_err_b[:, i], label='data')
        ax[i].scatter(v_b[:, i], preds[:, i], label='RDRv')
        ax[i].legend()
        ax[i].set_ylabel(y_dim_name[features[i] - 7])
        ax[i].set_xlabel(ax_names[features[i] - 7])
    ax[0].set_title('World reference frame')
    plt.show()

    return drag_coeffs
    def acados_setup_model(self, nominal, model_name):
        """
        Builds an Acados symbolic models using CasADi expressions.
        :param model_name: name for the acados model. Must be different from previously used names or there may be
        problems loading the right model.
        :param nominal: CasADi symbolic nominal model of the quadrotor: f(self.x, self.u) = x_dot, dimensions 13x1.
        :return: Returns a total of three outputs, where m is the number of GP's in the GP ensemble, or 1 if no GP:
            - A dictionary of m AcadosModel of the GP-augmented quadrotor
            - A dictionary of m CasADi symbolic nominal dynamics equations with GP mean value augmentations (if with GP)
        :rtype: dict, dict, cs.MX
        """

        def fill_in_acados_model(x, u, p, dynamics, name):

            x_dot = cs.MX.sym('x_dot', dynamics.shape)
            f_impl = x_dot - dynamics

            # Dynamics model
            model = AcadosModel()
            model.f_expl_expr = dynamics
            model.f_impl_expr = f_impl
            model.x = x
            model.xdot = x_dot
            model.u = u
            model.p = p
            model.name = name

            return model

        acados_models = {}
        dynamics_equations = {}

        # Run GP inference if GP's available
        if self.gp_reg_ensemble is not None:

            # Feature vector are the elements of x and u determined by the selection matrix B_z. The trigger var is used
            # to select the gp-specific state estimate in the first optimization node, and the regular integrated state
            # in the rest. The computing of the features z is done within the GPEnsemble.
            gp_x = self.gp_x * self.trigger_var + self.x * (1 - self.trigger_var)
            #  Transform velocity to body frame
            v_b = v_dot_q(gp_x[7:10], quaternion_inverse(gp_x[3:7]))
            gp_x = cs.vertcat(gp_x[:7], v_b, gp_x[10:])
            gp_u = self.u

            gp_dims = self.gp_reg_ensemble.dim_idx

            # Get number of models in GP
            for i in range(self.gp_reg_ensemble.n_models):
                # Evaluate cluster of the GP ensemble
                cluster_id = {k: [v] for k, v in zip(gp_dims, i * np.ones_like(gp_dims, dtype=int))}
                outs = self.gp_reg_ensemble.predict(gp_x, gp_u, return_cov=False, gp_idx=cluster_id, return_z=False)

                # Unpack prediction outputs. Transform back to world reference frame
                outs = self.add_missing_states(outs)
                gp_means = v_dot_q(outs["pred"], gp_x[3:7])
                gp_means = self.remove_extra_states(gp_means)

                # Add GP mean prediction
                dynamics_equations[i] = nominal + cs.mtimes(self.B_x, gp_means)

                x_ = self.x
                dynamics_ = dynamics_equations[i]

                # Add again the gp augmented dynamics for the GP state
                dynamics_ = cs.vertcat(dynamics_)
                dynamics_equations[i] = cs.vertcat(dynamics_equations[i])

                i_name = model_name + "_domain_" + str(i)

                params = cs.vertcat(self.gp_x, self.trigger_var)
                acados_models[i] = fill_in_acados_model(x=x_, u=self.u, p=params, dynamics=dynamics_, name=i_name)

        else:

            # No available GP so return nominal dynamics
            dynamics_equations[0] = nominal

            x_ = self.x
            dynamics_ = nominal

            acados_models[0] = fill_in_acados_model(x=x_, u=self.u, p=[], dynamics=dynamics_, name=model_name)

        return acados_models, dynamics_equations