Exemple #1
0
    def _compose_core(is0, mu1, mu2, nu, mu1_axis, mu2_axis):

        # convert angles to radians
        mu1rad, mu2rad = mu1 / 1000.0, mu2 / 1000.0

        # compose rotation matrices and their first order derivatives
        Mu1 = (mu1_axis).axis_and_angle_as_r3_rotation_matrix(mu1rad, deg=False)
        dMu1_dmu1 = dR_from_axis_and_angle(mu1_axis, mu1rad, deg=False)

        Mu2 = (mu2_axis).axis_and_angle_as_r3_rotation_matrix(mu2rad, deg=False)
        dMu2_dmu2 = dR_from_axis_and_angle(mu2_axis, mu2rad, deg=False)

        # compose new state
        Mu21 = Mu2 * Mu1
        s0_new_dir = (Mu21 * is0).normalize()
        s0 = nu * s0_new_dir

        # calculate derivatives of the beam direction wrt angles:
        #  1) derivative wrt mu1
        dMu21_dmu1 = Mu2 * dMu1_dmu1
        ds0_new_dir_dmu1 = dMu21_dmu1 * is0

        #  2) derivative wrt mu2
        dMu21_dmu2 = dMu2_dmu2 * Mu1
        ds0_new_dir_dmu2 = dMu21_dmu2 * is0

        # calculate derivatives of the attached beam vector, converting
        # parameters back to mrad
        ds0_dval = [
            ds0_new_dir_dmu1 * nu / 1000.0,
            ds0_new_dir_dmu2 * nu / 1000.0,
            s0_new_dir,
        ]

        return s0, ds0_dval
    def _compose_core(iS, gamma1, gamma2, gamma1_axis, gamma2_axis):

        # convert angles to radians
        g1rad, g2rad = gamma1 / 1000.0, gamma2 / 1000.0

        # compose rotation matrices and their first order derivatives
        G1 = (gamma1_axis).axis_and_angle_as_r3_rotation_matrix(g1rad,
                                                                deg=False)
        dG1_dg1 = dR_from_axis_and_angle(gamma1_axis, g1rad, deg=False)

        G2 = (gamma2_axis).axis_and_angle_as_r3_rotation_matrix(g2rad,
                                                                deg=False)
        dG2_dg2 = dR_from_axis_and_angle(gamma2_axis, g2rad, deg=False)

        # compose new state
        G21 = G2 * G1
        S = G21 * iS

        # calculate derivatives of S wrt angles:
        #  1) derivative wrt gamma1
        dG21_dg1 = G2 * dG1_dg1
        dS_dg1 = dG21_dg1 * iS

        #  2) derivative wrt gamma2
        dG21_dg2 = dG2_dg2 * G1
        dS_dg2 = dG21_dg2 * iS

        # Convert derivatives back to mrad
        dS_dval = [dS_dg1 / 1000.0, dS_dg2 / 1000.0]

        return S, dS_dval
Exemple #3
0
  def _compose_core(is0, mu1, mu2, nu, mu1_axis, mu2_axis):

    # convert angles to radians
    mu1rad, mu2rad = mu1 / 1000., mu2 / 1000.

    # compose rotation matrices and their first order derivatives
    Mu1 = (mu1_axis).axis_and_angle_as_r3_rotation_matrix(mu1rad, deg=False)
    dMu1_dmu1 = dR_from_axis_and_angle(mu1_axis, mu1rad, deg=False)

    Mu2 = (mu2_axis).axis_and_angle_as_r3_rotation_matrix(mu2rad, deg=False)
    dMu2_dmu2 = dR_from_axis_and_angle(mu2_axis, mu2rad, deg=False)

    # compose new state
    Mu21 = Mu2 * Mu1
    s0_new_dir = (Mu21 * is0).normalize()
    s0 = nu * s0_new_dir

    # calculate derivatives of the beam direction wrt angles:
    #  1) derivative wrt mu1
    dMu21_dmu1 = Mu2 * dMu1_dmu1
    ds0_new_dir_dmu1 = dMu21_dmu1 * is0

    #  2) derivative wrt mu2
    dMu21_dmu2 = dMu2_dmu2 * Mu1
    ds0_new_dir_dmu2 = dMu21_dmu2 * is0

    # calculate derivatives of the attached beam vector, converting
    # parameters back to mrad
    ds0_dval = [ds0_new_dir_dmu1 * nu / 1000.,
                ds0_new_dir_dmu2 * nu / 1000.,
                s0_new_dir]

    return s0, ds0_dval
  def compose(self, t):
    """calculate state and derivatives for model at image number t"""

    # Extract orientation from the initial state
    U0 = self._initial_state

    # extract parameter sets from the internal list
    phi1_set, phi2_set, phi3_set = self._param

    # extract angles and other data at time t using the smoother
    phi1, phi1_weights, phi1_sumweights = self._smoother.value_weight(t, phi1_set)
    phi2, phi2_weights, phi2_sumweights = self._smoother.value_weight(t, phi2_set)
    phi3, phi3_weights, phi3_sumweights = self._smoother.value_weight(t, phi3_set)

    # calculate derivatives of angles wrt underlying parameters.
    # FIXME write up notes in orange notebook
    dphi1_dp = [e / phi1_sumweights for e in phi1_weights]
    dphi2_dp = [e / phi2_sumweights for e in phi2_weights]
    dphi3_dp = [e / phi3_sumweights for e in phi3_weights]

    # convert angles to radians
    phi1rad, phi2rad, phi3rad = (phi1 / 1000., phi2 / 1000.,
                                 phi3 / 1000.)

    # compose rotation matrices and their first order derivatives wrt angle
    Phi1 = (phi1_set.axis).axis_and_angle_as_r3_rotation_matrix(phi1rad, deg=False)
    dPhi1_dphi1 = dR_from_axis_and_angle(phi1_set.axis, phi1rad, deg=False)

    Phi2 = (phi2_set.axis).axis_and_angle_as_r3_rotation_matrix(phi2rad, deg=False)
    dPhi2_dphi2 = dR_from_axis_and_angle(phi2_set.axis, phi2rad, deg=False)

    Phi3 = (phi3_set.axis).axis_and_angle_as_r3_rotation_matrix(phi3rad, deg=False)
    dPhi3_dphi3 = dR_from_axis_and_angle(phi3_set.axis, phi3rad, deg=False)

    Phi21 = Phi2 * Phi1
    Phi321 = Phi3 * Phi21

    # Compose new state
    self._U_at_t = Phi321 * U0

    # calculate derivatives of the state wrt angle, convert back to mrad
    dU_dphi1 = Phi3 * Phi2 * dPhi1_dphi1 * U0 / 1000.
    dU_dphi2 = Phi3 * dPhi2_dphi2 * Phi1 * U0 / 1000.
    dU_dphi3 = dPhi3_dphi3 * Phi21 * U0 / 1000.

    # calculate derivatives of state wrt underlying parameters
    dU_dp1 = [dU_dphi1 * e for e in dphi1_dp]
    dU_dp2 = [dU_dphi2 * e for e in dphi2_dp]
    dU_dp3 = [dU_dphi3 * e for e in dphi3_dp]

    # store derivatives as list-of-lists
    self._dstate_dp = [dU_dp1, dU_dp2, dU_dp3]

    return
    def compose(self):

        # extract parameters from the internal list
        dist, shift1, shift2, tau1, tau2, tau3 = self._param

        # convert angles to radians
        tau1rad = tau1.value / 1000.0
        tau2rad = tau2.value / 1000.0
        tau3rad = tau3.value / 1000.0

        # compose rotation matrices and their first order derivatives
        Tau1 = (tau1.axis).axis_and_angle_as_r3_rotation_matrix(tau1rad,
                                                                deg=False)
        dTau1_dtau1 = dR_from_axis_and_angle(tau1.axis, tau1rad, deg=False)

        Tau2 = (tau2.axis).axis_and_angle_as_r3_rotation_matrix(tau2rad,
                                                                deg=False)
        dTau2_dtau2 = dR_from_axis_and_angle(tau2.axis, tau2rad, deg=False)

        Tau3 = (tau3.axis).axis_and_angle_as_r3_rotation_matrix(tau3rad,
                                                                deg=False)
        dTau3_dtau3 = dR_from_axis_and_angle(tau3.axis, tau3rad, deg=False)

        # Compose the new state
        from scitbx.array_family import flex

        from dials_refinement_helpers_ext import multi_panel_compose

        ret = multi_panel_compose(
            flex.vec3_double(
                [self._initial_state[tag] for tag in ("d1", "d2", "dn")]),
            flex.double([p.value for p in self._param]),
            flex.vec3_double([p.axis for p in self._param]),
            self._model,
            flex.vec3_double(self._offsets),
            flex.vec3_double(self._dir1s),
            flex.vec3_double(self._dir2s),
            Tau1,
            dTau1_dtau1,
            Tau2,
            dTau2_dtau2,
            Tau3,
            dTau3_dtau3,
        )

        # Store the results.  The results come back as a single array, convert it to a 2D array
        self._multi_state_derivatives = [[
            matrix.sqr(ret[j * len(self._offsets) + i])
            for j in range(len(self._param))
        ] for i in range(len(self._offsets))]
Exemple #6
0
  def compose(self):

    # extract direction from the initial state
    is0 = self._initial_state

    # extract parameters from the internal list
    mu1, mu2, nu = self._param

    # convert angles to radians
    mu1rad, mu2rad = mu1.value / 1000., mu2.value / 1000.

    # compose rotation matrices and their first order derivatives
    Mu1 = (mu1.axis).axis_and_angle_as_r3_rotation_matrix(mu1rad, deg=False)
    dMu1_dmu1 = dR_from_axis_and_angle(mu1.axis, mu1rad, deg=False)

    Mu2 = (mu2.axis).axis_and_angle_as_r3_rotation_matrix(mu2rad, deg=False)
    dMu2_dmu2 = dR_from_axis_and_angle(mu2.axis, mu2rad, deg=False)

    Mu21 = Mu2 * Mu1

    ### Compose new state
    s0_new_dir = (Mu21 * is0).normalize()

    # now update the model with its new s0
    self._model.set_s0(nu.value * s0_new_dir)

    ### calculate derivatives of the beam direction wrt angles
    # derivative wrt mu1
    dMu21_dmu1 = Mu2 * dMu1_dmu1
    ds0_new_dir_dmu1 = dMu21_dmu1 * is0

    # derivative wrt mu2
    dMu21_dmu2 = dMu2_dmu2 * Mu1
    ds0_new_dir_dmu2 = dMu21_dmu2 * is0

    ### calculate derivatives of the attached beam vector, converting
    ### parameters back to mrad, and store
    # derivative wrt mu1
    self._dstate_dp[0] = ds0_new_dir_dmu1 * nu.value / 1000.
    # derivative wrt mu2
    self._dstate_dp[1] = ds0_new_dir_dmu2 * nu.value / 1000.
    # derivative wrt nu
    self._dstate_dp[2] = s0_new_dir

    return
