Пример #1
0
    def start(self):
        quad = self.model

        # vehicle dimensons
        d = quad['d']  # Hub displacement from COG
        r = quad['r']  # Rotor radius

        # C = np.zeros((3, self.nrotors))   ## WHERE USED?
        self.D = np.zeros((3, self.nrotors))

        for i in range(0, self.nrotors):
            theta = i / self.nrotors * 2 * pi
            #  Di      Rotor hub displacements (1x3)
            # first rotor is on the x-axis, clockwise order looking down from above
            self.D[:, i] = np.r_[quad['d'] * cos(theta),
                                 quad['d'] * sin(theta), quad['h']]

        # draw ground
        self.fig = plt.figure()
        # no axes in the figure, create a 3D axes
        self.ax = self.fig.add_subplot(111,
                                       projection='3d',
                                       proj_type=self.projection)

        # ax.set_aspect('equal')
        self.ax.set_xlabel('X')
        self.ax.set_ylabel('Y')
        self.ax.set_zlabel('-Z (height above ground)')

        # TODO allow user to set maximum height of plot volume
        self.ax.set_xlim(self.scale[0], self.scale[1])
        self.ax.set_ylim(self.scale[2], self.scale[3])
        self.ax.set_zlim(0, self.scale[4])

        # plot the ground boundaries and the big cross
        self.ax.plot([self.scale[0], self.scale[2]],
                     [self.scale[1], self.scale[3]], [0, 0], 'b-')
        self.ax.plot([self.scale[0], self.scale[3]],
                     [self.scale[1], self.scale[2]], [0, 0], 'b-')
        self.ax.grid(True)

        self.shadow, = self.ax.plot([0, 0], [0, 0], 'k--')
        self.groundmark, = self.ax.plot([0], [0], [0], 'kx')

        self.arm = []
        self.disk = []
        for i in range(0, self.nrotors):
            h, = self.ax.plot([0], [0], [0])
            self.arm.append(h)
            if i == 0:
                color = 'b-'
            else:
                color = 'g-'
            h, = self.ax.plot([0], [0], [0], color)
            self.disk.append(h)

        self.a1s = np.zeros((self.nrotors, ))
        self.b1s = np.zeros((self.nrotors, ))
Пример #2
0
    def __init__(self, *inputs, x0=None, L=1, vlim=1, slim=1, **kwargs):
        """
        :param ``*inputs``: Optional incoming connections
        :type ``*inputs``: Block or Plug
        :param x0: Inital state, defaults to 0
        :type x0: array_like, optional
        :param L: Wheelbase, defaults to 1
        :type L: float, optional
        :param vlim: Velocity limit, defaults to 1
        :type vlim: float, optional
        :param slim: Steering limit, defaults to 1
        :type slim: float, optional
        :param ``**kwargs``: common Block options
        :return: a BICYCLE block
        :rtype: Bicycle instance

        Create a vehicle model with Bicycle kinematics.

        Bicycle kinematic model with state :math:`[x, y, \theta]`.  

        The block has two input ports:

            1. Vehicle speed (metres/sec).  The velocity limit ``vlim`` is
               applied to the magnitude of this input.
            2. Steering wheel angle (radians).  The steering limit ``slim``
               is applied to the magnitude of this input.

        and three output ports:

            1. x position in the world frame (metres)
            2. y positon in the world frame (metres)
            3. heading angle with respect to the world frame (radians)

        """
        super().__init__(nin=2, nout=3, inputs=inputs, **kwargs)

        self.nstates = 3
        self.vlim = vlim
        self.slim = slim
        self.type = 'bicycle'

        self.L = L
        if x0 is None:
            self._x0 = np.zeros((self.nstates, ))
        else:
            assert len(
                x0) == self.nstates, "x0 is {:d} long, should be {:d}".format(
                    len(x0), self.nstates)
            self._x0 = x0

        self.inport_names(('v', '$\gamma$'))
        self.outport_names(('x', 'y', r'$\theta$'))
        self.state_names(('x', 'y', r'$\theta$'))
