def __init__(self, value, num_samples = 5, axis = None, ptype = None,
               name = "ScanVaryingParameterSet"):

    assert num_samples >= 2 #otherwise use scan-independent parameterisation

    value = [value] * num_samples
    self._name_stem = name
    name = [e + "_sample%d" % i for i, e in enumerate(
      [self._name_stem] * num_samples)]

    Parameter.__init__(self, value, axis, ptype, name)

    self._esd = [None] * num_samples
    self._num_samples = num_samples

    return
  def __init__(self, value, num_samples = 5, axis = None, ptype = None,
               name = "ScanVaryingParameterSet"):

    assert num_samples >= 2 #otherwise use scan-independent parameterisation

    value = [value] * num_samples
    self._name_stem = name
    name = [e + "_sample%d" % i for i, e in enumerate(
      [self._name_stem] * num_samples)]

    Parameter.__init__(self, value, axis, ptype, name)

    self._esd = [None] * num_samples
    self._num_samples = num_samples

    return
    def __init__(self, detector, experiment_ids=None, level=0):
        """Initialise the DetectorParameterisationHierarchical object

        Args:
            detector: A dxtbx Detector object to be parameterised.
            experiment_ids (list): The experiment IDs affected by this
                parameterisation. Defaults to None, which is replaced by [0].
            level (int): Select level of the detector hierarchy to determine panel
                groupings that are treated as separate rigid blocks.
        """

        if experiment_ids is None:
            experiment_ids = [0]

        try:
            h = detector.hierarchy()
        except AttributeError:
            print("This detector does not have a hierarchy")
            raise

        # list the panel groups at the chosen level
        try:
            self._groups = get_panel_groups_at_depth(h, level)
        except AttributeError:
            print("Cannot access the hierarchy at the depth level={}".format(
                level))
            raise

        # collect the panel ids for each Panel within the groups
        panels = list(detector)
        self._panel_ids_by_group = [
            get_panel_ids_at_root(panels, g) for g in self._groups
        ]

        p_list = []
        self._group_ids_by_parameter = []
        istate = []
        self._offsets = []
        self._dir1s = []
        self._dir2s = []

        # loop over the groups, collecting initial parameters and states
        for igp, pnl_ids in enumerate(self._panel_ids_by_group):

            panel_centres_in_lab_frame = []
            for i in pnl_ids:
                pnl = detector[i]
                im_size = pnl.get_image_size_mm()
                cntr = (matrix.col(pnl.get_origin()) +
                        0.5 * matrix.col(pnl.get_fast_axis()) * im_size[0] +
                        0.5 * matrix.col(pnl.get_slow_axis()) * im_size[1])
                panel_centres_in_lab_frame.append(cntr)

            # get some vectors we need from the group
            go = matrix.col(self._groups[igp].get_origin())
            d1 = matrix.col(self._groups[igp].get_fast_axis())
            d2 = matrix.col(self._groups[igp].get_slow_axis())
            dn = matrix.col(self._groups[igp].get_normal())

            # we choose the dorg vector for this group to terminate on the group's
            # frame, at a point that we consider close to the centre of the group of
            # panels. This point is defined by taking the 3D centroid of the panel
            # centres then projecting that point onto the group frame.
            centroid = reduce(
                lambda a, b: a + b,
                panel_centres_in_lab_frame) / len(panel_centres_in_lab_frame)
            try:
                gp_centroid = matrix.col(
                    self._groups[igp].get_bidirectional_ray_intersection(
                        centroid))
                dorg = go + gp_centroid[0] * d1 + gp_centroid[1] * d2
            except RuntimeError:  # workaround for a group frame that passes through
                # the origin
                dorg = matrix.col((0.0, 0.0, 0.0))

            # The offset between the end of the dorg vector and
            # each Panel origin is a coordinate matrix with elements in the basis d1,
            # d2, dn. We need also each Panel's plane directions dir1 and dir2 in
            # terms of d1, d2 and dn.
            offsets, dir1s, dir2s = [], [], []
            # FIXME these dot products would be more efficiently done using a change of
            # basis matrix instead
            for p in [detector[i] for i in pnl_ids]:
                offset = matrix.col(p.get_origin()) - dorg
                offsets.append(
                    matrix.col(
                        (offset.dot(d1), offset.dot(d2), offset.dot(dn))))
                dir1 = matrix.col(p.get_fast_axis())
                dir1_new_basis = matrix.col(
                    (dir1.dot(d1), dir1.dot(d2), dir1.dot(dn)))
                dir1s.append(dir1_new_basis)
                dir2 = matrix.col(p.get_slow_axis())
                dir2_new_basis = matrix.col(
                    (dir2.dot(d1), dir2.dot(d2), dir2.dot(dn)))
                dir2s.append(dir2_new_basis)

            # The offsets and directions in the d1, d2, dn basis are fixed
            # quantities, not dependent on parameter values. Keep these as separate
            # sub-lists for each group
            self._offsets.append(offsets)
            self._dir1s.append(dir1s)
            self._dir2s.append(dir2s)

            # Set up the initial state for this group. This is the basis d1, d2, dn,
            # plus the offset locating the origin of the initial group frame
            gp_offset = go - dorg  # lab frame basis
            # FIXME another set of dot products better done by a matrix multiplication
            gp_offset = matrix.col((gp_offset.dot(d1), gp_offset.dot(d2),
                                    gp_offset.dot(dn)))  # d1,d2,dn basis
            istate.append({
                "d1": d1,
                "d2": d2,
                "dn": dn,
                "gp_offset": gp_offset
            })

            # set up the parameters.
            # distance from lab origin to ref_panel plane along its normal,
            # in initial orientation
            distance = self._groups[igp].get_directed_distance()
            dist = Parameter(distance, dn, "length (mm)",
                             "Group{}Dist".format(igp + 1))

            # shift in the detector model plane to locate dorg, in initial
            # orientation
            shift = dorg - dn * distance
            shift1 = Parameter(shift.dot(d1), d1, "length (mm)",
                               "Group{}Shift1".format(igp + 1))
            shift2 = Parameter(shift.dot(d2), d2, "length (mm)",
                               "Group{}Shift2".format(igp + 1))

            # rotations of the plane through its origin about:
            # 1) axis normal to initial orientation
            # 2) d1 axis of initial orientation
            # 3) d2 axis of initial orientation
            tau1 = Parameter(0, dn, "angle (mrad)",
                             "Group{}Tau1".format(igp + 1))
            tau2 = Parameter(0, d1, "angle (mrad)",
                             "Group{}Tau2".format(igp + 1))
            tau3 = Parameter(0, d2, "angle (mrad)",
                             "Group{}Tau3".format(igp + 1))

            # extend the parameter list with those pertaining to this group
            p_list.extend([dist, shift1, shift2, tau1, tau2, tau3])
            self._group_ids_by_parameter.extend([igp] * 6)

        # set up the base class
        ModelParameterisation.__init__(
            self,
            detector,
            istate,
            p_list,
            experiment_ids=experiment_ids,
            is_multi_state=True,
        )

        # call compose to calculate all the derivatives
        self.compose()
    def __init__(self, detector, beam, experiment_ids=None):
        """Initialise the DetectorParameterisationMultiPanel object

        Args:
            detector: A dxtbx Detector object to be parameterised.
            beam: An dxtbx beam object used to calculate the closest panel.
            experiment_ids (list): The experiment IDs affected by this
                parameterisation. Defaults to None, which is replaced by [0].
        """
        # The state of each Panel in the detector model is its matrix
        # d = (d1|d2|d0). We need to define a new coordinate system rigidly
        # attached to the detector model in which to express the
        # parameterisation and compose each of the Panel states.
        #
        # We define:
        #
        # * a vector 'dorg' locating a point in laboratory space that moves with
        #   the rigid body of the detector and thus is fixed wrt each of the
        #   Panels.
        # * A pair of orthogonal unit directions 'd1' and 'd2' forming a plane
        #   with its origin at the end of the vector dorg.
        # * a third unit direction 'dn', orthogonal to both 'd1' & 'd2'.
        # * offsets to locate the origin of each panel frame from the
        #   tip of the dorg vector, in terms of the coordinate system
        #   formed by d1, d2 and dn.
        #
        # Held separately in attribute 'models' are:
        # * references to detector objects contained in this model

        # set up the initial state of the detector model from the
        # orientation of whichever Panel has its centre most closely
        # located to the direct beam intersection. Call this 'mid_panel'

        if experiment_ids is None:
            experiment_ids = [0]
        beam_centres = [
            matrix.col(p.get_beam_centre(beam.get_unit_s0())) for p in detector
        ]
        panel_centres = [
            0.5 * matrix.col(p.get_image_size_mm()) for p in detector
        ]
        beam_to_centres = [(a - b).length()
                           for a, b in zip(beam_centres, panel_centres)]
        mid_panel_id = beam_to_centres.index(min(beam_to_centres))
        mid_panel = detector[mid_panel_id]

        # get some vectors we need from the mid_panel
        so = matrix.col(mid_panel.get_origin())
        d1 = matrix.col(mid_panel.get_fast_axis())
        d2 = matrix.col(mid_panel.get_slow_axis())
        dn = matrix.col(mid_panel.get_normal())

        # we choose the dorg vector to terminate in the centre of the mid_panel,
        # and the offset between the end of the dorg vector and each Panel
        # origin is a coordinate matrix with elements in the basis d1, d2, dn.
        # We need also each Panel's plane directions dir1 and dir2 in terms of
        # d1, d2 and dn.
        mid_panel_centre = panel_centres[mid_panel_id]
        dorg = so + mid_panel_centre[0] * d1 + mid_panel_centre[1] * d2

        offsets, dir1s, dir2s = [], [], []
        for p in detector:
            offset = matrix.col(p.get_origin()) - dorg
            offsets.append(
                matrix.col((offset.dot(d1), offset.dot(d2), offset.dot(dn))))
            dir1 = matrix.col(p.get_fast_axis())
            dir1_new_basis = matrix.col(
                (dir1.dot(d1), dir1.dot(d2), dir1.dot(dn)))
            dir1s.append(dir1_new_basis)
            dir2 = matrix.col(p.get_slow_axis())
            dir2_new_basis = matrix.col(
                (dir2.dot(d1), dir2.dot(d2), dir2.dot(dn)))
            dir2s.append(dir2_new_basis)

        # The offsets and directions in the d1, d2, dn basis are fixed
        # quantities, not dependent on parameter values.
        self._offsets = offsets
        self._dir1s = dir1s
        self._dir2s = dir2s

        # Set up the initial state. This is the basis d1, d2, dn.
        istate = {"d1": d1, "d2": d2, "dn": dn}

        # set up the parameters.
        # distance from lab origin to mid_panel plane along its normal,
        # in initial orientation
        distance = mid_panel.get_directed_distance()
        dist = Parameter(distance, dn, "length (mm)", "Dist")

        # shift in the detector model plane to locate dorg, in initial
        # orientation
        shift = dorg - dn * distance
        shift1 = Parameter(shift.dot(d1), d1, "length (mm)", "Shift1")
        shift2 = Parameter(shift.dot(d2), d2, "length (mm)", "Shift2")

        # rotations of the plane through its origin about:
        # 1) axis normal to initial orientation
        # 2) d1 axis of initial orientation
        # 3) d2 axis of initial orientation
        tau1 = Parameter(0, dn, "angle (mrad)", "Tau1")
        tau2 = Parameter(0, d1, "angle (mrad)", "Tau2")
        tau3 = Parameter(0, d2, "angle (mrad)", "Tau3")

        # build the parameter list in a specific,  maintained order
        p_list = [dist, shift1, shift2, tau1, tau2, tau3]

        # set up the base class
        ModelParameterisation.__init__(
            self,
            detector,
            istate,
            p_list,
            experiment_ids=experiment_ids,
            is_multi_state=True,
        )

        # call compose to calculate all the derivatives
        self.compose()