Exemple #7
0
  def compose(self):
    # extract parameters from the internal list
    dist, shift1, shift2, tau1, tau2, tau3 = self._param

    # convert angles to radians
    tau1rad = tau1.value / 1000.
    tau2rad = tau2.value / 1000.
    tau3rad = tau3.value / 1000.

    # compose rotation matrices and their first order derivatives
    Tau1 = (tau1.axis).axis_and_angle_as_r3_rotation_matrix(tau1rad,
                                                            deg=False)
    dTau1_dtau1 = dR_from_axis_and_angle(tau1.axis, tau1rad, deg=False)

    Tau2 = (tau2.axis).axis_and_angle_as_r3_rotation_matrix(tau2rad,
                                                            deg=False)
    dTau2_dtau2 = dR_from_axis_and_angle(tau2.axis, tau2rad, deg=False)

    Tau3 = (tau3.axis).axis_and_angle_as_r3_rotation_matrix(tau3rad,
                                                            deg=False)
    dTau3_dtau3 = dR_from_axis_and_angle(tau3.axis, tau3rad, deg=False)

    # Compose the new state
    from dials_refinement_helpers_ext import multi_panel_compose
    from scitbx.array_family import flex
    ret = multi_panel_compose(flex.vec3_double([self._initial_state[tag] for tag in ('d1','d2','dn')]),
                              flex.double([p.value for p in self._param]),
                              flex.vec3_double([p.axis for p in self._param]),
                              self._model,
                              flex.vec3_double(self._offsets),
                              flex.vec3_double(self._dir1s),
                              flex.vec3_double(self._dir2s),
                              Tau1, dTau1_dtau1, Tau2, dTau2_dtau2, Tau3, dTau3_dtau3)

    # Store the results.  The results come back as a single array, convert it to a 2D array
    self._multi_state_derivatives = [[matrix.sqr(ret[j*len(self._offsets)+i]) \
        for j in xrange(len(self._param))] \
        for i in xrange(len(self._offsets))]

    return
    def _compose_core(self, dist, shift1, shift2, tau1, tau2, tau3):

        # extract items from the initial state
        id1 = self._initial_state["d1"]
        id2 = self._initial_state["d2"]
        ioffset = self._initial_state["offset"]

        # convert angles to radians
        tau1rad = tau1.value / 1000.0
        tau2rad = tau2.value / 1000.0
        tau3rad = tau3.value / 1000.0

        # compose rotation matrices and their first order derivatives
        Tau1 = (tau1.axis).axis_and_angle_as_r3_rotation_matrix(tau1rad,
                                                                deg=False)
        dTau1_dtau1 = dR_from_axis_and_angle(tau1.axis, tau1rad, deg=False)

        Tau2 = (tau2.axis).axis_and_angle_as_r3_rotation_matrix(tau2rad,
                                                                deg=False)
        dTau2_dtau2 = dR_from_axis_and_angle(tau2.axis, tau2rad, deg=False)

        Tau3 = (tau3.axis).axis_and_angle_as_r3_rotation_matrix(tau3rad,
                                                                deg=False)
        dTau3_dtau3 = dR_from_axis_and_angle(tau3.axis, tau3rad, deg=False)

        Tau32 = Tau3 * Tau2
        Tau321 = Tau32 * Tau1

        # Compose new state
        # =================
        # First the frame positioned at a distance from the lab origin
        P0 = dist.value * dist.axis  # distance along initial detector normal
        Px = P0 + id1  # point at the end of d1 in lab frame
        Py = P0 + id2  # point at the end of d2 in lab frame

        # detector shift vector
        dsv = P0 + shift1.value * shift1.axis + shift2.value * shift2.axis

        # compose dorg point
        dorg = Tau321 * dsv - Tau32 * P0 + P0

        # compose d1, d2 and dn and ensure frame remains orthonormal.
        d1 = (Tau321 * (Px - P0)).normalize()
        d2 = (Tau321 * (Py - P0)).normalize()
        dn = d1.cross(d2).normalize()
        # NB dn not actually used in this simple model; calculation left
        # here as a reminder for future extension
        d2 = dn.cross(d1)

        # compose new sensor origin
        o = dorg + ioffset[0] * d1 + ioffset[1] * d2

        # keep the new state for return
        new_state = {"d1": d1, "d2": d2, "origin": o}

        # calculate derivatives of the state wrt parameters
        # =================================================
        # Start with the dorg vector, where
        # dorg = Tau321 * dsv - Tau32 * P0 + P0

        # derivative wrt dist
        dP0_ddist = dist.axis
        ddsv_ddist = dP0_ddist
        ddorg_ddist = Tau321 * ddsv_ddist - Tau32 * dP0_ddist + dP0_ddist

        # derivative wrt shift1
        ddsv_dshift1 = shift1.axis
        ddorg_dshift1 = Tau321 * ddsv_dshift1

        # derivative wrt shift2
        ddsv_dshift2 = shift2.axis
        ddorg_dshift2 = Tau321 * ddsv_dshift2

        # derivative wrt tau1
        dTau321_dtau1 = Tau32 * dTau1_dtau1
        ddorg_dtau1 = dTau321_dtau1 * dsv

        # derivative wrt tau2
        dTau32_dtau2 = Tau3 * dTau2_dtau2
        dTau321_dtau2 = dTau32_dtau2 * Tau1
        ddorg_dtau2 = dTau321_dtau2 * dsv - dTau32_dtau2 * P0

        # derivative wrt tau3
        dTau32_dtau3 = dTau3_dtau3 * Tau2
        dTau321_dtau3 = dTau32_dtau3 * Tau1
        ddorg_dtau3 = dTau321_dtau3 * dsv - dTau32_dtau3 * P0

        # Now derivatives of the direction d1, where
        # d1 = (Tau321 * (Px - P0)).normalize()
        # For calc of derivatives ignore the normalize(), which should
        # be unnecessary anyway as Px - P0 is a unit vector and Tau321 a
        # pure rotation.

        # derivative wrt dist
        # dPx_ddist = dist.axis; dP0_ddist = dist.axis, so these cancel
        dd1_ddist = matrix.col((0.0, 0.0, 0.0))

        # derivative wrt shift1
        dd1_dshift1 = matrix.col((0.0, 0.0, 0.0))

        # derivative wrt shift2
        dd1_dshift2 = matrix.col((0.0, 0.0, 0.0))

        # derivative wrt tau1
        dd1_dtau1 = dTau321_dtau1 * (Px - P0)

        # derivative wrt tau2
        dd1_dtau2 = dTau321_dtau2 * (Px - P0)

        # derivative wrt tau3
        dd1_dtau3 = dTau321_dtau3 * (Px - P0)

        # Derivatives of the direction d2, where
        # d2 = (Tau321 * (Py - P0)).normalize()

        # derivative wrt dist
        dd2_ddist = matrix.col((0.0, 0.0, 0.0))

        # derivative wrt shift1
        dd2_dshift1 = matrix.col((0.0, 0.0, 0.0))

        # derivative wrt shift2
        dd2_dshift2 = matrix.col((0.0, 0.0, 0.0))

        # derivative wrt tau1
        dd2_dtau1 = dTau321_dtau1 * (Py - P0)

        # derivative wrt tau2
        dd2_dtau2 = dTau321_dtau2 * (Py - P0)

        # derivative wrt tau3
        dd2_dtau3 = dTau321_dtau3 * (Py - P0)

        # Derivatives of the direction dn, where dn = d1.cross(d2).normalize()
        # These derivatives are not used, but are left as comments for understanding

        # derivative wrt dist
        # ddn_ddist = matrix.col((0.0, 0.0, 0.0))

        # derivative wrt shift1
        # ddn_dshift1 = matrix.col((0.0, 0.0, 0.0))

        # derivative wrt shift2
        # ddn_dshift2 = matrix.col((0.0, 0.0, 0.0))

        # derivative wrt tau1. Product rule for cross product applies
        # ddn_dtau1 = dd1_dtau1.cross(d2) + d1.cross(dd2_dtau1)

        # derivative wrt tau2
        # ddn_dtau2 = dd1_dtau2.cross(d2) + d1.cross(dd2_dtau2)

        # derivative wrt tau3
        # ddn_dtau3 = dd1_dtau3.cross(d2) + d1.cross(dd2_dtau3)

        # calculate derivatives of the attached sensor matrix
        # ===================================================
        # sensor origin:
        # o = dorg + ioffset[0] * d1 + ioffset[1] * d2

        # derivative wrt dist
        do_ddist = ddorg_ddist + ioffset[0] * dd1_ddist + ioffset[1] * dd2_ddist

        # derivative wrt shift1
        do_dshift1 = ddorg_dshift1 + ioffset[0] * dd1_dshift1 + ioffset[
            1] * dd2_dshift1

        # derivative wrt shift2
        do_dshift2 = ddorg_dshift2 + ioffset[0] * dd1_dshift2 + ioffset[
            1] * dd2_dshift2

        # derivative wrt tau1
        do_dtau1 = ddorg_dtau1 + ioffset[0] * dd1_dtau1 + ioffset[1] * dd2_dtau1

        # derivative wrt tau2
        do_dtau2 = ddorg_dtau2 + ioffset[0] * dd1_dtau2 + ioffset[1] * dd2_dtau2

        # derivative wrt tau3
        do_dtau3 = ddorg_dtau3 + ioffset[0] * dd1_dtau3 + ioffset[1] * dd2_dtau3

        # combine these vectors together into derivatives of the sensor
        # matrix d, converting angles back to mrad
        dd_dval = []

        # derivative wrt dist
        dd_dval.append(
            matrix.sqr(dd1_ddist.elems + dd2_ddist.elems +
                       do_ddist.elems).transpose())

        # derivative wrt shift1
        dd_dval.append(
            matrix.sqr(dd1_dshift1.elems + dd2_dshift1.elems +
                       do_dshift1.elems).transpose())

        # derivative wrt shift2
        dd_dval.append(
            matrix.sqr(dd1_dshift2.elems + dd2_dshift2.elems +
                       do_dshift2.elems).transpose())

        # derivative wrt tau1
        dd_dval.append(
            matrix.sqr(dd1_dtau1.elems + dd2_dtau1.elems +
                       do_dtau1.elems).transpose() / 1000.0)

        # derivative wrt tau2
        dd_dval.append(
            matrix.sqr(dd1_dtau2.elems + dd2_dtau2.elems +
                       do_dtau2.elems).transpose() / 1000.0)

        # derivative wrt tau3
        dd_dval.append(
            matrix.sqr(dd1_dtau3.elems + dd2_dtau3.elems +
                       do_dtau3.elems).transpose() / 1000.0)

        return new_state, dd_dval
    def compose(self):

        # extract items from the initial state
        id1 = self._initial_state["d1"]
        id2 = self._initial_state["d2"]

        # extract parameters from the internal list
        dist, shift1, shift2, tau1, tau2, tau3 = self._param

        # Extract the detector model
        detector = self._model

        # convert angles to radians
        tau1rad = tau1.value / 1000.0
        tau2rad = tau2.value / 1000.0
        tau3rad = tau3.value / 1000.0

        # compose rotation matrices and their first order derivatives
        Tau1 = (tau1.axis).axis_and_angle_as_r3_rotation_matrix(tau1rad,
                                                                deg=False)
        dTau1_dtau1 = dR_from_axis_and_angle(tau1.axis, tau1rad, deg=False)

        Tau2 = (tau2.axis).axis_and_angle_as_r3_rotation_matrix(tau2rad,
                                                                deg=False)
        dTau2_dtau2 = dR_from_axis_and_angle(tau2.axis, tau2rad, deg=False)

        Tau3 = (tau3.axis).axis_and_angle_as_r3_rotation_matrix(tau3rad,
                                                                deg=False)
        dTau3_dtau3 = dR_from_axis_and_angle(tau3.axis, tau3rad, deg=False)

        Tau32 = Tau3 * Tau2
        Tau321 = Tau32 * Tau1

        # Compose new state
        # =================
        # First the frame positioned at a distance from the lab origin
        P0 = dist.value * dist.axis  # distance along initial detector normal
        Px = P0 + id1  # point at the end of d1 in lab frame
        Py = P0 + id2  # point at the end of d2 in lab frame

        # detector shift vector
        dsv = P0 + shift1.value * shift1.axis + shift2.value * shift2.axis

        # compose dorg point
        dorg = Tau321 * dsv - Tau32 * P0 + P0

        # compose new d1, d2 and dn and ensure frame remains orthonormal.
        d1 = (Tau321 * (Px - P0)).normalize()
        d2 = (Tau321 * (Py - P0)).normalize()
        dn = d1.cross(d2).normalize()
        d2 = dn.cross(d1)

        # compose new Panel origins
        origins = [
            dorg + offset[0] * d1 + offset[1] * d2 + offset[2] * dn
            for offset in self._offsets
        ]

        # compose new Panel directions
        dir1s = [
            vec[0] * d1 + vec[1] * d2 + vec[2] * dn for vec in self._dir1s
        ]
        dir2s = [
            vec[0] * d1 + vec[1] * d2 + vec[2] * dn for vec in self._dir2s
        ]

        # now update the panels with their new position and orientation.
        for p, dir1, dir2, org in zip(detector, dir1s, dir2s, origins):

            p.set_frame(dir1, dir2, org)

        # calculate derivatives of the state wrt parameters
        # =================================================
        # Start with the dorg vector, where
        # dorg = Tau321 * dsv - Tau32 * P0 + P0

        # derivative wrt dist
        dP0_ddist = dist.axis
        ddsv_ddist = dP0_ddist
        ddorg_ddist = Tau321 * ddsv_ddist - Tau32 * dP0_ddist + dP0_ddist

        # derivative wrt shift1
        ddsv_dshift1 = shift1.axis
        ddorg_dshift1 = Tau321 * ddsv_dshift1

        # derivative wrt shift2
        ddsv_dshift2 = shift2.axis
        ddorg_dshift2 = Tau321 * ddsv_dshift2

        # derivative wrt tau1
        dTau321_dtau1 = Tau32 * dTau1_dtau1
        ddorg_dtau1 = dTau321_dtau1 * dsv

        # derivative wrt tau2
        dTau32_dtau2 = Tau3 * dTau2_dtau2
        dTau321_dtau2 = dTau32_dtau2 * Tau1
        ddorg_dtau2 = dTau321_dtau2 * dsv - dTau32_dtau2 * P0

        # derivative wrt tau3
        dTau32_dtau3 = dTau3_dtau3 * Tau2
        dTau321_dtau3 = dTau32_dtau3 * Tau1
        ddorg_dtau3 = dTau321_dtau3 * dsv - dTau32_dtau3 * P0

        # Now derivatives of the direction d1, where
        # d1 = (Tau321 * (Px - P0)).normalize()
        # For calc of derivatives ignore the normalize(), which should
        # be unnecessary anyway as Px - P0 is a unit vector and Tau321 a
        # pure rotation.

        # derivative wrt dist
        # dPx_ddist = dist.axis; dP0_ddist = dist.axis, so these cancel
        dd1_ddist = matrix.col((0.0, 0.0, 0.0))

        # derivative wrt shift1
        dd1_dshift1 = matrix.col((0.0, 0.0, 0.0))

        # derivative wrt shift2
        dd1_dshift2 = matrix.col((0.0, 0.0, 0.0))

        # derivative wrt tau1
        dd1_dtau1 = dTau321_dtau1 * (Px - P0)

        # derivative wrt tau2
        dd1_dtau2 = dTau321_dtau2 * (Px - P0)

        # derivative wrt tau3
        dd1_dtau3 = dTau321_dtau3 * (Px - P0)

        # Derivatives of the direction d2, where
        # d2 = (Tau321 * (Py - P0)).normalize()

        # derivative wrt dist
        dd2_ddist = matrix.col((0.0, 0.0, 0.0))

        # derivative wrt shift1
        dd2_dshift1 = matrix.col((0.0, 0.0, 0.0))

        # derivative wrt shift2
        dd2_dshift2 = matrix.col((0.0, 0.0, 0.0))

        # derivative wrt tau1
        dd2_dtau1 = dTau321_dtau1 * (Py - P0)

        # derivative wrt tau2
        dd2_dtau2 = dTau321_dtau2 * (Py - P0)

        # derivative wrt tau3
        dd2_dtau3 = dTau321_dtau3 * (Py - P0)

        # Derivatives of the direction dn, where
        # dn = d1.cross(d2).normalize()

        # derivative wrt dist
        ddn_ddist = matrix.col((0.0, 0.0, 0.0))

        # derivative wrt shift1
        ddn_dshift1 = matrix.col((0.0, 0.0, 0.0))

        # derivative wrt shift2
        ddn_dshift2 = matrix.col((0.0, 0.0, 0.0))

        # derivative wrt tau1. Product rule for cross product applies
        ddn_dtau1 = dd1_dtau1.cross(d2) + d1.cross(dd2_dtau1)

        # derivative wrt tau2
        ddn_dtau2 = dd1_dtau2.cross(d2) + d1.cross(dd2_dtau2)

        # derivative wrt tau3
        ddn_dtau3 = dd1_dtau3.cross(d2) + d1.cross(dd2_dtau3)

        # reset stored derivatives
        for i in range(len(detector)):
            self._multi_state_derivatives[i] = [None] * len(self._dstate_dp)

        # calculate derivatives of the attached Panel matrices
        # ====================================================
        for panel_id, (offset, dir1_new_basis, dir2_new_basis) in enumerate(
                zip(self._offsets, self._dir1s, self._dir2s)):

            # Panel origin:
            # o = dorg + offset[0] * d1 + offset[1] * d2 + offset[2] * dn

            # derivative wrt dist. NB only ddorg_ddist is not null! The other
            # elements are left here to aid understanding, but should be removed
            # when this class is ported to C++ for speed.
            do_ddist = (ddorg_ddist + offset[0] * dd1_ddist +
                        offset[1] * dd2_ddist + offset[2] * ddn_ddist)

            # derivative wrt shift1. NB only ddorg_dshift1 is non-null.
            do_dshift1 = (ddorg_dshift1 + offset[0] * dd1_dshift1 +
                          offset[1] * dd2_dshift1 + offset[2] * ddn_dshift1)

            # derivative wrt shift2. NB only ddorg_dshift2 is non-null.
            do_dshift2 = (ddorg_dshift2 + offset[0] * dd1_dshift2 +
                          offset[1] * dd2_dshift2 + offset[2] * ddn_dshift2)

            # derivative wrt tau1
            do_dtau1 = (ddorg_dtau1 + offset[0] * dd1_dtau1 +
                        offset[1] * dd2_dtau1 + offset[2] * ddn_dtau1)

            # derivative wrt tau2
            do_dtau2 = (ddorg_dtau2 + offset[0] * dd1_dtau2 +
                        offset[1] * dd2_dtau2 + offset[2] * ddn_dtau2)

            # derivative wrt tau3
            do_dtau3 = (ddorg_dtau3 + offset[0] * dd1_dtau3 +
                        offset[1] * dd2_dtau3 + offset[2] * ddn_dtau3)

            # Panel dir1:
            # dir1 = dir1_new_basis[0] * d1 + dir1_new_basis[1] * d2 +
            #        dir1_new_basis[2] * dn

            # derivative wrt dist. NB These are all null.
            ddir1_ddist = (dir1_new_basis[0] * dd1_ddist +
                           dir1_new_basis[1] * dd2_ddist +
                           dir1_new_basis[2] * ddn_ddist)

            # derivative wrt shift1. NB These are all null.
            ddir1_dshift1 = (dir1_new_basis[0] * dd1_dshift1 +
                             dir1_new_basis[1] * dd2_dshift1 +
                             dir1_new_basis[2] * ddn_dshift1)

            # derivative wrt shift2. NB These are all null.
            ddir1_dshift2 = (dir1_new_basis[0] * dd1_dshift2 +
                             dir1_new_basis[1] * dd2_dshift2 +
                             dir1_new_basis[2] * ddn_dshift2)

            # derivative wrt tau1
            ddir1_dtau1 = (dir1_new_basis[0] * dd1_dtau1 +
                           dir1_new_basis[1] * dd2_dtau1 +
                           dir1_new_basis[2] * ddn_dtau1)

            # derivative wrt tau2
            ddir1_dtau2 = (dir1_new_basis[0] * dd1_dtau2 +
                           dir1_new_basis[1] * dd2_dtau2 +
                           dir1_new_basis[2] * ddn_dtau2)

            # derivative wrt tau3
            ddir1_dtau3 = (dir1_new_basis[0] * dd1_dtau3 +
                           dir1_new_basis[1] * dd2_dtau3 +
                           dir1_new_basis[2] * ddn_dtau3)

            # Panel dir2:
            # dir2 = dir2_new_basis[0] * d1 + dir2_new_basis[1] * d2 +
            #        dir2_new_basis[2] * dn

            # derivative wrt dist. NB These are all null.
            ddir2_ddist = (dir2_new_basis[0] * dd1_ddist +
                           dir2_new_basis[1] * dd2_ddist +
                           dir2_new_basis[2] * ddn_ddist)

            # derivative wrt shift1. NB These are all null.
            ddir2_dshift1 = (dir2_new_basis[0] * dd1_dshift1 +
                             dir2_new_basis[1] * dd2_dshift1 +
                             dir2_new_basis[2] * ddn_dshift1)

            # derivative wrt shift2. NB These are all null.
            ddir2_dshift2 = (dir2_new_basis[0] * dd1_dshift2 +
                             dir2_new_basis[1] * dd2_dshift2 +
                             dir2_new_basis[2] * ddn_dshift2)

            # derivative wrt tau1
            ddir2_dtau1 = (dir2_new_basis[0] * dd1_dtau1 +
                           dir2_new_basis[1] * dd2_dtau1 +
                           dir2_new_basis[2] * ddn_dtau1)

            # derivative wrt tau2
            ddir2_dtau2 = (dir2_new_basis[0] * dd1_dtau2 +
                           dir2_new_basis[1] * dd2_dtau2 +
                           dir2_new_basis[2] * ddn_dtau2)

            # derivative wrt tau3
            ddir2_dtau3 = (dir2_new_basis[0] * dd1_dtau3 +
                           dir2_new_basis[1] * dd2_dtau3 +
                           dir2_new_basis[2] * ddn_dtau3)

            # combine these vectors together into derivatives of the panel
            # matrix d and store them, converting angles back to mrad
            self._multi_state_derivatives[panel_id] = [
                matrix.sqr(ddir1_ddist.elems + ddir2_ddist.elems +
                           do_ddist.elems).transpose(),
                matrix.sqr(ddir1_dshift1.elems + ddir2_dshift1.elems +
                           do_dshift1.elems).transpose(),
                matrix.sqr(ddir1_dshift2.elems + ddir2_dshift2.elems +
                           do_dshift2.elems).transpose(),
                matrix.sqr(ddir1_dtau1.elems + ddir2_dtau1.elems +
                           do_dtau1.elems).transpose() / 1000.0,
                matrix.sqr(ddir1_dtau2.elems + ddir2_dtau2.elems +
                           do_dtau2.elems).transpose() / 1000.0,
                matrix.sqr(ddir1_dtau3.elems + ddir2_dtau3.elems +
                           do_dtau3.elems).transpose() / 1000.0,
            ]