Пример #3
0
    def __init__(self, *inputs, R=1, W=1, x0=None, **kwargs):
        r"""
        :param ``*inputs``: Optional incoming connections
        :type ``*inputs``: Block or Plug
        :param x0: Inital state, defaults to 0
        :type x0: array_like, optional
        :param R: Wheel radius, defaults to 1
        :type R: float, optional
        :param W: Wheel separation in lateral direction, defaults to 1
        :type R: float, optional
        :param ``**kwargs``: common Block options
        :return: a DIFFSTEER block
        :rtype: DifSteer instance

        Create a differential steer vehicle model with Unicycle kinematics, with inputs
        given as wheel angular velocity.

        Unicycle kinematic model with state :math:`[x, y, \theta]`.

        The block has two input ports:

            1. Left-wheel angular velocity (radians/sec).
            2. Right-wheel angular velocity (radians/sec).

        and three output ports:

            1. x position in the world frame (metres)
            2. y positon in the world frame (metres)
            3. heading angle with respect to the world frame (radians)

        Note:

            - wheel velocity is defined such that if both are positive the vehicle
              moves forward.
        """
        super().__init__(nin=2, nout=3, inputs=inputs, **kwargs)
        self.nstates = 3
        self.type = 'diffsteer'
        self.R = R
        self.W = W

        if x0 is None:
            self._x0 = np.zeros((self.nstates, ))
        else:
            assert len(
                x0) == self.nstates, "x0 is {:d} long, should be {:d}".format(
                    len(x0), self.nstates)
            self._x0 = x0
Пример #4
0
    def __init__(self, *inputs, x0=None, **kwargs):
        r"""
        :param ``*inputs``: Optional incoming connections
        :type ``*inputs``: Block or Plug
        :param x0: Inital state, defaults to 0
        :type x0: array_like, optional
        :param ``*inputs``: Optional incoming connections
        :type ``*inputs``: Block or Plug
        :param ``**kwargs``: common Block options
        :return: a UNICYCLE block
        :rtype: Unicycle instance

        Create a vehicle model with Unicycle kinematics.

        Unicycle kinematic model with state :math:`[x, y, \theta]`.

        The block has two input ports:

            1. Vehicle speed (metres/sec).
            2. Angular velocity (radians/sec).

        and three output ports:

            1. x position in the world frame (metres)
            2. y positon in the world frame (metres)
            3. heading angle with respect to the world frame (radians)

        """
        super().__init__(nin=2, nout=3, inputs=inputs, **kwargs)
        self.nstates = 3
        self.type = 'unicycle'

        if x0 is None:
            self._x0 = np.zeros((self.nstates, ))
        else:
            assert len(
                x0) == self.nstates, "x0 is {:d} long, should be {:d}".format(
                    len(x0), self.nstates)
            self._x0 = x0