Exemple #10
0
  def compose(self):

    # reset the list that holds derivatives
    for i in range(len(self._model)):
      self._multi_state_derivatives[i] = [None] * len(self._dstate_dp)

    # loop over groups of panels collecting derivatives of the state wrt
    # parameters
    param = iter(self._param)
    for igp, pnl_ids in enumerate(self._panel_ids_by_group):

      # extract parameters from the internal list
      dist = next(param)
      shift1 = next(param)
      shift2 = next(param)
      tau1 = next(param)
      tau2 = next(param)
      tau3 = next(param)

      #param_vals = flex.double((dist.value, shift1.value, shift2.value,
      #                            tau1.value, tau2.value, tau3.value))
      #param_axes = flex.vec3_double((dist.axis, shift1.axis, shift2.axis,
      #                                tau1.axis, tau2.axis, tau3.axis))
      offsets = self._offsets[igp]
      dir1s = self._dir1s[igp]
      dir2s = self._dir2s[igp]

      # convert angles to radians
      tau1rad = tau1.value / 1000.
      tau2rad = tau2.value / 1000.
      tau3rad = tau3.value / 1000.

      # compose rotation matrices and their first order derivatives
      Tau1 = (tau1.axis).axis_and_angle_as_r3_rotation_matrix(tau1rad,
                                                              deg=False)
      dTau1_dtau1 = dR_from_axis_and_angle(tau1.axis, tau1rad, deg=False)

      Tau2 = (tau2.axis).axis_and_angle_as_r3_rotation_matrix(tau2rad,
                                                              deg=False)
      dTau2_dtau2 = dR_from_axis_and_angle(tau2.axis, tau2rad, deg=False)

      Tau3 = (tau3.axis).axis_and_angle_as_r3_rotation_matrix(tau3rad,
                                                              deg=False)
      dTau3_dtau3 = dR_from_axis_and_angle(tau3.axis, tau3rad, deg=False)

      Tau32 = Tau3 * Tau2
      Tau321 = Tau32 * Tau1

      # Get items from the initial state for the group of interest
      initial_state = self._initial_state[igp]
      id1 = initial_state['d1']
      id2 = initial_state['d2']
      idn = initial_state['dn']
      igp_offset = initial_state['gp_offset']

      # Compose new state
      # =================
      # First the frame positioned at a distance from the lab origin
      P0 = dist.value * dist.axis # distance along initial group normal
      Px = P0 + id1 # point at the end of d1 in lab frame
      Py = P0 + id2 # point at the end of d2 in lab frame

      # detector shift vector
      dsv = P0 + shift1.value * shift1.axis + shift2.value * shift2.axis

      # compose dorg point
      dorg = Tau321 * dsv - Tau32 * P0 + P0

      # compose new d1, d2 and dn and ensure frame remains orthonormal.
      d1 = (Tau321 * (Px - P0)).normalize()
      d2 = (Tau321 * (Py - P0)).normalize()
      dn = d1.cross(d2).normalize()
      d2 = dn.cross(d1)

      # compose new group origin
      origin = dorg + igp_offset[0] * d1 + \
                      igp_offset[1] * d2 + \
                      igp_offset[2] * dn

      # assign back to the group frame
      self._groups[igp].set_frame(d1, d2, origin)

      # calculate derivatives of the state wrt parameters
      # =================================================
      # Start with the dorg vector, where
      # dorg = Tau321 * dsv - Tau32 * P0 + P0

      # derivative wrt dist
      dP0_ddist = dist.axis
      ddsv_ddist = dP0_ddist
      ddorg_ddist = Tau321 * ddsv_ddist - Tau32 * dP0_ddist + \
                    dP0_ddist

      # derivative wrt shift1
      ddsv_dshift1 = shift1.axis
      ddorg_dshift1 = Tau321 * ddsv_dshift1

      # derivative wrt shift2
      ddsv_dshift2 = shift2.axis
      ddorg_dshift2 = Tau321 * ddsv_dshift2

      # derivative wrt tau1
      dTau321_dtau1 = Tau32 * dTau1_dtau1
      ddorg_dtau1 = dTau321_dtau1 * dsv

      # derivative wrt tau2
      dTau32_dtau2 = Tau3 * dTau2_dtau2
      dTau321_dtau2 = dTau32_dtau2 * Tau1
      ddorg_dtau2 = dTau321_dtau2 * dsv - dTau32_dtau2 * P0

      # derivative wrt tau3
      dTau32_dtau3 = dTau3_dtau3 * Tau2
      dTau321_dtau3 = dTau32_dtau3 * Tau1
      ddorg_dtau3 = dTau321_dtau3 * dsv - dTau32_dtau3 * P0

      # Now derivatives of the direction d1, where
      # d1 = (Tau321 * (Px - P0)).normalize()
      # For calc of derivatives ignore the normalize(), which should
      # be unnecessary anyway as Px - P0 is a unit vector and Tau321 a
      # pure rotation.

      # derivative wrt dist
      # dPx_ddist = dist.axis; dP0_ddist = dist.axis, so these cancel
      dd1_ddist = matrix.col((0., 0., 0.))

      # derivative wrt shift1
      dd1_dshift1 = matrix.col((0., 0., 0.))

      # derivative wrt shift2
      dd1_dshift2 = matrix.col((0., 0., 0.))

      # derivative wrt tau1
      dd1_dtau1 = dTau321_dtau1 * (Px - P0)

      # derivative wrt tau2
      dd1_dtau2 = dTau321_dtau2 * (Px - P0)

      # derivative wrt tau3
      dd1_dtau3 = dTau321_dtau3 * (Px - P0)

      # Derivatives of the direction d2, where
      # d2 = (Tau321 * (Py - P0)).normalize()

      # derivative wrt dist
      dd2_ddist = matrix.col((0., 0., 0.))

      # derivative wrt shift1
      dd2_dshift1 = matrix.col((0., 0., 0.))

      # derivative wrt shift2
      dd2_dshift2 = matrix.col((0., 0., 0.))

      # derivative wrt tau1
      dd2_dtau1 = dTau321_dtau1 * (Py - P0)

      # derivative wrt tau2
      dd2_dtau2 = dTau321_dtau2 * (Py - P0)

      # derivative wrt tau3
      dd2_dtau3 = dTau321_dtau3 * (Py - P0)

      # Derivatives of the direction dn, where
      # dn = d1.cross(d2).normalize()

      # derivative wrt dist
      ddn_ddist = matrix.col((0., 0., 0.))

      # derivative wrt shift1
      ddn_dshift1 = matrix.col((0., 0., 0.))

      # derivative wrt shift2
      ddn_dshift2 = matrix.col((0., 0., 0.))

      # derivative wrt tau1. Product rule for cross product applies
      ddn_dtau1 = dd1_dtau1.cross(d2) + d1.cross(dd2_dtau1)

      # derivative wrt tau2
      ddn_dtau2 = dd1_dtau2.cross(d2) + d1.cross(dd2_dtau2)

      # derivative wrt tau3
      ddn_dtau3 = dd1_dtau3.cross(d2) + d1.cross(dd2_dtau3)

      # calculate derivatives of the attached Panel matrices
      # ====================================================
      for (panel_id, offset, dir1_new_basis, dir2_new_basis) in \
                              zip(pnl_ids, offsets, dir1s, dir2s):

        # Panel origin, which is calculated by:
        # o = dorg + offset[0] * d1 + offset[1] * d2 + offset[2] * dn

        # derivative wrt dist. NB only ddorg_ddist is not null! The other
        # elements are left here to aid understanding, but should be removed
        # when this class is ported to C++ for speed.
        do_ddist = ddorg_ddist + offset[0] * dd1_ddist + \
                                 offset[1] * dd2_ddist + \
                                 offset[2] * ddn_ddist

        # derivative wrt shift1. NB only ddorg_dshift1 is non-null.
        do_dshift1 = ddorg_dshift1 + offset[0] * dd1_dshift1 + \
                                     offset[1] * dd2_dshift1 + \
                                     offset[2] * ddn_dshift1

        # derivative wrt shift2. NB only ddorg_dshift2 is non-null.
        do_dshift2 = ddorg_dshift2 + offset[0] * dd1_dshift2 + \
                                     offset[1] * dd2_dshift2 + \
                                     offset[2] * ddn_dshift2

        # derivative wrt tau1
        do_dtau1 = ddorg_dtau1 + offset[0] * dd1_dtau1 + \
                                 offset[1] * dd2_dtau1 + \
                                 offset[2] * ddn_dtau1

        # derivative wrt tau2
        do_dtau2 = ddorg_dtau2 + offset[0] * dd1_dtau2 + \
                                 offset[1] * dd2_dtau2 + \
                                 offset[2] * ddn_dtau2

        # derivative wrt tau3
        do_dtau3 = ddorg_dtau3 + offset[0] * dd1_dtau3 + \
                                 offset[1] * dd2_dtau3 + \
                                 offset[2] * ddn_dtau3

        # Panel dir1:
        # dir1 = dir1_new_basis[0] * d1 + dir1_new_basis[1] * d2 +
        #        dir1_new_basis[2] * dn

        # derivative wrt dist. NB These are all null.
        ddir1_ddist = dir1_new_basis[0] * dd1_ddist + \
                      dir1_new_basis[1] * dd2_ddist + \
                      dir1_new_basis[2] * ddn_ddist

        # derivative wrt shift1. NB These are all null.
        ddir1_dshift1 = dir1_new_basis[0] * dd1_dshift1 + \
                        dir1_new_basis[1] * dd2_dshift1 + \
                        dir1_new_basis[2] * ddn_dshift1

        # derivative wrt shift2. NB These are all null.
        ddir1_dshift2 = dir1_new_basis[0] * dd1_dshift2 + \
                        dir1_new_basis[1] * dd2_dshift2 + \
                        dir1_new_basis[2] * ddn_dshift2

        # derivative wrt tau1
        ddir1_dtau1 = dir1_new_basis[0] * dd1_dtau1 + \
                      dir1_new_basis[1] * dd2_dtau1 + \
                      dir1_new_basis[2] * ddn_dtau1

        # derivative wrt tau2
        ddir1_dtau2 = dir1_new_basis[0] * dd1_dtau2 + \
                      dir1_new_basis[1] * dd2_dtau2 + \
                      dir1_new_basis[2] * ddn_dtau2

        # derivative wrt tau3
        ddir1_dtau3 = dir1_new_basis[0] * dd1_dtau3 + \
                      dir1_new_basis[1] * dd2_dtau3 + \
                      dir1_new_basis[2] * ddn_dtau3

        # Panel dir2:
        # dir2 = dir2_new_basis[0] * d1 + dir2_new_basis[1] * d2 +
        #        dir2_new_basis[2] * dn

        # derivative wrt dist. NB These are all null.
        ddir2_ddist = dir2_new_basis[0] * dd1_ddist + \
                      dir2_new_basis[1] * dd2_ddist + \
                      dir2_new_basis[2] * ddn_ddist

        # derivative wrt shift1. NB These are all null.
        ddir2_dshift1 = dir2_new_basis[0] * dd1_dshift1 + \
                        dir2_new_basis[1] * dd2_dshift1 + \
                        dir2_new_basis[2] * ddn_dshift1

        # derivative wrt shift2. NB These are all null.
        ddir2_dshift2 = dir2_new_basis[0] * dd1_dshift2 + \
                        dir2_new_basis[1] * dd2_dshift2 + \
                        dir2_new_basis[2] * ddn_dshift2

        # derivative wrt tau1
        ddir2_dtau1 = dir2_new_basis[0] * dd1_dtau1 + \
                      dir2_new_basis[1] * dd2_dtau1 + \
                      dir2_new_basis[2] * ddn_dtau1

        # derivative wrt tau2
        ddir2_dtau2 = dir2_new_basis[0] * dd1_dtau2 + \
                      dir2_new_basis[1] * dd2_dtau2 + \
                      dir2_new_basis[2] * ddn_dtau2

        # derivative wrt tau3
        ddir2_dtau3 = dir2_new_basis[0] * dd1_dtau3 + \
                      dir2_new_basis[1] * dd2_dtau3 + \
                      dir2_new_basis[2] * ddn_dtau3

        # combine these vectors together into derivatives of the panel
        # matrix d and store them, converting angles back to mrad

        i = igp * 6

        # derivative wrt dist
        self._multi_state_derivatives[panel_id][i] = \
          matrix.sqr(ddir1_ddist.elems + ddir2_ddist.elems +
                     do_ddist.elems).transpose()

        # derivative wrt shift1
        self._multi_state_derivatives[panel_id][i+1] = \
          matrix.sqr(ddir1_dshift1.elems +
                     ddir2_dshift1.elems + do_dshift1.elems).transpose()

        # derivative wrt shift2
        self._multi_state_derivatives[panel_id][i+2] = \
          matrix.sqr(ddir1_dshift2.elems +
                    ddir2_dshift2.elems + do_dshift2.elems).transpose()

        # derivative wrt tau1
        self._multi_state_derivatives[panel_id][i+3] = \
          matrix.sqr(ddir1_dtau1.elems +
                    ddir2_dtau1.elems + do_dtau1.elems).transpose() / 1000.

        # derivative wrt tau2
        self._multi_state_derivatives[panel_id][i+4] = \
          matrix.sqr(ddir1_dtau2.elems +
                    ddir2_dtau2.elems + do_dtau2.elems).transpose() / 1000.

        # derivative wrt tau3
        self._multi_state_derivatives[panel_id][i+5] = \
          matrix.sqr(ddir1_dtau3.elems +
                    ddir2_dtau3.elems + do_dtau3.elems).transpose() / 1000.

    return
Exemple #11
0
  def compose(self):

    # reset the list that holds derivatives
    for i in range(len(self._model)):
      self._multi_state_derivatives[i] = [None] * len(self._dstate_dp)

    # loop over groups of panels collecting derivatives of the state wrt
    # parameters
    param = iter(self._param)
    for igp, pnl_ids in enumerate(self._panel_ids_by_group):

      # extract parameters from the internal list
      dist = param.next()
      shift1 = param.next()
      shift2 = param.next()
      tau1 = param.next()
      tau2 = param.next()
      tau3 = param.next()

      #param_vals = flex.double((dist.value, shift1.value, shift2.value,
      #                            tau1.value, tau2.value, tau3.value))
      #param_axes = flex.vec3_double((dist.axis, shift1.axis, shift2.axis,
      #                                tau1.axis, tau2.axis, tau3.axis))
      offsets = self._offsets[igp]
      dir1s = self._dir1s[igp]
      dir2s = self._dir2s[igp]

      # convert angles to radians
      tau1rad = tau1.value / 1000.
      tau2rad = tau2.value / 1000.
      tau3rad = tau3.value / 1000.

      # compose rotation matrices and their first order derivatives
      Tau1 = (tau1.axis).axis_and_angle_as_r3_rotation_matrix(tau1rad,
                                                              deg=False)
      dTau1_dtau1 = dR_from_axis_and_angle(tau1.axis, tau1rad, deg=False)

      Tau2 = (tau2.axis).axis_and_angle_as_r3_rotation_matrix(tau2rad,
                                                              deg=False)
      dTau2_dtau2 = dR_from_axis_and_angle(tau2.axis, tau2rad, deg=False)

      Tau3 = (tau3.axis).axis_and_angle_as_r3_rotation_matrix(tau3rad,
                                                              deg=False)
      dTau3_dtau3 = dR_from_axis_and_angle(tau3.axis, tau3rad, deg=False)

      Tau32 = Tau3 * Tau2
      Tau321 = Tau32 * Tau1

      # Get items from the initial state for the group of interest
      initial_state = self._initial_state[igp]
      id1 = initial_state['d1']
      id2 = initial_state['d2']
      idn = initial_state['dn']
      igp_offset = initial_state['gp_offset']

      # Compose new state
      # =================
      # First the frame positioned at a distance from the lab origin
      P0 = dist.value * dist.axis # distance along initial group normal
      Px = P0 + id1 # point at the end of d1 in lab frame
      Py = P0 + id2 # point at the end of d2 in lab frame

      # detector shift vector
      dsv = P0 + shift1.value * shift1.axis + shift2.value * shift2.axis

      # compose dorg point
      dorg = Tau321 * dsv - Tau32 * P0 + P0

      # compose new d1, d2 and dn and ensure frame remains orthonormal.
      d1 = (Tau321 * (Px - P0)).normalize()
      d2 = (Tau321 * (Py - P0)).normalize()
      dn = d1.cross(d2).normalize()
      d2 = dn.cross(d1)

      # compose new group origin
      origin = dorg + igp_offset[0] * d1 + \
                      igp_offset[1] * d2 + \
                      igp_offset[2] * dn

      # assign back to the group frame
      self._groups[igp].set_frame(d1, d2, origin)

      # calculate derivatives of the state wrt parameters
      # =================================================
      # Start with the dorg vector, where
      # dorg = Tau321 * dsv - Tau32 * P0 + P0

      # derivative wrt dist
      dP0_ddist = dist.axis
      ddsv_ddist = dP0_ddist
      ddorg_ddist = Tau321 * ddsv_ddist - Tau32 * dP0_ddist + \
                    dP0_ddist

      # derivative wrt shift1
      ddsv_dshift1 = shift1.axis
      ddorg_dshift1 = Tau321 * ddsv_dshift1

      # derivative wrt shift2
      ddsv_dshift2 = shift2.axis
      ddorg_dshift2 = Tau321 * ddsv_dshift2

      # derivative wrt tau1
      dTau321_dtau1 = Tau32 * dTau1_dtau1
      ddorg_dtau1 = dTau321_dtau1 * dsv

      # derivative wrt tau2
      dTau32_dtau2 = Tau3 * dTau2_dtau2
      dTau321_dtau2 = dTau32_dtau2 * Tau1
      ddorg_dtau2 = dTau321_dtau2 * dsv - dTau32_dtau2 * P0

      # derivative wrt tau3
      dTau32_dtau3 = dTau3_dtau3 * Tau2
      dTau321_dtau3 = dTau32_dtau3 * Tau1
      ddorg_dtau3 = dTau321_dtau3 * dsv - dTau32_dtau3 * P0

      # Now derivatives of the direction d1, where
      # d1 = (Tau321 * (Px - P0)).normalize()
      # For calc of derivatives ignore the normalize(), which should
      # be unnecessary anyway as Px - P0 is a unit vector and Tau321 a
      # pure rotation.

      # derivative wrt dist
      # dPx_ddist = dist.axis; dP0_ddist = dist.axis, so these cancel
      dd1_ddist = matrix.col((0., 0., 0.))

      # derivative wrt shift1
      dd1_dshift1 = matrix.col((0., 0., 0.))

      # derivative wrt shift2
      dd1_dshift2 = matrix.col((0., 0., 0.))

      # derivative wrt tau1
      dd1_dtau1 = dTau321_dtau1 * (Px - P0)

      # derivative wrt tau2
      dd1_dtau2 = dTau321_dtau2 * (Px - P0)

      # derivative wrt tau3
      dd1_dtau3 = dTau321_dtau3 * (Px - P0)

      # Derivatives of the direction d2, where
      # d2 = (Tau321 * (Py - P0)).normalize()

      # derivative wrt dist
      dd2_ddist = matrix.col((0., 0., 0.))

      # derivative wrt shift1
      dd2_dshift1 = matrix.col((0., 0., 0.))

      # derivative wrt shift2
      dd2_dshift2 = matrix.col((0., 0., 0.))

      # derivative wrt tau1
      dd2_dtau1 = dTau321_dtau1 * (Py - P0)

      # derivative wrt tau2
      dd2_dtau2 = dTau321_dtau2 * (Py - P0)

      # derivative wrt tau3
      dd2_dtau3 = dTau321_dtau3 * (Py - P0)

      # Derivatives of the direction dn, where
      # dn = d1.cross(d2).normalize()

      # derivative wrt dist
      ddn_ddist = matrix.col((0., 0., 0.))

      # derivative wrt shift1
      ddn_dshift1 = matrix.col((0., 0., 0.))

      # derivative wrt shift2
      ddn_dshift2 = matrix.col((0., 0., 0.))

      # derivative wrt tau1. Product rule for cross product applies
      ddn_dtau1 = dd1_dtau1.cross(d2) + d1.cross(dd2_dtau1)

      # derivative wrt tau2
      ddn_dtau2 = dd1_dtau2.cross(d2) + d1.cross(dd2_dtau2)

      # derivative wrt tau3
      ddn_dtau3 = dd1_dtau3.cross(d2) + d1.cross(dd2_dtau3)

      # calculate derivatives of the attached Panel matrices
      # ====================================================
      for (panel_id, offset, dir1_new_basis, dir2_new_basis) in \
                              zip(pnl_ids, offsets, dir1s, dir2s):

        # Panel origin, which is calculated by:
        # o = dorg + offset[0] * d1 + offset[1] * d2 + offset[2] * dn

        # derivative wrt dist. NB only ddorg_ddist is not null! The other
        # elements are left here to aid understanding, but should be removed
        # when this class is ported to C++ for speed.
        do_ddist = ddorg_ddist + offset[0] * dd1_ddist + \
                                 offset[1] * dd2_ddist + \
                                 offset[2] * ddn_ddist

        # derivative wrt shift1. NB only ddorg_dshift1 is non-null.
        do_dshift1 = ddorg_dshift1 + offset[0] * dd1_dshift1 + \
                                     offset[1] * dd2_dshift1 + \
                                     offset[2] * ddn_dshift1

        # derivative wrt shift2. NB only ddorg_dshift2 is non-null.
        do_dshift2 = ddorg_dshift2 + offset[0] * dd1_dshift2 + \
                                     offset[1] * dd2_dshift2 + \
                                     offset[2] * ddn_dshift2

        # derivative wrt tau1
        do_dtau1 = ddorg_dtau1 + offset[0] * dd1_dtau1 + \
                                 offset[1] * dd2_dtau1 + \
                                 offset[2] * ddn_dtau1

        # derivative wrt tau2
        do_dtau2 = ddorg_dtau2 + offset[0] * dd1_dtau2 + \
                                 offset[1] * dd2_dtau2 + \
                                 offset[2] * ddn_dtau2

        # derivative wrt tau3
        do_dtau3 = ddorg_dtau3 + offset[0] * dd1_dtau3 + \
                                 offset[1] * dd2_dtau3 + \
                                 offset[2] * ddn_dtau3

        # Panel dir1:
        # dir1 = dir1_new_basis[0] * d1 + dir1_new_basis[1] * d2 +
        #        dir1_new_basis[2] * dn

        # derivative wrt dist. NB These are all null.
        ddir1_ddist = dir1_new_basis[0] * dd1_ddist + \
                      dir1_new_basis[1] * dd2_ddist + \
                      dir1_new_basis[2] * ddn_ddist

        # derivative wrt shift1. NB These are all null.
        ddir1_dshift1 = dir1_new_basis[0] * dd1_dshift1 + \
                        dir1_new_basis[1] * dd2_dshift1 + \
                        dir1_new_basis[2] * ddn_dshift1

        # derivative wrt shift2. NB These are all null.
        ddir1_dshift2 = dir1_new_basis[0] * dd1_dshift2 + \
                        dir1_new_basis[1] * dd2_dshift2 + \
                        dir1_new_basis[2] * ddn_dshift2

        # derivative wrt tau1
        ddir1_dtau1 = dir1_new_basis[0] * dd1_dtau1 + \
                      dir1_new_basis[1] * dd2_dtau1 + \
                      dir1_new_basis[2] * ddn_dtau1

        # derivative wrt tau2
        ddir1_dtau2 = dir1_new_basis[0] * dd1_dtau2 + \
                      dir1_new_basis[1] * dd2_dtau2 + \
                      dir1_new_basis[2] * ddn_dtau2

        # derivative wrt tau3
        ddir1_dtau3 = dir1_new_basis[0] * dd1_dtau3 + \
                      dir1_new_basis[1] * dd2_dtau3 + \
                      dir1_new_basis[2] * ddn_dtau3

        # Panel dir2:
        # dir2 = dir2_new_basis[0] * d1 + dir2_new_basis[1] * d2 +
        #        dir2_new_basis[2] * dn

        # derivative wrt dist. NB These are all null.
        ddir2_ddist = dir2_new_basis[0] * dd1_ddist + \
                      dir2_new_basis[1] * dd2_ddist + \
                      dir2_new_basis[2] * ddn_ddist

        # derivative wrt shift1. NB These are all null.
        ddir2_dshift1 = dir2_new_basis[0] * dd1_dshift1 + \
                        dir2_new_basis[1] * dd2_dshift1 + \
                        dir2_new_basis[2] * ddn_dshift1

        # derivative wrt shift2. NB These are all null.
        ddir2_dshift2 = dir2_new_basis[0] * dd1_dshift2 + \
                        dir2_new_basis[1] * dd2_dshift2 + \
                        dir2_new_basis[2] * ddn_dshift2

        # derivative wrt tau1
        ddir2_dtau1 = dir2_new_basis[0] * dd1_dtau1 + \
                      dir2_new_basis[1] * dd2_dtau1 + \
                      dir2_new_basis[2] * ddn_dtau1

        # derivative wrt tau2
        ddir2_dtau2 = dir2_new_basis[0] * dd1_dtau2 + \
                      dir2_new_basis[1] * dd2_dtau2 + \
                      dir2_new_basis[2] * ddn_dtau2

        # derivative wrt tau3
        ddir2_dtau3 = dir2_new_basis[0] * dd1_dtau3 + \
                      dir2_new_basis[1] * dd2_dtau3 + \
                      dir2_new_basis[2] * ddn_dtau3

        # combine these vectors together into derivatives of the panel
        # matrix d and store them, converting angles back to mrad

        i = igp * 6

        # derivative wrt dist
        self._multi_state_derivatives[panel_id][i] = \
          matrix.sqr(ddir1_ddist.elems + ddir2_ddist.elems +
                     do_ddist.elems).transpose()

        # derivative wrt shift1
        self._multi_state_derivatives[panel_id][i+1] = \
          matrix.sqr(ddir1_dshift1.elems +
                     ddir2_dshift1.elems + do_dshift1.elems).transpose()

        # derivative wrt shift2
        self._multi_state_derivatives[panel_id][i+2] = \
          matrix.sqr(ddir1_dshift2.elems +
                    ddir2_dshift2.elems + do_dshift2.elems).transpose()

        # derivative wrt tau1
        self._multi_state_derivatives[panel_id][i+3] = \
          matrix.sqr(ddir1_dtau1.elems +
                    ddir2_dtau1.elems + do_dtau1.elems).transpose() / 1000.

        # derivative wrt tau2
        self._multi_state_derivatives[panel_id][i+4] = \
          matrix.sqr(ddir1_dtau2.elems +
                    ddir2_dtau2.elems + do_dtau2.elems).transpose() / 1000.

        # derivative wrt tau3
        self._multi_state_derivatives[panel_id][i+5] = \
          matrix.sqr(ddir1_dtau3.elems +
                    ddir2_dtau3.elems + do_dtau3.elems).transpose() / 1000.

    return