Пример #5
0
    def __init__(self,
                 N=1,
                 D=[1, 1],
                 *inputs,
                 x0=None,
                 verbose=False,
                 **kwargs):
        r"""
        :param N: numerator coefficients, defaults to 1
        :type N: array_like, optional
        :param D: denominator coefficients, defaults to [1, 1]
        :type D: array_like, optional
        :param ``*inputs``: Optional incoming connections
        :type ``*inputs``: Block or Plug
        :param x0: initial states, defaults to zero
        :type x0: array_like, optional
        :param ``**kwargs``: common Block options
        :return: A SCOPE block
        :rtype: LTI_SISO instance

        Create a SISO LTI block.

        Describes the dynamics of a single-input single-output (SISO) linear
        time invariant (LTI) system described by numerator and denominator
        polynomial coefficients.

        Coefficients are given in the order from highest order to zeroth 
        order, ie. :math:`2s^2 - 4s +3` is ``[2, -4, 3]``.

        Only proper transfer functions, where order of numerator is less
        than denominator are allowed.

        The order of the states in ``x0`` is consistent with controller canonical
        form.

        Examples::

            LTI_SISO(N=[1, 2], D=[2, 3, -4])

        is the transfer function :math:`\frac{s+2}{2s^2+3s-4}`.
        """
        #print('in SISO constscutor')

        if not isinstance(N, list):
            N = [N]
        if not isinstance(D, list):
            D = [D]
        self.N = N
        self.D = N
        n = len(D) - 1
        nn = len(N)
        if x0 is None:
            x0 = np.zeros((n, ))
        assert nn <= n, 'direct pass through is not supported'

        # convert to numpy arrays
        N = np.r_[np.zeros((len(D) - len(N), )), np.array(N)]
        D = np.array(D)

        # normalize the coefficients to obtain
        #
        #   b_0 s^n + b_1 s^(n-1) + ... + b_n
        #   ---------------------------------
        #   a_0 s^n + a_1 s^(n-1) + ....+ a_n

        # normalize so leading coefficient of denominator is one
        D0 = D[0]
        D = D / D0
        N = N / D0

        A = np.eye(len(D) - 1, k=1)  # control canonic (companion matrix) form
        A[-1, :] = -D[1:]

        B = np.zeros((n, 1))
        B[-1] = 1

        C = (N[1:] - N[0] * D[1:]).reshape((1, n))

        if verbose:
            print('A=', A)
            print('B=', B)
            print('C=', C)

        super().__init__(A=A, B=B, C=C, x0=x0, **kwargs)
        self.type = 'LTI'
Пример #6
0
    def __init__(self,
                 *inputs,
                 A=None,
                 B=None,
                 C=None,
                 x0=None,
                 verbose=False,
                 **kwargs):
        r"""
        :param ``*inputs``: Optional incoming connections
        :type ``*inputs``: Block or Plug
        :param N: numerator coefficients, defaults to 1
        :type N: array_like, optional
        :param D: denominator coefficients, defaults to [1, 1]
        :type D: array_like, optional
        :param x0: initial states, defaults to zero
        :type x0: array_like, optional
        :param ``**kwargs``: common Block options
        :return: A SCOPE block
        :rtype: LTI_SISO instance

        Create a state-space LTI block.

        Describes the dynamics of a single-input single-output (SISO) linear
        time invariant (LTI) system described by numerator and denominator
        polynomial coefficients.

        Coefficients are given in the order from highest order to zeroth 
        order, ie. :math:`2s^2 - 4s +3` is ``[2, -4, 3]``.

        Only proper transfer functions, where order of numerator is less
        than denominator are allowed.

        The order of the states in ``x0`` is consistent with controller canonical
        form.

        Examples::

            LTI_SISO(N=[1,2], D=[2, 3, -4])

        is the transfer function :math:`\frac{s+2}{2s^2+3s-4}`.
        """
        #print('in SS constructor')
        self.type = 'LTI SS'

        assert A.shape[0] == A.shape[1], 'A must be square'
        n = A.shape[0]
        if len(B.shape) == 1:
            nin = 1
            B = B.reshape((n, 1))
        else:
            nin = B.shape[1]
        assert B.shape[0] == n, 'B must have same number of rows as A'

        if len(C.shape) == 1:
            nout = 1
            assert C.shape[0] == n, 'C must have same number of columns as A'
            C = C.reshape((1, n))
        else:
            nout = C.shape[0]
            assert C.shape[1] == n, 'C must have same number of columns as A'

        super().__init__(nin=nin, nout=nout, inputs=inputs, **kwargs)

        self.A = A
        self.B = B
        self.C = C

        self.nstates = A.shape[0]

        if x0 is None:
            self._x0 = np.zeros((self.nstates, ))
        else:
            self._x0 = x0
Пример #7
0
    def deriv(self):

        model = self.model

        # Body-fixed frame references
        #   ei      Body fixed frame references 3x1
        e3 = np.r_[0, 0, 1]

        # process inputs
        w = self.inputs[0]
        if len(w) != self.nrotors:
            raise RuntimeError('input vector wrong size')

        if self.speedcheck and np.any(w == 0):
            # might need to fix this, preculudes aerobatics :(
            # mu becomes NaN due to 0/0
            raise RuntimeError(
                'quadrotor_dynamics: not defined for zero rotor speed')

        # EXTRACT STATES FROM X
        z = self._x[0:3]  # position in {W}
        n = self._x[3:6]  # RPY angles {W}
        v = self._x[6:9]  # velocity in {W}
        o = self._x[9:12]  # angular velocity in {W}

        # PREPROCESS ROTATION AND WRONSKIAN MATRICIES
        phi = n[0]  # yaw
        the = n[1]  # pitch
        psi = n[2]  # roll

        # rotz(phi)*roty(the)*rotx(psi)
        # BBF > Inertial rotation matrix
        R = np.array([[
            cos(the) * cos(phi),
            sin(psi) * sin(the) * cos(phi) - cos(psi) * sin(phi),
            cos(psi) * sin(the) * cos(phi) + sin(psi) * sin(phi)
        ],
                      [
                          cos(the) * sin(phi),
                          sin(psi) * sin(the) * sin(phi) + cos(psi) * cos(phi),
                          cos(psi) * sin(the) * sin(phi) - sin(psi) * cos(phi)
                      ], [-sin(the),
                          sin(psi) * cos(the),
                          cos(psi) * cos(the)]])

        # Manual Construction
        #     Q3 = [cos(phi) -sin(phi) 0;sin(phi) cos(phi) 0;0 0 1];   % RZ %Rotation mappings
        #     Q2 = [cos(the) 0 sin(the);0 1 0;-sin(the) 0 cos(the)];   % RY
        #     Q1 = [1 0 0;0 cos(psi) -sin(psi);0 sin(psi) cos(psi)];   % RX
        #     R = Q3*Q2*Q1    %Rotation matrix
        #
        #    RZ * RY * RX
        # inverted Wronskian
        iW = np.array([[0, sin(psi), cos(psi)],
                       [0, cos(psi) * cos(the), -sin(psi) * cos(the)],
                       [cos(the),
                        sin(psi) * sin(the),
                        cos(psi) * sin(the)]]) / cos(the)

        # ROTOR MODEL
        T = np.zeros((3, 4))
        Q = np.zeros((3, 4))
        tau = np.zeros((3, 4))

        a1s = self.a1s
        b1s = self.b1s

        for i in range(0, self.nrotors):  # for each rotor

            # Relative motion
            Vr = np.cross(o, self.D[:, i]) + v
            # Magnitude of mu, planar components
            mu = sqrt(np.sum(Vr[0:2]**2)) / (abs(w[i]) * model['r'])
            # Non-dimensionalised normal inflow
            lc = Vr[2] / (abs(w[i]) * model['r'])
            # Non-dimensionalised induced velocity approximation
            li = mu
            alphas = atan2(lc, mu)
            # Sideslip azimuth relative to e1 (zero over nose)
            j = atan2(Vr[1], Vr[0])
            J = np.array([[cos(j), -sin(j)],
                          [sin(j),
                           cos(j)]])  # BBF > mu sideslip rotation matrix

            # Flapping
            beta = np.array([
                [((8 / 3 * model['theta0'] + 2 * model['theta1']) * mu -
                  2 * lc * mu) / (1 - mu**2 / 2)],  # Longitudinal flapping
                # Lattitudinal flapping (note sign)
                [0]
            ])

            # sign(w) * (4/3)*((Ct/sigma)*(2*mu*gamma/3/a)/(1+3*e/2/r) + li)/(1+mu^2/2)];

            # Rotate the beta flapping angles to longitudinal and lateral coordinates.
            beta = J.T @ beta
            a1s[i] = beta[0] - 16 / model['gamma'] / abs(w[i]) * o[1]
            b1s[i] = beta[1] - 16 / model['gamma'] / abs(w[i]) * o[0]

            # Forces and torques

            # Rotor thrust, linearised angle approximations

            T[:, i] = model['Ct'] * model['rho'] * model['A'] * model['r']**2 * w[i]**2 * \
                np.r_[-cos(b1s[i]) * sin(a1s[i]),
                      sin(b1s[i]), -cos(a1s[i])*cos(b1s[i])]

            # Rotor drag torque - note that this preserves w[i] direction sign

            Q[:, i] = -model['Cq'] * model['rho'] * \
                model['A'] * model['r']**3 * w[i] * abs(w[i]) * e3

            # Torque due to rotor thrust
            tau[:, i] = np.cross(T[:, i], self.D[:, i])

        # RIGID BODY DYNAMIC MODEL
        dz = v
        dn = iW @ o

        dv = model['g'] * e3 + R @ np.sum(T, axis=1) / model['M']

        # vehicle can't fall below ground, remember z is down
        if self.groundcheck and z[2] > 0:
            z[0] = 0
            dz[0] = 0

        # row sum of torques
        do = np.linalg.inv(model['J']) @ (np.cross(
            -o, model['J'] @ o) + np.sum(tau, axis=1) + np.sum(Q, axis=1))

        # stash the flapping information for plotting
        self.a1s = a1s
        self.b1s = b1s

        return np.r_[dz, dn, dv, do]  # This is the state derivative vector