Exemple #12
0
  def _compose_core(self, dist, shift1, shift2, tau1, tau2, tau3):

    # extract items from the initial state
    id1 = self._initial_state['d1']
    id2 = self._initial_state['d2']
    ioffset = self._initial_state['offset']

    # convert angles to radians
    tau1rad = tau1.value / 1000.
    tau2rad = tau2.value / 1000.
    tau3rad = tau3.value / 1000.

    # compose rotation matrices and their first order derivatives
    Tau1 = (tau1.axis).axis_and_angle_as_r3_rotation_matrix(tau1rad,
                                                            deg=False)
    dTau1_dtau1 = dR_from_axis_and_angle(tau1.axis, tau1rad, deg=False)

    Tau2 = (tau2.axis).axis_and_angle_as_r3_rotation_matrix(tau2rad,
                                                            deg=False)
    dTau2_dtau2 = dR_from_axis_and_angle(tau2.axis, tau2rad, deg=False)

    Tau3 = (tau3.axis).axis_and_angle_as_r3_rotation_matrix(tau3rad,
                                                            deg=False)
    dTau3_dtau3 = dR_from_axis_and_angle(tau3.axis, tau3rad, deg=False)

    Tau32 = Tau3 * Tau2
    Tau321 = Tau32 * Tau1

    # Compose new state
    # =================
    # First the frame positioned at a distance from the lab origin
    P0 = dist.value * dist.axis # distance along initial detector normal
    Px = P0 + id1 # point at the end of d1 in lab frame
    Py = P0 + id2 # point at the end of d2 in lab frame

    # detector shift vector
    dsv = P0 + shift1.value * shift1.axis + shift2.value * shift2.axis

    # compose dorg point
    dorg = Tau321 * dsv - Tau32 * P0 + P0

    # compose d1, d2 and dn and ensure frame remains orthonormal.
    d1 = (Tau321 * (Px - P0)).normalize()
    d2 = (Tau321 * (Py - P0)).normalize()
    dn = d1.cross(d2).normalize()
    # NB dn not actually used in this simple model; calculation left
    # here as a reminder for future extension
    d2 = dn.cross(d1)

    # compose new sensor origin
    o = dorg + ioffset[0] * d1 + ioffset[1] * d2

    # keep the new state for return
    new_state = {'d1':d1,
                 'd2':d2,
                 'origin':o}

    # calculate derivatives of the state wrt parameters
    # =================================================
    # Start with the dorg vector, where
    # dorg = Tau321 * dsv - Tau32 * P0 + P0

    # derivative wrt dist
    dP0_ddist = dist.axis
    ddsv_ddist = dP0_ddist
    ddorg_ddist = Tau321 * ddsv_ddist - Tau32 * dP0_ddist + \
                  dP0_ddist

    # derivative wrt shift1
    ddsv_dshift1 = shift1.axis
    ddorg_dshift1 = Tau321 * ddsv_dshift1

    # derivative wrt shift2
    ddsv_dshift2 = shift2.axis
    ddorg_dshift2 = Tau321 * ddsv_dshift2

    # derivative wrt tau1
    dTau321_dtau1 = Tau32 * dTau1_dtau1
    ddorg_dtau1 = dTau321_dtau1 * dsv

    # derivative wrt tau2
    dTau32_dtau2 = Tau3 * dTau2_dtau2
    dTau321_dtau2 = dTau32_dtau2 * Tau1
    ddorg_dtau2 = dTau321_dtau2 * dsv - dTau32_dtau2 * P0

    # derivative wrt tau3
    dTau32_dtau3 = dTau3_dtau3 * Tau2
    dTau321_dtau3 = dTau32_dtau3 * Tau1
    ddorg_dtau3 = dTau321_dtau3 * dsv - dTau32_dtau3 * P0

    # Now derivatives of the direction d1, where
    # d1 = (Tau321 * (Px - P0)).normalize()
    # For calc of derivatives ignore the normalize(), which should
    # be unnecessary anyway as Px - P0 is a unit vector and Tau321 a
    # pure rotation.

    # derivative wrt dist
    # dPx_ddist = dist.axis; dP0_ddist = dist.axis, so these cancel
    dd1_ddist = matrix.col((0., 0., 0.))

    # derivative wrt shift1
    dd1_dshift1 = matrix.col((0., 0., 0.))

    # derivative wrt shift2
    dd1_dshift2 = matrix.col((0., 0., 0.))

    # derivative wrt tau1
    dd1_dtau1 = dTau321_dtau1 * (Px - P0)

    # derivative wrt tau2
    dd1_dtau2 = dTau321_dtau2 * (Px - P0)

    # derivative wrt tau3
    dd1_dtau3 = dTau321_dtau3 * (Px - P0)

    # Derivatives of the direction d2, where
    # d2 = (Tau321 * (Py - P0)).normalize()

    # derivative wrt dist
    dd2_ddist = matrix.col((0., 0., 0.))

    # derivative wrt shift1
    dd2_dshift1 = matrix.col((0., 0., 0.))

    # derivative wrt shift2
    dd2_dshift2 = matrix.col((0., 0., 0.))

    # derivative wrt tau1
    dd2_dtau1 = dTau321_dtau1 * (Py - P0)

    # derivative wrt tau2
    dd2_dtau2 = dTau321_dtau2 * (Py - P0)

    # derivative wrt tau3
    dd2_dtau3 = dTau321_dtau3 * (Py - P0)

    # Derivatives of the direction dn, where
    # dn = d1.cross(d2).normalize()

    # derivative wrt dist
    ddn_ddist = matrix.col((0., 0., 0.))

    # derivative wrt shift1
    ddn_dshift1 = matrix.col((0., 0., 0.))

    # derivative wrt shift2
    ddn_dshift2 = matrix.col((0., 0., 0.))

    # derivative wrt tau1. Product rule for cross product applies
    ddn_dtau1 = dd1_dtau1.cross(d2) + d1.cross(dd2_dtau1)

    # derivative wrt tau2
    ddn_dtau2 = dd1_dtau2.cross(d2) + d1.cross(dd2_dtau2)

    # derivative wrt tau3
    ddn_dtau3 = dd1_dtau3.cross(d2) + d1.cross(dd2_dtau3)

    # calculate derivatives of the attached sensor matrix
    # ===================================================
    # sensor origin:
    # o = dorg + ioffset[0] * d1 + ioffset[1] * d2

    # derivative wrt dist
    do_ddist = ddorg_ddist + ioffset[0] * dd1_ddist + \
               ioffset[1] * dd2_ddist

    # derivative wrt shift1
    do_dshift1 = ddorg_dshift1 + ioffset[0] * dd1_dshift1 + \
                 ioffset[1] * dd2_dshift1

    # derivative wrt shift2
    do_dshift2 = ddorg_dshift2 + ioffset[0] * dd1_dshift2 + \
                 ioffset[1] * dd2_dshift2

    # derivative wrt tau1
    do_dtau1 = ddorg_dtau1 + ioffset[0] * dd1_dtau1 + \
               ioffset[1] * dd2_dtau1

    # derivative wrt tau2
    do_dtau2 = ddorg_dtau2 + ioffset[0] * dd1_dtau2 + \
               ioffset[1] * dd2_dtau2

    # derivative wrt tau3
    do_dtau3 = ddorg_dtau3 + ioffset[0] * dd1_dtau3 + \
               ioffset[1] * dd2_dtau3

    # combine these vectors together into derivatives of the sensor
    # matrix d, converting angles back to mrad
    dd_dval = []

    # derivative wrt dist
    dd_dval.append(matrix.sqr(dd1_ddist.elems +
                              dd2_ddist.elems +
                              do_ddist.elems).transpose())

    # derivative wrt shift1
    dd_dval.append(matrix.sqr(dd1_dshift1.elems +
                              dd2_dshift1.elems +
                              do_dshift1.elems).transpose())

    # derivative wrt shift2
    dd_dval.append(matrix.sqr(dd1_dshift2.elems +
                              dd2_dshift2.elems +
                              do_dshift2.elems).transpose())

    # derivative wrt tau1
    dd_dval.append(matrix.sqr(dd1_dtau1.elems +
                              dd2_dtau1.elems +
                              do_dtau1.elems).transpose() / 1000.)

    # derivative wrt tau2
    dd_dval.append(matrix.sqr(dd1_dtau2.elems +
                              dd2_dtau2.elems +
                              do_dtau2.elems).transpose() / 1000.)

    # derivative wrt tau3
    dd_dval.append(matrix.sqr(dd1_dtau3.elems +
                              dd2_dtau3.elems +
                              do_dtau3.elems).transpose() / 1000.)

    return new_state, dd_dval