Пример #8
0
    def __init__(self,
                 model,
                 *inputs,
                 groundcheck=True,
                 speedcheck=True,
                 x0=None,
                 **kwargs):
        r"""
        :param model: Vehicle geometric and inertial parameters
        :type model: dict
        :param ``*inputs``: Optional incoming connections
        :type ``*inputs``: Block or Plug
        :param groundcheck: Prevent vehicle moving below ground , defaults to True
        :type groundcheck: bool
        :param speedcheck: Check for zero rotor speed, defaults to True
        :type speedcheck: bool
        :param x0: Initial state, defaults to all zeros
        :type x0: TYPE, optional
        :param ``**kwargs``: common Block options
        :return: a MULTIROTOR block
        :rtype: MultiRotor instance

        Create a a multi-rotor dynamic model block.

        The block has one input port which is a vector of input rotor speeds
        in (radians/sec).  These are, looking down, clockwise from the front rotor
        which lies on the x-axis.

        The block has one output port which is a dictionary signal with the
        following items:

            - ``x`` pose in the world frame as :math:`[x, y, z, \theta_Y, \theta_P, \theta_R]`
            - ``vb`` translational velocity in the world frame (metres/sec)
            - ``w`` angular rates in the world frame as yaw-pitch-roll rates (radians/second)
            - ``a1s`` longitudinal flapping angles (radians)
            - ``b1s`` lateral flapping angles (radians)

        Based on MATLAB code developed by Pauline Pounds 2004.
        """
        super().__init__(nin=1, nout=1, inputs=inputs, **kwargs)
        self.type = 'quadrotor'

        try:
            nrotors = model['nrotors']
        except KeyError:
            raise RuntimeError('vehicle model does not contain nrotors')
        assert nrotors % 2 == 0, 'Must have an even number of rotors'

        self.nstates = 12
        if x0 is not None:
            assert len(x0) == self.nstates, "x0 is the wrong length"
        else:
            x0 = np.zeros((self.nstates, ))
        self._x0 = x0

        self.nrotors = nrotors
        self.model = model

        self.groundcheck = groundcheck
        self.speedcheck = speedcheck

        self.D = np.zeros((3, self.nrotors))
        for i in range(0, self.nrotors):
            theta = i / self.nrotors * 2 * pi
            #  Di      Rotor hub displacements (1x3)
            # first rotor is on the x-axis, clockwise order looking down from above
            self.D[:, i] = np.r_[model['d'] * cos(theta),
                                 model['d'] * sin(theta), model['h']]

        self.a1s = np.zeros((self.nrotors, ))
        self.b1s = np.zeros((self.nrotors, ))
Пример #9
0
    def step(self):
        def plot3(h, x, y, z):
            h.set_data(x, y)
            h.set_3d_properties(z)

        # READ STATE
        z = self.inputs[0][0:3]
        n = self.inputs[0][3:6]

        # TODO, check input dimensions, 12 or 12+2N, deal with flapping

        a1s = self.a1s
        b1s = self.b1s

        quad = self.model

        # vehicle dimensons
        d = quad['d']  # Hub displacement from COG
        r = quad['r']  # Rotor radius

        # PREPROCESS ROTATION MATRIX
        phi = n[0]  # Euler angles
        the = n[1]
        psi = n[2]

        # BBF > Inertial rotation matrix
        R = np.array([[
            cos(the) * cos(phi),
            sin(psi) * sin(the) * cos(phi) - cos(psi) * sin(phi),
            cos(psi) * sin(the) * cos(phi) + sin(psi) * sin(phi)
        ],
                      [
                          cos(the) * sin(phi),
                          sin(psi) * sin(the) * sin(phi) + cos(psi) * cos(phi),
                          cos(psi) * sin(the) * sin(phi) - sin(psi) * cos(phi)
                      ], [-sin(the),
                          sin(psi) * cos(the),
                          cos(psi) * cos(the)]])

        # Manual Construction
        # Q3 = [cos(psi) -sin(psi) 0;sin(psi) cos(psi) 0;0 0 1];   %Rotation mappings
        # Q2 = [cos(the) 0 sin(the);0 1 0;-sin(the) 0 cos(the)];
        # Q1 = [1 0 0;0 cos(phi) -sin(phi);0 sin(phi) cos(phi)];
        # R = Q3*Q2*Q1;    %Rotation matrix

        # CALCULATE FLYER TIP POSITONS USING COORDINATE FRAME ROTATION
        F = np.array([[1, 0, 0], [0, -1, 0], [0, 0, -1]])

        # Draw flyer rotors
        theta = np.linspace(0, 2 * pi, 20)
        circle = np.zeros((3, 20))
        for j, t in enumerate(theta):
            circle[:, j] = np.r_[r * sin(t), r * cos(t), 0]

        hub = np.zeros((3, self.nrotors))
        tippath = np.zeros((3, 20, self.nrotors))
        for i in range(0, self.nrotors):
            # points in the inertial frame
            hub[:, i] = F @ (z + R @ self.D[:, i])

            # Flapping angle scaling for output display - makes it easier to see what flapping is occurring
            q = self.flapscale
            # Rotor -> Plot frame
            Rr = np.array([[
                cos(q * a1s[i]),
                sin(q * b1s[i]) * sin(q * a1s[i]),
                cos(q * b1s[i]) * sin(q * a1s[i])
            ], [0, cos(q * b1s[i]), -sin(q * b1s[i])],
                           [
                               -sin(q * a1s[i]),
                               sin(q * b1s[i]) * cos(q * a1s[i]),
                               cos(q * b1s[i]) * cos(q * a1s[i])
                           ]])

            tippath[:, :, i] = F @ R @ Rr @ circle
            plot3(self.disk[i], hub[0, i] + tippath[0, :, i],
                  hub[1, i] + tippath[1, :, i], hub[2, i] + tippath[2, :, i])

        # Draw flyer
        hub0 = F @ z  # centre of vehicle
        for i in range(0, self.nrotors):
            # line from hub to centre plot3([hub(1,N) hub(1,S)],[hub(2,N) hub(2,S)],[hub(3,N) hub(3,S)],'-b')
            plot3(self.arm[i], [hub[0, i], hub0[0]], [hub[1, i], hub0[1]],
                  [hub[2, i], hub0[2]])

            # plot a circle at the hub itself
            # plot3([hub(1,i)],[hub(2,i)],[hub(3,i)],'o')

        # plot the vehicle's centroid on the ground plane
        plot3(self.shadow, [z[0], 0], [-z[1], 0], [0, 0])
        plot3(self.groundmark, z[0], -z[1], 0)