Exemple #13
0
  def compose(self):

    # extract items from the initial state
    id1 = self._initial_state['d1']
    id2 = self._initial_state['d2']
    idn = self._initial_state['dn']

    # extract parameters from the internal list
    dist, shift1, shift2, tau1, tau2, tau3 = self._param

    # Extract the detector model
    detector = self._model

    # convert angles to radians
    tau1rad = tau1.value / 1000.
    tau2rad = tau2.value / 1000.
    tau3rad = tau3.value / 1000.

    # compose rotation matrices and their first order derivatives
    Tau1 = (tau1.axis).axis_and_angle_as_r3_rotation_matrix(tau1rad,
                                                            deg=False)
    dTau1_dtau1 = dR_from_axis_and_angle(tau1.axis, tau1rad, deg=False)

    Tau2 = (tau2.axis).axis_and_angle_as_r3_rotation_matrix(tau2rad,
                                                            deg=False)
    dTau2_dtau2 = dR_from_axis_and_angle(tau2.axis, tau2rad, deg=False)

    Tau3 = (tau3.axis).axis_and_angle_as_r3_rotation_matrix(tau3rad,
                                                            deg=False)
    dTau3_dtau3 = dR_from_axis_and_angle(tau3.axis, tau3rad, deg=False)

    Tau32 = Tau3 * Tau2
    Tau321 = Tau32 * Tau1

    # Compose new state
    # =================
    # First the frame positioned at a distance from the lab origin
    P0 = dist.value * dist.axis # distance along initial detector normal
    Px = P0 + id1 # point at the end of d1 in lab frame
    Py = P0 + id2 # point at the end of d2 in lab frame

    # detector shift vector
    dsv = P0 + shift1.value * shift1.axis + shift2.value * shift2.axis

    # compose dorg point
    dorg = Tau321 * dsv - Tau32 * P0 + P0

    # compose new d1, d2 and dn and ensure frame remains orthonormal.
    d1 = (Tau321 * (Px - P0)).normalize()
    d2 = (Tau321 * (Py - P0)).normalize()
    dn = d1.cross(d2).normalize()
    d2 = dn.cross(d1)

    # compose new Panel origins
    origins = [dorg + offset[0] * d1 + \
                      offset[1] * d2 + \
                      offset[2] * dn for offset in self._offsets]

    # compose new Panel directions
    dir1s = [vec[0] * d1 + \
             vec[1] * d2 + \
             vec[2] * dn for vec in self._dir1s]
    dir2s = [vec[0] * d1 + \
             vec[1] * d2 + \
             vec[2] * dn for vec in self._dir2s]

    # now update the panels with their new position and orientation.
    for p, dir1, dir2, org in zip(detector, dir1s, dir2s, origins):

      p.set_frame(dir1, dir2, org)

    # calculate derivatives of the state wrt parameters
    # =================================================
    # Start with the dorg vector, where
    # dorg = Tau321 * dsv - Tau32 * P0 + P0

    # derivative wrt dist
    dP0_ddist = dist.axis
    ddsv_ddist = dP0_ddist
    ddorg_ddist = Tau321 * ddsv_ddist - Tau32 * dP0_ddist + \
                  dP0_ddist

    # derivative wrt shift1
    ddsv_dshift1 = shift1.axis
    ddorg_dshift1 = Tau321 * ddsv_dshift1

    # derivative wrt shift2
    ddsv_dshift2 = shift2.axis
    ddorg_dshift2 = Tau321 * ddsv_dshift2

    # derivative wrt tau1
    dTau321_dtau1 = Tau32 * dTau1_dtau1
    ddorg_dtau1 = dTau321_dtau1 * dsv

    # derivative wrt tau2
    dTau32_dtau2 = Tau3 * dTau2_dtau2
    dTau321_dtau2 = dTau32_dtau2 * Tau1
    ddorg_dtau2 = dTau321_dtau2 * dsv - dTau32_dtau2 * P0

    # derivative wrt tau3
    dTau32_dtau3 = dTau3_dtau3 * Tau2
    dTau321_dtau3 = dTau32_dtau3 * Tau1
    ddorg_dtau3 = dTau321_dtau3 * dsv - dTau32_dtau3 * P0

    # Now derivatives of the direction d1, where
    # d1 = (Tau321 * (Px - P0)).normalize()
    # For calc of derivatives ignore the normalize(), which should
    # be unnecessary anyway as Px - P0 is a unit vector and Tau321 a
    # pure rotation.

    # derivative wrt dist
    # dPx_ddist = dist.axis; dP0_ddist = dist.axis, so these cancel
    dd1_ddist = matrix.col((0., 0., 0.))

    # derivative wrt shift1
    dd1_dshift1 = matrix.col((0., 0., 0.))

    # derivative wrt shift2
    dd1_dshift2 = matrix.col((0., 0., 0.))

    # derivative wrt tau1
    dd1_dtau1 = dTau321_dtau1 * (Px - P0)

    # derivative wrt tau2
    dd1_dtau2 = dTau321_dtau2 * (Px - P0)

    # derivative wrt tau3
    dd1_dtau3 = dTau321_dtau3 * (Px - P0)

    # Derivatives of the direction d2, where
    # d2 = (Tau321 * (Py - P0)).normalize()

    # derivative wrt dist
    dd2_ddist = matrix.col((0., 0., 0.))

    # derivative wrt shift1
    dd2_dshift1 = matrix.col((0., 0., 0.))

    # derivative wrt shift2
    dd2_dshift2 = matrix.col((0., 0., 0.))

    # derivative wrt tau1
    dd2_dtau1 = dTau321_dtau1 * (Py - P0)

    # derivative wrt tau2
    dd2_dtau2 = dTau321_dtau2 * (Py - P0)

    # derivative wrt tau3
    dd2_dtau3 = dTau321_dtau3 * (Py - P0)

    # Derivatives of the direction dn, where
    # dn = d1.cross(d2).normalize()

    # derivative wrt dist
    ddn_ddist = matrix.col((0., 0., 0.))

    # derivative wrt shift1
    ddn_dshift1 = matrix.col((0., 0., 0.))

    # derivative wrt shift2
    ddn_dshift2 = matrix.col((0., 0., 0.))

    # derivative wrt tau1. Product rule for cross product applies
    ddn_dtau1 = dd1_dtau1.cross(d2) + d1.cross(dd2_dtau1)

    # derivative wrt tau2
    ddn_dtau2 = dd1_dtau2.cross(d2) + d1.cross(dd2_dtau2)

    # derivative wrt tau3
    ddn_dtau3 = dd1_dtau3.cross(d2) + d1.cross(dd2_dtau3)

    # reset stored derivatives
    for i in range(len(detector)):
      self._multi_state_derivatives[i] = [None] * len(self._dstate_dp)

    # calculate derivatives of the attached Panel matrices
    # ====================================================
    for panel_id, (offset, dir1_new_basis, dir2_new_basis) in enumerate(
                            zip(self._offsets, self._dir1s, self._dir2s)):

      # Panel origin:
      # o = dorg + offset[0] * d1 + offset[1] * d2 + offset[2] * dn

      # derivative wrt dist. NB only ddorg_ddist is not null! The other
      # elements are left here to aid understanding, but should be removed
      # when this class is ported to C++ for speed.
      do_ddist = ddorg_ddist + offset[0] * dd1_ddist + \
                               offset[1] * dd2_ddist + \
                               offset[2] * ddn_ddist

      # derivative wrt shift1. NB only ddorg_dshift1 is non-null.
      do_dshift1 = ddorg_dshift1 + offset[0] * dd1_dshift1 + \
                                   offset[1] * dd2_dshift1 + \
                                   offset[2] * ddn_dshift1

      # derivative wrt shift2. NB only ddorg_dshift2 is non-null.
      do_dshift2 = ddorg_dshift2 + offset[0] * dd1_dshift2 + \
                                   offset[1] * dd2_dshift2 + \
                                   offset[2] * ddn_dshift2

      # derivative wrt tau1
      do_dtau1 = ddorg_dtau1 + offset[0] * dd1_dtau1 + \
                               offset[1] * dd2_dtau1 + \
                               offset[2] * ddn_dtau1

      # derivative wrt tau2
      do_dtau2 = ddorg_dtau2 + offset[0] * dd1_dtau2 + \
                               offset[1] * dd2_dtau2 + \
                               offset[2] * ddn_dtau2

      # derivative wrt tau3
      do_dtau3 = ddorg_dtau3 + offset[0] * dd1_dtau3 + \
                               offset[1] * dd2_dtau3 + \
                               offset[2] * ddn_dtau3

      # Panel dir1:
      # dir1 = dir1_new_basis[0] * d1 + dir1_new_basis[1] * d2 +
      #        dir1_new_basis[2] * dn

      # derivative wrt dist. NB These are all null.
      ddir1_ddist = dir1_new_basis[0] * dd1_ddist + \
                    dir1_new_basis[1] * dd2_ddist + \
                    dir1_new_basis[2] * ddn_ddist

      # derivative wrt shift1. NB These are all null.
      ddir1_dshift1 = dir1_new_basis[0] * dd1_dshift1 + \
                      dir1_new_basis[1] * dd2_dshift1 + \
                      dir1_new_basis[2] * ddn_dshift1

      # derivative wrt shift2. NB These are all null.
      ddir1_dshift2 = dir1_new_basis[0] * dd1_dshift2 + \
                      dir1_new_basis[1] * dd2_dshift2 + \
                      dir1_new_basis[2] * ddn_dshift2

      # derivative wrt tau1
      ddir1_dtau1 = dir1_new_basis[0] * dd1_dtau1 + \
                    dir1_new_basis[1] * dd2_dtau1 + \
                    dir1_new_basis[2] * ddn_dtau1

      # derivative wrt tau2
      ddir1_dtau2 = dir1_new_basis[0] * dd1_dtau2 + \
                    dir1_new_basis[1] * dd2_dtau2 + \
                    dir1_new_basis[2] * ddn_dtau2

      # derivative wrt tau3
      ddir1_dtau3 = dir1_new_basis[0] * dd1_dtau3 + \
                    dir1_new_basis[1] * dd2_dtau3 + \
                    dir1_new_basis[2] * ddn_dtau3

      # Panel dir2:
      # dir2 = dir2_new_basis[0] * d1 + dir2_new_basis[1] * d2 +
      #        dir2_new_basis[2] * dn

      # derivative wrt dist. NB These are all null.
      ddir2_ddist = dir2_new_basis[0] * dd1_ddist + \
                    dir2_new_basis[1] * dd2_ddist + \
                    dir2_new_basis[2] * ddn_ddist

      # derivative wrt shift1. NB These are all null.
      ddir2_dshift1 = dir2_new_basis[0] * dd1_dshift1 + \
                      dir2_new_basis[1] * dd2_dshift1 + \
                      dir2_new_basis[2] * ddn_dshift1

      # derivative wrt shift2. NB These are all null.
      ddir2_dshift2 = dir2_new_basis[0] * dd1_dshift2 + \
                      dir2_new_basis[1] * dd2_dshift2 + \
                      dir2_new_basis[2] * ddn_dshift2

      # derivative wrt tau1
      ddir2_dtau1 = dir2_new_basis[0] * dd1_dtau1 + \
                    dir2_new_basis[1] * dd2_dtau1 + \
                    dir2_new_basis[2] * ddn_dtau1

      # derivative wrt tau2
      ddir2_dtau2 = dir2_new_basis[0] * dd1_dtau2 + \
                    dir2_new_basis[1] * dd2_dtau2 + \
                    dir2_new_basis[2] * ddn_dtau2

      # derivative wrt tau3
      ddir2_dtau3 = dir2_new_basis[0] * dd1_dtau3 + \
                    dir2_new_basis[1] * dd2_dtau3 + \
                    dir2_new_basis[2] * ddn_dtau3

      # combine these vectors together into derivatives of the panel
      # matrix d and store them, converting angles back to mrad
      self._multi_state_derivatives[panel_id] = \
        [matrix.sqr(ddir1_ddist.elems + ddir2_ddist.elems + do_ddist.elems).transpose(),
         matrix.sqr(ddir1_dshift1.elems + ddir2_dshift1.elems + do_dshift1.elems).transpose(),
         matrix.sqr(ddir1_dshift2.elems + ddir2_dshift2.elems + do_dshift2.elems).transpose(),
         matrix.sqr(ddir1_dtau1.elems + ddir2_dtau1.elems + do_dtau1.elems).transpose() / 1000.,
         matrix.sqr(ddir1_dtau2.elems + ddir2_dtau2.elems + do_dtau2.elems).transpose() / 1000.,
         matrix.sqr(ddir1_dtau3.elems + ddir2_dtau3.elems + do_dtau3.elems).transpose() / 1000.]

    return