Example #1
0
class _DummyObject(DataModel):

    name = StringProperty(text="Name", tabular=True, default="")
    number = FloatProperty(text="Number", tabular=True, default=0)
    test = LabeledProperty(text="Test", tabular=True, default=[])

    pass  # end of class
Example #2
0
class _DummyParent(DataModel):

    attrib = LabeledProperty(text="Attrib",
                             default=[],
                             tabular=True,
                             data_type=_DummyObject)

    pass  # end of class
Example #3
0
class AtomContentObject(Model):
    """
        Wrapper around an atom object used in the AtomContents model.
        Stores the atom, the property to set and it's default amount.
    """

    atom = LabeledProperty(
        default=None,
        text="Atom",
        visible=False,
        persistent=False,
        tabular=True,
    )

    prop = LabeledProperty(
        default=None,
        text="Prop",
        visible=False,
        persistent=False,
        tabular=True,
    )

    amount = FloatProperty(
        default=0.0,
        text="Amount",
        minimum=0.0,
        visible=False,
        persistent=False,
        tabular=True,
    )

    def __init__(self, atom, prop, amount, *args, **kwargs):
        super(AtomContentObject, self).__init__(*args, **kwargs)
        self.atom = atom
        self.prop = prop
        self.amount = amount

    def update_atom(self, value):
        if not (self.atom == "" or self.atom is None or self.prop is None):
            with self.atom.data_changed.ignore():
                setattr(self.atom, self.prop, self.amount * value)
                if hasattr(self.atom, "_set_driven_flag_for_prop"):
                    self.atom._set_driven_flag_for_prop(self.prop)

    pass
Example #4
0
class ChildModel(PyXRDModel):
    """
        A PyXRDModel with child-parent relation support.
    """

    # MODEL INTEL:
    class Meta(PyXRDModel.Meta):
        @classmethod
        def get_inheritable_properties(
                cls):  # TODO MOVE THIS TO THE CHILD MODEL!!
            if not hasattr(cls, "all_properties"):
                raise RuntimeError("Meta class '%s' has not been initialized" \
                    " properly: 'all_properties' is not set!" % type(cls))
            else:
                return [
                    attr for attr in cls.all_properties
                    if getattr(attr, "inheritable", False)
                ]

    # SIGNALS:
    removed = SignalProperty()
    added = SignalProperty()

    # PROPERTIES:
    __parent = None

    def __get_parent(self):
        if callable(self.__parent):
            return self.__parent()
        else:
            return self.__parent

    def __set_parent(self, value):
        if not self.parent == value:
            if self.parent is not None:
                self.removed.emit()
            try:
                self.__parent = weakref.ref(value, self.__on_parent_finalize)
            except TypeError:
                self.__parent = value
            if self.parent is not None:
                self.added.emit()

    def __on_parent_finalize(self, ref):
        self.removed.emit()
        self.__parent = None

    parent = LabeledProperty(fget=__get_parent, fset=__set_parent)

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self, parent=None, *args, **kwargs):
        super(ChildModel, self).__init__(*args, **kwargs)
        self.parent = parent

    pass  # end of class
Example #5
0
class ExperimentalLine(PyXRDLine):

    # MODEL INTEL:
    class Meta(PyXRDLine.Meta):
        store_id = "ExperimentalLine"

    specimen = property(DataModel.parent.fget, DataModel.parent.fset)

    # PROPERTIES:

    #: The line color
    color = modify(PyXRDLine.color,
                   default=settings.EXPERIMENTAL_COLOR,
                   inherit_from="parent.parent.display_exp_color")
    #: The linewidth in points
    lw = modify(PyXRDLine.lw,
                default=settings.EXPERIMENTAL_LINEWIDTH,
                inherit_from="parent.parent.display_exp_lw")

    #: A short string describing the (matplotlib) linestyle
    ls = modify(PyXRDLine.ls,
                default=settings.EXPERIMENTAL_LINESTYLE,
                inherit_from="parent.parent.display_exp_ls")

    #: A short string describing the (matplotlib) marker
    marker = modify(PyXRDLine.marker,
                    default=settings.EXPERIMENTAL_MARKER,
                    inherit_from="parent.parent.display_exp_marker")

    #: The value to cap the pattern at (in raw values)
    cap_value = FloatProperty(default=0.0,
                              text="Cap value",
                              persistent=True,
                              visible=True,
                              widget_type="float_entry",
                              signal_name="visuals_changed",
                              mix_with=(SignalMixin, ))

    @property
    def max_display_y(self):
        max_value = super(ExperimentalLine, self).max_display_y
        # Only cap single and multi-line patterns, not 2D images:
        if self.cap_value > 0 and not (self.num_columns > 2
                                       and len(self.z_data)):
            max_value = min(max_value, self.cap_value)
        return max_value

    ###########################################################################

    #: The background offset value
    bg_position = FloatProperty(default=0.0,
                                text="Background offset",
                                persistent=False,
                                visible=True,
                                widget_type="float_entry",
                                signal_name="visuals_changed",
                                mix_with=(SignalMixin, ))

    #: The background scale
    bg_scale = FloatProperty(default=1.0,
                             text="Background scale",
                             persistent=False,
                             visible=True,
                             widget_type="float_entry",
                             signal_name="visuals_changed",
                             mix_with=(SignalMixin, ))

    #: The background pattern or None for linear patterns
    bg_pattern = LabeledProperty(default=None,
                                 text="Background pattern",
                                 persistent=False,
                                 visible=False,
                                 signal_name="visuals_changed",
                                 mix_with=(SignalMixin, ))

    #: The background type: pattern or linear
    bg_type = IntegerChoiceProperty(default=0,
                                    text="Background type",
                                    choices=settings.PATTERN_BG_TYPES,
                                    persistent=False,
                                    visible=True,
                                    signal_name="visuals_changed",
                                    set_action_name="find_bg_position",
                                    mix_with=(
                                        SignalMixin,
                                        SetActionMixin,
                                    ))

    def get_bg_type_lbl(self):
        return settings.PATTERN_BG_TYPES[self.bg_type]

    ###########################################################################

    #: Pattern smoothing type
    smooth_type = IntegerChoiceProperty(
        default=0,
        text="Smooth type",
        choices=settings.PATTERN_SMOOTH_TYPES,
        persistent=False,
        visible=True,
        signal_name="visuals_changed",
        set_action_name="setup_smooth_variables",
        mix_with=(
            SignalMixin,
            SetActionMixin,
        ))

    smooth_pattern = None

    #: The smooth degree
    smooth_degree = IntegerProperty(default=0,
                                    text="Smooth degree",
                                    persistent=False,
                                    visible=True,
                                    signal_name="visuals_changed",
                                    mix_with=(SignalMixin, ))

    ###########################################################################

    #: The noise fraction to add
    noise_fraction = FloatProperty(default=0.0,
                                   text="Noise fraction",
                                   persistent=False,
                                   visible=True,
                                   widget_type="spin",
                                   signal_name="visuals_changed",
                                   mix_with=(SignalMixin, ))

    ###########################################################################

    #: The pattern shift correction value
    shift_value = FloatProperty(default=0.0,
                                text="Shift value",
                                persistent=False,
                                visible=True,
                                widget_type="float_entry",
                                signal_name="visuals_changed",
                                mix_with=(SignalMixin, ))

    #: Shift reference position
    shift_position = FloatChoiceProperty(
        default=0.42574,
        text="Shift position",
        choices=settings.PATTERN_SHIFT_POSITIONS,
        persistent=False,
        visible=True,
        signal_name="visuals_changed",
        set_action_name="setup_shift_variables",
        mix_with=(
            SignalMixin,
            SetActionMixin,
        ))

    ###########################################################################

    #: The peak properties calculation start position
    peak_startx = FloatProperty(default=0.0,
                                text="Peak properties start position",
                                persistent=False,
                                visible=True,
                                widget_type="float_entry",
                                set_action_name="update_peak_properties",
                                mix_with=(SetActionMixin, ))

    #: The peak properties calculation end position
    peak_endx = FloatProperty(default=0.0,
                              text="Peak properties end position",
                              persistent=False,
                              visible=True,
                              widget_type="float_entry",
                              set_action_name="update_peak_properties",
                              mix_with=(SetActionMixin, ))

    #: The peak fwhm value
    peak_fwhm_result = FloatProperty(
        default=0.0,
        text="Peak FWHM value",
        persistent=False,
        visible=True,
        widget_type="label",
    )

    #: The peak area value
    peak_area_result = FloatProperty(
        default=0.0,
        text="Peak area value",
        persistent=False,
        visible=True,
        widget_type="label",
    )

    #: The patterns peak properties are calculated from
    peak_properties_pattern = LabeledProperty(default=None,
                                              text="Peak properties pattern",
                                              persistent=False,
                                              visible=False,
                                              signal_name="visuals_changed",
                                              mix_with=(SignalMixin, ))

    ###########################################################################

    #: The strip peak start position
    strip_startx = FloatProperty(default=0.0,
                                 text="Strip peak start position",
                                 persistent=False,
                                 visible=True,
                                 widget_type="float_entry",
                                 set_action_name="update_strip_pattern",
                                 mix_with=(SetActionMixin, ))

    #: The strip peak end position
    strip_endx = FloatProperty(default=0.0,
                               text="Strip peak end position",
                               persistent=False,
                               visible=True,
                               widget_type="float_entry",
                               set_action_name="update_strip_pattern",
                               mix_with=(SetActionMixin, ))

    #: The stripped peak pattern
    stripped_pattern = LabeledProperty(default=None,
                                       text="Strip peak pattern",
                                       persistent=False,
                                       visible=False,
                                       signal_name="visuals_changed",
                                       mix_with=(SignalMixin, ))

    #: The stripped peak pattern noise
    noise_level = FloatProperty(default=0.0,
                                text="Strip peak noise level",
                                persistent=False,
                                visible=True,
                                widget_type="float_entry",
                                set_action_name="update_strip_pattern_noise",
                                mix_with=(SetActionMixin, ))

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self, cap_value=0.0, *args, **kwargs):
        """
            Valid keyword arguments for a ExperimentalLine are:
                cap_value: the value (in raw counts) at which to cap
                 the experimental pattern  
        """
        super(ExperimentalLine, self).__init__(*args, **kwargs)
        self.cap_value = cap_value

    # ------------------------------------------------------------
    #      Background Removal
    # ------------------------------------------------------------
    def remove_background(self):
        with self.data_changed.hold_and_emit():
            bg = None
            if self.bg_type == 0:
                bg = self.bg_position
            elif self.bg_type == 1 and self.bg_pattern is not None and not (
                    self.bg_position == 0 and self.bg_scale == 0):
                bg = self.bg_pattern * self.bg_scale + self.bg_position
            if bg is not None and self.data_y.size > 0:
                self.data_y[:, 0] -= bg
            self.clear_bg_variables()

    def find_bg_position(self):
        try:
            self.bg_position = np.min(self.data_y)
        except ValueError:
            return 0.0

    def clear_bg_variables(self):
        with self.visuals_changed.hold_and_emit():
            self.bg_pattern = None
            self.bg_scale = 0.0
            self.bg_position = 0.0

    # ------------------------------------------------------------
    #       Data Smoothing
    # ------------------------------------------------------------
    def smooth_data(self):
        with self.data_changed.hold_and_emit():
            if self.smooth_degree > 0:
                degree = int(self.smooth_degree)
                self.data_y[:, 0] = smooth(self.data_y[:, 0], degree)
            self.smooth_degree = 0.0

    def setup_smooth_variables(self):
        with self.visuals_changed.hold_and_emit():
            self.smooth_degree = 5.0

    def clear_smooth_variables(self):
        with self.visuals_changed.hold_and_emit():
            self.smooth_degree = 0.0

    # ------------------------------------------------------------
    #       Noise adding
    # ------------------------------------------------------------
    def add_noise(self):
        with self.data_changed.hold_and_emit():
            if self.noise_fraction > 0:
                noisified = add_noise(self.data_y[:, 0], self.noise_fraction)
                self.set_data(self.data_x, noisified)
            self.noise_fraction = 0.0

    def clear_noise_variables(self):
        with self.visuals_changed.hold_and_emit():
            self.noise_fraction = 0.0

    # ------------------------------------------------------------
    #       Data Shifting
    # ------------------------------------------------------------
    def shift_data(self):
        with self.data_changed.hold_and_emit():
            if self.shift_value != 0.0:
                if settings.PATTERN_SHIFT_TYPE == "Linear":
                    self.data_x = self.data_x - self.shift_value
                    if self.specimen is not None:
                        with self.specimen.visuals_changed.hold():
                            for marker in self.specimen.markers:
                                marker.position = marker.position - self.shift_value
                elif settings.PATTERN_SHIFT_TYPE == "Displacement":
                    position = self.specimen.goniometer.get_t_from_nm(
                        self.shift_position)
                    displacement = 0.5 * self.specimen.goniometer.radius * self.shift_value / np.cos(
                        position / 180 * np.pi)
                    correction = 2 * displacement * np.cos(
                        self.data_x / 2 / 180 *
                        np.pi) / self.specimen.goniometer.radius
                    self.data_x = self.data_x - correction

            self.shift_value = 0.0

    def setup_shift_variables(self):
        with self.visuals_changed.hold_and_emit():
            position = self.specimen.goniometer.get_2t_from_nm(
                self.shift_position)
            if position > 0.1:
                max_x = position + 0.5
                min_x = position - 0.5
                condition = (self.data_x >= min_x) & (self.data_x <= max_x)
                section_x, section_y = np.extract(condition,
                                                  self.data_x), np.extract(
                                                      condition,
                                                      self.data_y[:, 0])
                try:
                    #TODO to exclude noise it'd be better to first interpolate
                    # or smooth the data and then find the max.
                    actual_position = section_x[np.argmax(section_y)]
                except ValueError:
                    actual_position = position
                self.shift_value = actual_position - position

    def clear_shift_variables(self):
        with self.visuals_changed.hold_and_emit():
            self.shift_value = 0

    # ------------------------------------------------------------
    #       Peak area calculation
    # ------------------------------------------------------------
    peak_bg_slope = 0.0
    avg_starty = 0.0
    avg_endy = 0.0

    def update_peak_properties(self):
        with self.visuals_changed.hold_and_emit():
            if self.peak_endx < self.peak_startx:
                self.peak_endx = self.peak_startx + 1.0
                return  # previous line will have re-invoked this method

            # calculate average starting point y value
            condition = (self.data_x >= self.peak_startx - 0.1) & (
                self.data_x <= self.peak_startx + 0.1)
            section = np.extract(condition, self.data_y[:, 0])
            self.avg_starty = np.min(section)

            # calculate average ending point y value
            condition = (self.data_x >= self.peak_endx - 0.1) & (
                self.data_x <= self.peak_endx + 0.1)
            section = np.extract(condition, self.data_y[:, 0])
            self.avg_endy = np.min(section)

            # Calculate new bg slope
            self.peak_bg_slope = (self.avg_starty - self.avg_endy) / (
                self.peak_startx - self.peak_endx)

            # Get the x-values in between start and end point:
            condition = (self.data_x >= self.peak_startx) & (self.data_x <=
                                                             self.peak_endx)
            section_x = np.extract(condition, self.data_x)
            section_y = np.extract(condition, self.data_y)
            bg_curve = (self.peak_bg_slope * (section_x - self.peak_startx) +
                        self.avg_starty)

            #Calculate the peak area:
            self.peak_area_result = abs(
                trapz(section_y, x=section_x) - trapz(bg_curve, x=section_x))

            # create a spline of of the peak (shifted down by half of its maximum)
            fwhm_curve = section_y - bg_curve
            peak_half_max = np.max(fwhm_curve) * 0.5
            spline = UnivariateSpline(section_x,
                                      fwhm_curve - peak_half_max,
                                      s=0)
            roots = spline.roots()  # find the roots = where the splin = 0
            self.peak_fwhm_result = np.abs(roots[0] - roots[-1]) if (
                len(roots) >= 2) else 0

            # Calculate the new y-values: x values, bg_curve y values, original pattern y values, x values for the FWHM, y values for the FWHM
            self.peak_properties_pattern = (section_x,
                                            bg_curve, section_y, roots,
                                            spline(roots) + peak_half_max)

    def clear_peak_properties_variables(self):
        with self.visuals_changed.hold_and_emit():
            self._peak_startx = 0.0
            self._peak_properties_pattern = None
            self._peak_endx = 0.0
            self.peak_properties = 0.0

    # ------------------------------------------------------------
    #       Peak stripping
    # ------------------------------------------------------------
    def strip_peak(self):
        with self.data_changed.hold_and_emit():
            if self.stripped_pattern is not None:
                stripx, stripy = self.stripped_pattern
                indeces = ((self.data_x >= self.strip_startx) &
                           (self.data_x <= self.strip_endx)).nonzero()[0]
                np.put(self.data_y[:, 0], indeces, stripy)
            self._strip_startx = 0.0
            self._stripped_pattern = None
            self.strip_endx = 0.0

    strip_slope = 0.0
    avg_starty = 0.0
    avg_endy = 0.0
    block_strip = False

    def update_strip_pattern_noise(self):
        with self.visuals_changed.hold_and_emit():
            # Get the x-values in between start and end point:
            condition = (self.data_x >= self.strip_startx) & (self.data_x <=
                                                              self.strip_endx)
            section_x = np.extract(condition, self.data_x)

            # Calculate the new y-values, add noise according to noise_level
            noise = self.avg_endy * 2 * (np.random.rand(*section_x.shape) -
                                         0.5) * self.noise_level
            section_y = (self.strip_slope * (section_x - self.strip_startx) +
                         self.avg_starty) + noise
            self.stripped_pattern = (section_x, section_y)

    def update_strip_pattern(self):
        with self.visuals_changed.hold_and_emit():
            if self.strip_endx < self.strip_startx:
                self.strip_endx = self.strip_startx + 1.0
                return  # previous line will have re-invoked this method

            if not self.block_strip:
                self.block_strip = True

                # calculate average starting point y value
                condition = (self.data_x >= self.strip_startx - 0.1) & (
                    self.data_x <= self.strip_startx + 0.1)
                section = np.extract(condition, self.data_y[:, 0])
                self.avg_starty = np.average(section)
                noise_starty = 2 * np.std(section) / self.avg_starty

                # calculate average ending point y value
                condition = (self.data_x >= self.strip_endx - 0.1) & (
                    self.data_x <= self.strip_endx + 0.1)
                section = np.extract(condition, self.data_y[:, 0])
                self.avg_endy = np.average(section)
                noise_endy = 2 * np.std(section) / self.avg_starty

                # Calculate new slope and noise level
                self.strip_slope = (self.avg_starty - self.avg_endy) / (
                    self.strip_startx - self.strip_endx)
                self.noise_level = (noise_starty + noise_endy) * 0.5

                self.update_strip_pattern_noise()

    def clear_strip_variables(self):
        with self.visuals_changed.hold_and_emit():
            self._strip_startx = 0.0
            self._strip_pattern = None
            self.strip_start_x = 0.0

    pass  # end of class
Example #6
0
class RawPatternPhase(RefinementGroup, AbstractPhase, metaclass=PyXRDRefinableMeta):

    # MODEL INTEL:
    class Meta(AbstractPhase.Meta):
        store_id = "RawPatternPhase"
        file_filters = [
            ("Phase file", get_case_insensitive_glob("*.PHS")),
        ]
        rp_filters = xrd_parsers.get_import_file_filters()
        rp_export_filters = xrd_parsers.get_export_file_filters()

    _data_object = None
    @property
    def data_object(self):
        self._data_object.type = "RawPatternPhase"

        self._data_object.raw_pattern_x = self.raw_pattern.data_x
        self._data_object.raw_pattern_y = self.raw_pattern.data_y[:, 0]
        self._data_object.apply_lpf = False
        self._data_object.apply_correction = False

        return self._data_object

    project = property(AbstractPhase.parent.fget, AbstractPhase.parent.fset)

    @property
    def refine_title(self):
        return "Raw Pattern Phase"

    @property
    def is_refinable(self):
        return False

    @property
    def children_refinable(self):
        return False

    @property
    def refinables(self):
        return []

    raw_pattern = LabeledProperty(
        default=None, text="Raw pattern",
        visible=True, persistent=True, tabular=True,
        inheritable=True, inherit_flag="inherit_CSDS_distribution", inherit_from="based_on.CSDS_distribution",
        signal_name="data_changed",
        mix_with=(SignalMixin, ObserveMixin,)
    )

    @property
    def spec_max_display_y(self):
        """The maximum intensity (y-axis) of the current loaded profile"""
        _max = 0.0
        if self.raw_pattern is not None:
            _max = max(_max, np.max(self.raw_pattern.max_display_y))
        return _max

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        my_kwargs = self.pop_kwargs(kwargs,
            *[prop.label for prop in RawPatternPhase.Meta.get_local_persistent_properties()]
        )
        super(RawPatternPhase, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        with self.data_changed.hold():
            self.raw_pattern = PyXRDLine(
                data=self.get_kwarg(kwargs, None, "raw_pattern"),
                parent=self
            )
            self.display_color = self.get_kwarg(kwargs, choice(self.line_colors), "display_color")
            self.inherit_display_color = self.get_kwarg(kwargs, False, "inherit_display_color")

    def __repr__(self):
        return "RawPatternPhase(name='%s')" % (self.name)

    # ------------------------------------------------------------
    #      Notifications of observable properties
    # ------------------------------------------------------------
    @AbstractPhase.observe("data_changed", signal=True)
    def notify_data_changed(self, model, prop_name, info):
        self.data_changed.emit() # propagate signal

    pass #end of class
Example #7
0
class Specimen(DataModel, Storable):
    # MODEL INTEL:
    class Meta(DataModel.Meta):
        store_id = "Specimen"

        export_filters = xrd_parsers.get_export_file_filters()
        excl_filters = exc_parsers.get_import_file_filters()

    _data_object = None

    @property
    def data_object(self):
        self._data_object.goniometer = self.goniometer.data_object
        self._data_object.range_theta = self.__get_range_theta()
        self._data_object.selected_range = self.get_exclusion_selector()
        self._data_object.z_list = self.get_z_list()
        try:
            self._data_object.observed_intensity = np.copy(
                self.experimental_pattern.data_y)
        except IndexError:
            self._data_object.observed_intensity = np.array([], dtype=float)
        return self._data_object

    def get_z_list(self):
        return list(self.experimental_pattern.z_data)

    project = property(DataModel.parent.fget, DataModel.parent.fset)

    # PROPERTIES:

    #: The sample name
    sample_name = StringProperty(default="",
                                 text="Sample",
                                 visible=True,
                                 persistent=True,
                                 tabular=True,
                                 signal_name="visuals_changed",
                                 mix_with=(SignalMixin, ))

    #: The sample name
    name = StringProperty(default="",
                          text="Name",
                          visible=True,
                          persistent=True,
                          tabular=True,
                          signal_name="visuals_changed",
                          mix_with=(SignalMixin, ))

    @StringProperty(default="",
                    text="Label",
                    visible=False,
                    persistent=False,
                    tabular=True,
                    mix_with=(ReadOnlyMixin, ))
    def label(self):
        if self.display_stats_in_lbl and (self.project is not None and
                                          self.project.layout_mode == "FULL"):
            label = self.sample_name
            label += "\nRp = %.1f%%" % not_none(self.statistics.Rp, 0.0)
            label += "\nRwp = %.1f%%" % not_none(self.statistics.Rwp, 0.0)
            return label
        else:
            return self.sample_name

    display_calculated = BoolProperty(default=True,
                                      text="Display calculated diffractogram",
                                      visible=True,
                                      persistent=True,
                                      tabular=True,
                                      signal_name="visuals_changed",
                                      mix_with=(SignalMixin, ))

    display_experimental = BoolProperty(
        default=True,
        text="Display experimental diffractogram",
        visible=True,
        persistent=True,
        tabular=True,
        signal_name="visuals_changed",
        mix_with=(SignalMixin, ))

    display_vshift = FloatProperty(default=0.0,
                                   text="Vertical shift of the plot",
                                   visible=True,
                                   persistent=True,
                                   tabular=True,
                                   signal_name="visuals_changed",
                                   widget_type="spin",
                                   mix_with=(SignalMixin, ))

    display_vscale = FloatProperty(default=0.0,
                                   text="Vertical scale of the plot",
                                   visible=True,
                                   persistent=True,
                                   tabular=True,
                                   signal_name="visuals_changed",
                                   widget_type="spin",
                                   mix_with=(SignalMixin, ))

    display_phases = BoolProperty(default=True,
                                  text="Display phases seperately",
                                  visible=True,
                                  persistent=True,
                                  tabular=True,
                                  signal_name="visuals_changed",
                                  mix_with=(SignalMixin, ))

    display_stats_in_lbl = BoolProperty(default=True,
                                        text="Display Rp in label",
                                        visible=True,
                                        persistent=True,
                                        tabular=True,
                                        signal_name="visuals_changed",
                                        mix_with=(SignalMixin, ))

    display_residuals = BoolProperty(default=True,
                                     text="Display residual patterns",
                                     visible=True,
                                     persistent=True,
                                     tabular=True,
                                     signal_name="visuals_changed",
                                     mix_with=(SignalMixin, ))

    display_residual_scale = FloatProperty(default=1.0,
                                           text="Residual pattern scale",
                                           minimum=0.0,
                                           visible=True,
                                           persistent=True,
                                           tabular=True,
                                           signal_name="visuals_changed",
                                           widget_type="spin",
                                           mix_with=(SignalMixin, ))

    display_derivatives = BoolProperty(default=False,
                                       text="Display derivative patterns",
                                       visible=True,
                                       persistent=True,
                                       tabular=True,
                                       signal_name="visuals_changed",
                                       mix_with=(SignalMixin, ))

    #: A :class:`~pyxrd.generic.models.lines.CalculatedLine` instance
    calculated_pattern = LabeledProperty(default=None,
                                         text="Calculated diffractogram",
                                         visible=True,
                                         persistent=True,
                                         tabular=True,
                                         signal_name="data_changed",
                                         widget_type="xy_list_view",
                                         mix_with=(
                                             SignalMixin,
                                             ObserveMixin,
                                         ))

    #: A :class:`~pyxrd.generic.models.lines.ExperimentalLine` instance
    experimental_pattern = LabeledProperty(default=None,
                                           text="Experimental diffractogram",
                                           visible=True,
                                           persistent=True,
                                           tabular=True,
                                           signal_name="data_changed",
                                           widget_type="xy_list_view",
                                           mix_with=(
                                               SignalMixin,
                                               ObserveMixin,
                                           ))

    #: A list of 2-theta ranges to exclude for the calculation of the Rp factor
    exclusion_ranges = LabeledProperty(default=None,
                                       text="Excluded ranges",
                                       visible=True,
                                       persistent=True,
                                       tabular=True,
                                       signal_name="data_changed",
                                       widget_type="xy_list_view",
                                       mix_with=(SignalMixin, ObserveMixin))

    #: A :class:`~pyxrd.goniometer.models.Goniometer` instance
    goniometer = LabeledProperty(default=None,
                                 text="Goniometer",
                                 visible=True,
                                 persistent=True,
                                 tabular=True,
                                 signal_name="data_changed",
                                 mix_with=(
                                     SignalMixin,
                                     ObserveMixin,
                                 ))

    #: A :class:`~pyxrd.specimen.models.Statistics` instance
    statistics = LabeledProperty(
        default=None,
        text="Markers",
        visible=False,
        persistent=False,
        tabular=True,
    )

    #: A list :class:`~pyxrd.specimen.models.Marker` instances
    markers = ListProperty(default=None,
                           text="Markers",
                           data_type=Marker,
                           visible=False,
                           persistent=True,
                           tabular=True,
                           signal_name="visuals_changed",
                           widget_type="object_list_view",
                           mix_with=(SignalMixin, ))

    @property
    def max_display_y(self):
        """
         The maximum intensity or z-value (display y axis) 
         of the current profile (both calculated and observed)
        """
        _max = 0.0
        if self.experimental_pattern is not None:
            _max = max(_max, np.max(self.experimental_pattern.max_display_y))
        if self.calculated_pattern is not None:
            _max = max(_max, np.max(self.calculated_pattern.max_display_y))
        return _max

    # ------------------------------------------------------------
    #      Initialisation and other internals
    # ------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        """
            Valid keyword arguments for a Specimen are:
                name: the name of the specimen
                sample_name: the sample name of the specimen
                calculated_pattern: the calculated pattern
                experimental_pattern: the experimental pattern
                exclusion_ranges: the exclusion ranges XYListStore
                goniometer: the goniometer used for recording data
                markers: the specimen's markers
                display_vshift: the patterns vertical shift from its default position
                display_vscale: the patterns vertical scale (default is 1.0)
                display_calculated: whether or not to show the calculated pattern
                display_experimental: whether or not to show the experimental pattern
                display_residuals: whether or not to show the residuals
                display_derivatives: whether or not to show the 1st derivative patterns
                display_phases: whether or not to show the separate phase patterns
                display_stats_in_lbl: whether or not to display the Rp values 
                 in the pattern label
        """

        my_kwargs = self.pop_kwargs(
            kwargs, "data_name", "data_sample", "data_sample_length",
            "data_calculated_pattern", "data_experimental_pattern",
            "calc_color", "calc_lw", "inherit_calc_color", "inherit_calc_lw",
            "exp_color", "exp_lw", "inherit_exp_color", "inherit_exp_lw",
            "project_goniometer", "data_markers", "bg_shift", "abs_scale",
            "exp_cap_value", "sample_length", "absorption", "sample_z_dev", *[
                prop.label
                for prop in Specimen.Meta.get_local_persistent_properties()
            ])
        super(Specimen, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        self._data_object = SpecimenData()

        with self.visuals_changed.hold_and_emit():
            with self.data_changed.hold_and_emit():
                self.name = self.get_kwarg(kwargs, "", "name", "data_name")
                sample_name = self.get_kwarg(kwargs, "", "sample_name",
                                             "data_sample")
                if isinstance(sample_name, bytes):
                    sample_name = sample_name.decode("utf-8", "ignore")
                self.sample_name = sample_name

                calc_pattern_old_kwargs = {}
                for kw in ("calc_color", "calc_lw", "inherit_calc_color",
                           "inherit_calc_lw"):
                    if kw in kwargs:
                        calc_pattern_old_kwargs[kw.replace(
                            "calc_", "")] = kwargs.pop(kw)
                self.calculated_pattern = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "calculated_pattern",
                                   "data_calculated_pattern"),
                    CalculatedLine,
                    child=True,
                    default_is_class=True,
                    label="Calculated Profile",
                    parent=self,
                    **calc_pattern_old_kwargs)

                exp_pattern_old_kwargs = {}
                for kw in ("exp_color", "exp_lw", "inherit_exp_color",
                           "inherit_exp_lw"):
                    if kw in kwargs:
                        exp_pattern_old_kwargs[kw.replace("exp_",
                                                          "")] = kwargs.pop(kw)
                self.experimental_pattern = self.parse_init_arg(
                    self.get_kwarg(kwargs, None, "experimental_pattern",
                                   "data_experimental_pattern"),
                    ExperimentalLine,
                    child=True,
                    default_is_class=True,
                    label="Experimental Profile",
                    parent=self,
                    **exp_pattern_old_kwargs)

                self.exclusion_ranges = PyXRDLine(data=self.get_kwarg(
                    kwargs, None, "exclusion_ranges"),
                                                  parent=self)

                # Extract old kwargs if they are there:
                gonio_kwargs = {}
                sample_length = self.get_kwarg(kwargs, None, "sample_length",
                                               "data_sample_length")
                if sample_length is not None:
                    gonio_kwargs["sample_length"] = float(sample_length)
                absorption = self.get_kwarg(kwargs, None, "absorption")
                if absorption is not None:  # assuming a surface density of at least 20 mg/cm²:
                    gonio_kwargs["absorption"] = float(absorption) / 0.02

                # Initialize goniometer (with optional old kwargs):
                self.goniometer = self.parse_init_arg(self.get_kwarg(
                    kwargs, None, "goniometer", "project_goniometer"),
                                                      Goniometer,
                                                      child=True,
                                                      default_is_class=True,
                                                      parent=self,
                                                      **gonio_kwargs)

                self.markers = self.get_list(kwargs,
                                             None,
                                             "markers",
                                             "data_markers",
                                             parent=self)
                for marker in self.markers:
                    self.observe_model(marker)
                self._specimens_observer = ListObserver(
                    self.on_marker_inserted,
                    self.on_marker_removed,
                    prop_name="markers",
                    model=self)

                self.display_vshift = float(
                    self.get_kwarg(kwargs, 0.0, "display_vshift"))
                self.display_vscale = float(
                    self.get_kwarg(kwargs, 1.0, "display_vscale"))
                self.display_calculated = bool(
                    self.get_kwarg(kwargs, True, "display_calculated"))
                self.display_experimental = bool(
                    self.get_kwarg(kwargs, True, "display_experimental"))
                self.display_residuals = bool(
                    self.get_kwarg(kwargs, True, "display_residuals"))
                self.display_residual_scale = float(
                    self.get_kwarg(kwargs, 1.0, "display_residual_scale"))
                self.display_derivatives = bool(
                    self.get_kwarg(kwargs, False, "display_derivatives"))
                self.display_phases = bool(
                    self.get_kwarg(kwargs, False, "display_phases"))
                self.display_stats_in_lbl = bool(
                    self.get_kwarg(kwargs, True, "display_stats_in_lbl"))

                self.statistics = Statistics(parent=self)

                pass  # end of with
            pass  # end of with
        pass  # end of __init__

    def __str__(self):
        return "<Specimen %s(%s)>" % (self.name, repr(self))

    # ------------------------------------------------------------
    #      Notifications of observable properties
    # ------------------------------------------------------------
    @DataModel.observe("data_changed", signal=True)
    def notify_data_changed(self, model, prop_name, info):
        if model == self.calculated_pattern:
            self.visuals_changed.emit()  # don't propagate this as data_changed
        else:
            self.data_changed.emit()  # propagate signal

    @DataModel.observe("visuals_changed", signal=True)
    def notify_visuals_changed(self, model, prop_name, info):
        self.visuals_changed.emit()  # propagate signal

    def on_marker_removed(self, item):
        with self.visuals_changed.hold_and_emit():
            self.relieve_model(item)
            item.parent = None

    def on_marker_inserted(self, item):
        with self.visuals_changed.hold_and_emit():
            self.observe_model(item)
            item.parent = self

    # ------------------------------------------------------------
    #      Input/Output stuff
    # ------------------------------------------------------------
    @staticmethod
    def from_experimental_data(filename,
                               parent,
                               parser=xrd_parsers._group_parser,
                               load_as_insitu=False):
        """
            Returns a list of new :class:`~.specimen.models.Specimen`'s loaded
            from `filename`, setting their parent to `parent` using the given
            parser. If the load_as_insitu flag is set to true, 
        """
        specimens = list()
        xrdfiles = parser.parse(filename)
        if len(xrdfiles):
            if getattr(xrdfiles[0], "relative_humidity_data",
                       None) is not None:  # we have relative humidity data
                specimen = None

                # Setup list variables:
                x_data = None
                y_datas = []
                rh_datas = []

                for xrdfile in xrdfiles:
                    # Get data we need:
                    name, sample, xy_data, rh_data = (
                        xrdfile.filename, xrdfile.name, xrdfile.data,
                        xrdfile.relative_humidity_data)
                    # Transform into numpy array for column selection
                    xy_data = np.array(xy_data)
                    rh_data = np.array(rh_data)
                    if specimen is None:
                        specimen = Specimen(parent=parent,
                                            name=name,
                                            sample_name=sample)
                        specimen.goniometer.reset_from_file(
                            xrdfile.create_gon_file())
                        # Extract the 2-theta positions once:
                        x_data = np.copy(xy_data[:, 0])

                    # Add a new sub-pattern:
                    y_datas.append(np.copy(xy_data[:, 1]))

                    # Store the average RH for this pattern:
                    rh_datas.append(np.average(rh_data))

                specimen.experimental_pattern.load_data_from_generator(
                    zip(x_data,
                        np.asanyarray(y_datas).transpose()),
                    clear=True)
                specimen.experimental_pattern.y_names = [
                    "%.1f" % f for f in rh_datas
                ]
                specimen.experimental_pattern.z_data = rh_datas
                specimens.append(specimen)
            else:  # regular (might be multi-pattern) file
                for xrdfile in xrdfiles:
                    name, sample, generator = xrdfile.filename, xrdfile.name, xrdfile.data
                    specimen = Specimen(parent=parent,
                                        name=name,
                                        sample_name=sample)
                    # TODO FIXME:
                    specimen.experimental_pattern.load_data_from_generator(
                        generator, clear=True)
                    specimen.goniometer.reset_from_file(
                        xrdfile.create_gon_file())
                    specimens.append(specimen)
        return specimens

    def json_properties(self):
        props = Storable.json_properties(self)
        props["exclusion_ranges"] = self.exclusion_ranges._serialize_data()
        return props

    def get_export_meta_data(self):
        """ Returns a dictionary with common meta-data used in export functions
            for experimental or calculated data """
        return dict(
            sample=self.label + " " + self.sample_name,
            wavelength=self.goniometer.wavelength,
            radius=self.goniometer.radius,
            divergence=self.goniometer.divergence,
            soller1=self.goniometer.soller1,
            soller2=self.goniometer.soller2,
        )

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def clear_markers(self):
        with self.visuals_changed.hold():
            for marker in list(self.markers)[::-1]:
                self.markers.remove(marker)

    def auto_add_peaks(self, tmodel):
        """
        Automagically add peak markers
        
        *tmodel* a :class:`~specimen.models.ThresholdSelector` model
        """
        threshold = tmodel.sel_threshold
        base = 1 if (tmodel.pattern == "exp") else 2
        data_x, data_y = tmodel.get_xy()
        maxtab, mintab = peakdetect(data_y, data_x, 5,
                                    threshold)  # @UnusedVariable

        mpositions = [marker.position for marker in self.markers]

        with self.visuals_changed.hold():
            i = 1
            for x, y in maxtab:  # @UnusedVariable
                if not x in mpositions:
                    nm = self.goniometer.get_nm_from_2t(x) if x != 0 else 0
                    new_marker = Marker(label="%%.%df" %
                                        (3 + min(int(log(nm, 10)), 0)) % nm,
                                        parent=self,
                                        position=x,
                                        base=base)
                    self.markers.append(new_marker)
                i += 1

    def get_exclusion_selector(self):
        """
        Get the numpy selector array for non-excluded data
        
        :rtype: a numpy ndarray
        """
        x = self.__get_range_theta() * 360.0 / pi  # convert to degrees
        selector = np.ones(x.shape, dtype=bool)
        data = np.sort(np.asarray(self.exclusion_ranges.get_xy_data()), axis=0)
        for x0, x1 in zip(*data):
            new_selector = ((x < x0) | (x > x1))
            selector = selector & new_selector
        return selector

    def get_exclusion_xy(self):
        """
        Get an numpy array containing only non-excluded data X and Y data
                
        :rtype: a tuple containing 4 numpy ndarray's: the experimental X and Y
        data and the calculated X and Y data
        """
        ex, ey = self.experimental_pattern.get_xy_data()
        cx, cy = self.calculated_pattern.get_xy_data()
        selector = self.get_exclusion_selector(ex)
        return ex[selector], ey[selector], cx[selector], cy[selector]

    # ------------------------------------------------------------
    #      Draggable mix-in hook:
    # ------------------------------------------------------------
    def on_pattern_dragged(self, delta_y, button=1):
        if button == 1:
            self.display_vshift += delta_y
        elif button == 3:
            self.display_vscale += delta_y
        elif button == 2:
            self.project.display_plot_offset += delta_y
        pass

    def update_visuals(self, phases):
        """
            Update visual representation of phase patterns (if any)
        """
        if phases is not None:
            self.calculated_pattern.y_names = [
                phase.name if phase is not None else "" for phase in phases
            ]
            self.calculated_pattern.phase_colors = [
                phase.display_color if phase is not None else "#FF00FF"
                for phase in phases
            ]

    # ------------------------------------------------------------
    #      Intensity calculations:
    # ------------------------------------------------------------
    def update_pattern(self, total_intensity, phase_intensities, phases):
        """
        Update calculated patterns using the provided total and phase intensities
        """
        if len(phases) == 0:
            self.calculated_pattern.clear()
        else:
            maxZ = len(self.get_z_list())
            new_data = np.zeros(
                (phase_intensities.shape[-1], maxZ + maxZ * len(phases)))
            for z_index in range(maxZ):
                # Set the total intensity for this z_index:
                new_data[:, z_index] = total_intensity[z_index]
                # Calculate phase intensity offsets:
                phase_start_index = maxZ + z_index * len(phases)
                phase_end_index = phase_start_index + len(phases)
                # Set phase intensities for this z_index:
                new_data[:, phase_start_index:
                         phase_end_index] = phase_intensities[:,
                                                              z_index, :].transpose(
                                                              )
                # Store in pattern:
                self.calculated_pattern.set_data(
                    self.__get_range_theta() * 360. / pi, new_data)
            self.update_visuals(phases)
        if settings.GUI_MODE:
            self.statistics.update_statistics(derived=self.display_derivatives)

    def convert_to_fixed(self):
        """
        Converts the experimental data from ADS to fixed slits in-place 
        (disregards the `has_ads` flag in the goniometer, but uses the settings
        otherwise) 
        """
        correction = self.goniometer.get_ADS_to_fixed_correction(
            self.__get_range_theta())
        self.experimental_pattern.apply_correction(correction)

    def convert_to_ads(self):
        """
        Converts the experimental data from fixed slits to ADS in-place 
        (disregards the `has_ads` flag in the goniometer, but uses the settings
        otherwise) 
        """
        correction = 1.0 / self.goniometer.get_ADS_to_fixed_correction(
            self.__get_range_theta())
        self.experimental_pattern.apply_correction(correction)

    def __get_range_theta(self):
        if len(self.experimental_pattern) <= 1:
            return self.goniometer.get_default_theta_range()
        else:
            return np.radians(self.experimental_pattern.data_x * 0.5)

    def __repr__(self):
        return "Specimen(name='%s')" % self.name

    pass  # end of class
Example #8
0
class ThresholdSelector(ChildModel):

    # MODEL INTEL:

    specimen = property(DataModel.parent.fget, DataModel.parent.fset)

    # PROPERTIES:

    pattern = StringChoiceProperty(
        default="exp",
        text="Pattern",
        visible=True,
        persistent=False,
        choices={
            "exp": "Experimental Pattern",
            "calc": "Calculated Pattern"
        },
        set_action_name="update_threshold_plot_data",
        mix_with=(SetActionMixin, ))

    max_threshold = FloatProperty(default=0.32,
                                  text="Maximum threshold",
                                  visible=True,
                                  persistent=False,
                                  minimum=0.0,
                                  maximum=1.0,
                                  widget_type="float_entry",
                                  set_action_name="update_threshold_plot_data",
                                  mix_with=(SetActionMixin, ))

    steps = IntegerProperty(default=20,
                            text="Steps",
                            visible=True,
                            persistent=False,
                            minimum=3,
                            maximum=50,
                            set_action_name="update_threshold_plot_data",
                            mix_with=(SetActionMixin, ))

    sel_num_peaks = IntegerProperty(default=0,
                                    text="Selected number of peaks",
                                    visible=True,
                                    persistent=False,
                                    widget_type="label")

    def set_sel_threshold(self, value):
        _sel_threshold = type(self).sel_threshold._get(self)
        if value != _sel_threshold and len(self.threshold_plot_data[0]) > 0:
            _sel_threshold = value
            if _sel_threshold >= self.threshold_plot_data[0][-1]:
                self.sel_num_peaks = self.threshold_plot_data[1][-1]
            elif _sel_threshold <= self.threshold_plot_data[0][0]:
                self.sel_num_peaks = self.threshold_plot_data[1][0]
            else:
                self.sel_num_peaks = int(
                    interp1d(*self.threshold_plot_data)(_sel_threshold))
            type(self).sel_threshold._set(self, _sel_threshold)

    sel_threshold = FloatProperty(default=0.1,
                                  text="Selected threshold",
                                  visible=True,
                                  persistent=False,
                                  widget_type="float_entry",
                                  fset=set_sel_threshold)

    threshold_plot_data = LabeledProperty(default=None,
                                          text="Threshold plot data",
                                          visible=False,
                                          persistent=False)

    # ------------------------------------------------------------
    #      Initialisation and other internals
    # ------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        super(ThresholdSelector, self).__init__(*args, **kwargs)

        self.max_threshold = self.get_kwarg(kwargs, self.max_threshold,
                                            "max_threshold")
        self.steps = self.get_kwarg(kwargs, self.steps, "steps")
        self.sel_threshold = self.get_kwarg(kwargs, self.sel_threshold,
                                            "sel_threshold")

        if self.parent.experimental_pattern.size > 0:
            self.pattern = "exp"
        else:
            self.pattern = "calc"

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def get_xy(self):
        if self.pattern == "exp":
            data_x, data_y = self.parent.experimental_pattern.get_xy_data()
        elif self.pattern == "calc":
            data_x, data_y = self.parent.calculated_pattern.get_xy_data()
        if data_y.size > 0:
            data_y = data_y / np.max(data_y)
        return data_x, data_y

    _updating_plot_data = False

    def update_threshold_plot_data(self, status_dict=None):
        if self.parent is not None and not self._updating_plot_data:
            self._updating_plot_data = True
            if self.pattern == "exp":
                p, t, m = self.parent.experimental_pattern.get_best_threshold(
                    self.max_threshold, self.steps, status_dict)
            elif self.pattern == "calc":
                p, t, m = self.parent.calculated_pattern.get_best_threshold(
                    self.max_threshold, self.steps, status_dict)
            self.threshold_plot_data = p
            self.sel_threshold = t
            self.max_threshold = m
            self._updating_plot_data = False

    pass  # end of class
Example #9
0
class Component(RefinementGroup,
                DataModel,
                Storable,
                metaclass=PyXRDRefinableMeta):

    # MODEL INTEL:
    class Meta(DataModel.Meta):
        store_id = "Component"

    _data_object = None

    @property
    def data_object(self):
        weight = 0.0

        self._data_object.layer_atoms = [None] * len(self.layer_atoms)
        for i, atom in enumerate(self.layer_atoms):
            self._data_object.layer_atoms[i] = atom.data_object
            weight += atom.weight

        self._data_object.interlayer_atoms = [None] * len(
            self.interlayer_atoms)
        for i, atom in enumerate(self.interlayer_atoms):
            self._data_object.interlayer_atoms[i] = atom.data_object
            weight += atom.weight

        self._data_object.volume = self.get_volume()
        self._data_object.weight = weight
        self._data_object.d001 = self.d001
        self._data_object.default_c = self.default_c
        self._data_object.delta_c = self.delta_c
        self._data_object.lattice_d = self.lattice_d

        return self._data_object

    phase = property(DataModel.parent.fget, DataModel.parent.fset)

    # SIGNALS:
    atoms_changed = SignalProperty()

    # UNIT CELL DIMENSION SHORTCUTS:
    @property
    def cell_a(self):
        return self._ucp_a.value

    @property
    def cell_b(self):
        return self._ucp_b.value

    @property
    def cell_c(self):
        return self.d001

    # PROPERTIES:

    #: The name of the Component
    name = StringProperty(default="",
                          text="Name",
                          visible=True,
                          persistent=True,
                          tabular=True,
                          signal_name="visuals_changed",
                          mix_with=(SignalMixin, ))

    #: Flag indicating whether to inherit the UCP a from :attr:`~linked_with`
    @BoolProperty(
        default=False,
        text="Inh. cell length a",
        visible=True,
        persistent=True,
        tabular=True,
    )
    def inherit_ucp_a(self):
        return self._ucp_a.inherited

    @inherit_ucp_a.setter
    def inherit_ucp_a(self, value):
        self._ucp_a.inherited = value

    #: Flag indicating whether to inherit the UCP b from :attr:`~linked_with`
    @BoolProperty(
        default=False,
        text="Inh. cell length b",
        visible=True,
        persistent=True,
        tabular=True,
    )
    def inherit_ucp_b(self):
        return self._ucp_b.inherited

    @inherit_ucp_b.setter
    def inherit_ucp_b(self, value):
        self._ucp_b.inherited = value

    #: Flag indicating whether to inherit d001 from :attr:`~linked_with`
    inherit_d001 = BoolProperty(default=False,
                                text="Inh. cell length c",
                                visible=True,
                                persistent=True,
                                tabular=True,
                                signal_name="data_changed",
                                mix_with=(SignalMixin, ))

    #: Flag indicating whether to inherit default_c from :attr:`~linked_with`
    inherit_default_c = BoolProperty(default=False,
                                     text="Inh. default length c",
                                     visible=True,
                                     persistent=True,
                                     tabular=True,
                                     signal_name="data_changed",
                                     mix_with=(SignalMixin, ))

    #: Flag indicating whether to inherit delta_c from :attr:`~linked_with`
    inherit_delta_c = BoolProperty(default=False,
                                   text="Inh. c length dev.",
                                   visible=True,
                                   persistent=True,
                                   tabular=True,
                                   signal_name="data_changed",
                                   mix_with=(SignalMixin, ))

    #: Flag indicating whether to inherit layer_atoms from :attr:`~linked_with`
    inherit_layer_atoms = BoolProperty(default=False,
                                       text="Inh. layer atoms",
                                       visible=True,
                                       persistent=True,
                                       tabular=True,
                                       signal_name="data_changed",
                                       mix_with=(SignalMixin, ))

    #: Flag indicating whether to inherit interlayer_atoms from :attr:`~linked_with`
    inherit_interlayer_atoms = BoolProperty(default=False,
                                            text="Inh. interlayer atoms",
                                            visible=True,
                                            persistent=True,
                                            tabular=True,
                                            signal_name="data_changed",
                                            mix_with=(SignalMixin, ))

    #: Flag indicating whether to inherit atom_relations from :attr:`~linked_with`
    inherit_atom_relations = BoolProperty(default=False,
                                          text="Inh. atom relations",
                                          visible=True,
                                          persistent=True,
                                          tabular=True,
                                          signal_name="data_changed",
                                          mix_with=(SignalMixin, ))

    _linked_with_index = None
    _linked_with_uuid = None

    #: The :class:`~Component` this component is linked with
    linked_with = LabeledProperty(default=None,
                                  text="Linked with",
                                  visible=True,
                                  persistent=True,
                                  signal_name="data_changed",
                                  mix_with=(SignalMixin, ))

    @linked_with.setter
    def linked_with(self, value):
        old = type(self).linked_with._get(self)
        if old != value:
            if old is not None:
                self.relieve_model(old)
            type(self).linked_with._set(self, value)
            if value is not None:
                self.observe_model(value)
            else:
                for prop in self.Meta.get_inheritable_properties():
                    setattr(self, prop.inherit_flag, False)

    #: The silicate lattice's c length
    lattice_d = FloatProperty(default=0.0,
                              text="Lattice c length [nm]",
                              visible=False,
                              persistent=True,
                              signal_name="data_changed")

    ucp_a = LabeledProperty(default=None,
                            text="Cell length a [nm]",
                            visible=True,
                            persistent=True,
                            tabular=True,
                            refinable=True,
                            inheritable=True,
                            inherit_flag="inherit_ucp_a",
                            inherit_from="linked_with.ucp_a",
                            signal_name="data_changed",
                            mix_with=(SignalMixin, InheritableMixin,
                                      ObserveMixin, RefinableMixin))

    ucp_b = LabeledProperty(default=None,
                            text="Cell length b [nm]",
                            visible=True,
                            persistent=True,
                            tabular=True,
                            refinable=True,
                            inheritable=True,
                            inherit_flag="inherit_ucp_b",
                            inherit_from="linked_with.ucp_b",
                            signal_name="data_changed",
                            mix_with=(SignalMixin, InheritableMixin,
                                      ObserveMixin, RefinableMixin))

    d001 = FloatProperty(default=1.0,
                         text="Cell length c [nm]",
                         minimum=0.0,
                         maximum=5.0,
                         visible=True,
                         persistent=True,
                         tabular=True,
                         refinable=True,
                         inheritable=True,
                         inherit_flag="inherit_default_c",
                         inherit_from="linked_with.d001",
                         signal_name="data_changed",
                         mix_with=(SignalMixin, InheritableMixin,
                                   RefinableMixin))

    default_c = FloatProperty(default=1.0,
                              text="Default c length [nm]",
                              minimum=0.0,
                              maximum=5.0,
                              visible=True,
                              persistent=True,
                              tabular=True,
                              inheritable=True,
                              inherit_flag="inherit_default_c",
                              inherit_from="linked_with.default_c",
                              signal_name="data_changed",
                              mix_with=(SignalMixin, InheritableMixin))

    delta_c = FloatProperty(default=0.0,
                            text="C length dev. [nm]",
                            minimum=0.0,
                            maximum=0.05,
                            visible=True,
                            persistent=True,
                            tabular=True,
                            inheritable=True,
                            inherit_flag="inherit_delta_c",
                            inherit_from="linked_with.delta_c",
                            signal_name="data_changed",
                            mix_with=(SignalMixin, InheritableMixin,
                                      RefinableMixin))

    layer_atoms = ListProperty(default=None,
                               text="Layer atoms",
                               visible=True,
                               persistent=True,
                               tabular=True,
                               widget_type="custom",
                               inheritable=True,
                               inherit_flag="inherit_layer_atoms",
                               inherit_from="linked_with.layer_atoms",
                               signal_name="data_changed",
                               data_type=Atom,
                               mix_with=(SignalMixin, InheritableMixin))

    interlayer_atoms = ListProperty(
        default=None,
        text="Interlayer atoms",
        visible=True,
        persistent=True,
        tabular=True,
        widget_type="custom",
        inheritable=True,
        inherit_flag="inherit_interlayer_atoms",
        inherit_from="linked_with.interlayer_atoms",
        signal_name="data_changed",
        data_type=Atom,
        mix_with=(SignalMixin, InheritableMixin))

    atom_relations = ListProperty(default=None,
                                  text="Atom relations",
                                  widget_type="custom",
                                  visible=True,
                                  persistent=True,
                                  tabular=True,
                                  refinable=True,
                                  inheritable=True,
                                  inherit_flag="inherit_atom_relations",
                                  inherit_from="linked_with.atom_relations",
                                  signal_name="data_changed",
                                  data_type=AtomRelation,
                                  mix_with=(SignalMixin, InheritableMixin,
                                            RefinableMixin))

    # Instance flag indicating whether or not linked_with & inherit flags should be saved
    save_links = True
    # Class flag indicating whether or not atom types in the component should be
    # exported using their name rather then their project-uuid.
    export_atom_types = False

    # REFINEMENT GROUP IMPLEMENTATION:
    @property
    def refine_title(self):
        return self.name

    @property
    def refine_descriptor_data(self):
        return dict(phase_name=self.phase.refine_title,
                    component_name=self.refine_title)

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self, **kwargs):
        """
        Valid keyword arguments for a Component are:
        *ucp_a*: unit cell property along a axis
        *ucp_b*: unit cell property along b axis
        *d001*: unit cell length c (aka d001)
        *default_c*: default c-value
        *delta_c*: the variation in basal spacing due to defects
        *layer_atoms*: ObjectListStore of layer Atoms
        *interlayer_atoms*: ObjectListStore of interlayer Atoms
        *atom_relations*: ObjectListStore of AtomRelations
        *inherit_ucp_a*: whether or not to inherit the ucp_a property from
         the linked component (if linked)
        *inherit_ucp_b*: whether or not to inherit the ucp_b property from
         the linked component (if linked)
        *inherit_d001*: whether or not to inherit the d001 property from
         the linked component (if linked)
        *inherit_default_c*: whether or not to inherit the default_c 
         property from the linked component (if linked)
        *inherit_delta_c*: whether or not to inherit the delta_c 
         property from the linked component (if linked)
        *inherit_layer_atoms*: whether or not to inherit the layer_atoms 
         property from the linked component (if linked)
        *inherit_interlayer_atoms*: whether or not to inherit the
         interlayer_atoms property from the linked component (if linked)
        *inherit_atom_relations*: whether or not to inherit the 
         atom_relations property from the linked component (if linked)
        *linked_with_uuid*: the UUID for the component this one is linked
         with
    Deprecated, but still supported:
        *linked_with_index*: the index of the component this one is 
         linked with in the ObjectListStore of the parent based on phase.
        """

        my_kwargs = self.pop_kwargs(
            kwargs, "data_name", "data_layer_atoms", "data_interlayer_atoms",
            "data_atom_relations", "data_atom_ratios", "data_d001",
            "data_default_c", "data_delta_c", "lattice_d", "data_cell_a",
            "data_ucp_a", "data_cell_b", "data_ucp_b", "linked_with_uuid",
            "linked_with_index", "inherit_cell_a", "inherit_cell_b", *[
                prop.label
                for prop in Component.Meta.get_local_persistent_properties()
            ])
        super(Component, self).__init__(**kwargs)
        kwargs = my_kwargs

        # Set up data object
        self._data_object = ComponentData(d001=0.0, delta_c=0.0)

        # Set attributes:
        self.name = self.get_kwarg(kwargs, "", "name", "data_name")

        # Load lists:
        self.layer_atoms = self.get_list(kwargs, [],
                                         "layer_atoms",
                                         "data_layer_atoms",
                                         parent=self)
        self.interlayer_atoms = self.get_list(kwargs, [],
                                              "interlayer_atoms",
                                              "data_interlayer_atoms",
                                              parent=self)
        self.atom_relations = self.get_list(kwargs, [],
                                            "atom_relations",
                                            "data_atom_relations",
                                            parent=self)

        # Add all atom ratios to the AtomRelation list
        for atom_ratio in self.get_list(kwargs, [],
                                        "atom_ratios",
                                        "data_atom_ratios",
                                        parent=self):
            self.atom_relations.append(atom_ratio)

        # Observe the inter-layer atoms, and make sure they get stretched
        for atom in self.interlayer_atoms:
            atom.stretch_values = True
            self.observe_model(atom)

        # Observe the layer atoms
        for atom in self.layer_atoms:
            self.observe_model(atom)

        # Resolve their relations and observe the atom relations
        for relation in self.atom_relations:
            relation.resolve_relations()
            self.observe_model(relation)

        # Connect signals to lists and dicts:
        self._layer_atoms_observer = ListObserver(self._on_layer_atom_inserted,
                                                  self._on_layer_atom_removed,
                                                  prop_name="layer_atoms",
                                                  model=self)
        self._interlayer_atoms_observer = ListObserver(
            self._on_interlayer_atom_inserted,
            self._on_interlayer_atom_removed,
            prop_name="interlayer_atoms",
            model=self)
        self._atom_relations_observer = ListObserver(
            self._on_atom_relation_inserted,
            self._on_atom_relation_removed,
            prop_name="atom_relations",
            model=self)

        # Update lattice values:
        self.d001 = self.get_kwarg(kwargs, self.d001, "d001", "data_d001")
        self._default_c = float(
            self.get_kwarg(kwargs, self.d001, "default_c", "data_default_c"))
        self.delta_c = float(
            self.get_kwarg(kwargs, self.delta_c, "delta_c", "data_delta_c"))
        self.update_lattice_d()

        # Set/Create & observe unit cell properties:
        ucp_a = self.get_kwarg(kwargs, None, "ucp_a", "data_ucp_a",
                               "data_cell_a")
        if isinstance(ucp_a, float):
            ucp_a = UnitCellProperty(name="cell length a",
                                     value=ucp_a,
                                     parent=self)
        ucp_a = self.parse_init_arg(ucp_a,
                                    UnitCellProperty,
                                    child=True,
                                    default_is_class=True,
                                    name="Cell length a [nm]",
                                    parent=self)
        type(self).ucp_a._set(self, ucp_a)
        self.observe_model(ucp_a)

        ucp_b = self.get_kwarg(kwargs, None, "ucp_b", "data_ucp_b",
                               "data_cell_b")
        if isinstance(ucp_b, float):
            ucp_b = UnitCellProperty(name="cell length b",
                                     value=ucp_b,
                                     parent=self)
        ucp_b = self.parse_init_arg(ucp_b,
                                    UnitCellProperty,
                                    child=True,
                                    default_is_class=True,
                                    name="Cell length b [nm]",
                                    parent=self)
        type(self).ucp_b._set(self, ucp_b)
        self.observe_model(ucp_b)

        # Set links:
        self._linked_with_uuid = self.get_kwarg(kwargs, "", "linked_with_uuid")
        self._linked_with_index = self.get_kwarg(kwargs, -1,
                                                 "linked_with_index")

        # Set inherit flags:
        self.inherit_d001 = self.get_kwarg(kwargs, False, "inherit_d001")
        self.inherit_ucp_a = self.get_kwarg(kwargs, False, "inherit_ucp_a",
                                            "inherit_cell_a")
        self.inherit_ucp_b = self.get_kwarg(kwargs, False, "inherit_ucp_b",
                                            "inherit_cell_b")
        self.inherit_default_c = self.get_kwarg(kwargs, False,
                                                "inherit_default_c")
        self.inherit_delta_c = self.get_kwarg(kwargs, False, "inherit_delta_c")
        self.inherit_layer_atoms = self.get_kwarg(kwargs, False,
                                                  "inherit_layer_atoms")
        self.inherit_interlayer_atoms = self.get_kwarg(
            kwargs, False, "inherit_interlayer_atoms")
        self.inherit_atom_relations = self.get_kwarg(kwargs, False,
                                                     "inherit_atom_relations")

    def __repr__(self):
        return "Component(name='%s', linked_with=%r)" % (self.name,
                                                         self.linked_with)

    # ------------------------------------------------------------
    #      Notifications of observable properties
    # ------------------------------------------------------------
    @DataModel.observe("data_changed", signal=True)
    def _on_data_model_changed(self, model, prop_name, info):
        # Check whether the changed model is an AtomRelation or Atom, if so
        # re-apply the atom_relations.
        with self.data_changed.hold():
            if isinstance(model, AtomRelation) or isinstance(model, Atom):
                self._apply_atom_relations()
                self._update_ucp_values()
            if isinstance(model, UnitCellProperty):
                self.data_changed.emit()  # propagate signal

    @DataModel.observe("removed", signal=True)
    def _on_data_model_removed(self, model, prop_name, info):
        # Check whether the removed component is linked with this one, if so
        # clears the link and emits the data_changed signal.
        if model != self and self.linked_with is not None and self.linked_with == model:
            with self.data_changed.hold_and_emit():
                self.linked_with = None

    def _on_layer_atom_inserted(self, atom):
        """Sets the atoms parent and stretch_values property,
        updates the components lattice d-value, and emits a data_changed signal"""
        with self.data_changed.hold_and_emit():
            with self.atoms_changed.hold_and_emit():
                atom.parent = self
                atom.stretch_values = False
                self.observe_model(atom)
                self.update_lattice_d()

    def _on_layer_atom_removed(self, atom):
        """Clears the atoms parent, updates the components lattice d-value, and
        emits a data_changed signal"""
        with self.data_changed.hold_and_emit():
            with self.atoms_changed.hold_and_emit():
                self.relieve_model(atom)
                atom.parent = None
                self.update_lattice_d()

    def _on_interlayer_atom_inserted(self, atom):
        """Sets the atoms parent and stretch_values property, 
        and emits a data_changed signal"""
        with self.data_changed.hold_and_emit():
            with self.atoms_changed.hold_and_emit():
                atom.stretch_values = True
                atom.parent = self

    def _on_interlayer_atom_removed(self, atom):
        """Clears the atoms parent property, 
        and emits a data_changed signal"""
        with self.data_changed.hold_and_emit():
            with self.atoms_changed.hold_and_emit():
                atom.parent = None

    def _on_atom_relation_inserted(self, item):
        item.parent = self
        self.observe_model(item)
        self._apply_atom_relations()

    def _on_atom_relation_removed(self, item):
        self.relieve_model(item)
        item.parent = None
        self._apply_atom_relations()

    # ------------------------------------------------------------
    #      Input/Output stuff
    # ------------------------------------------------------------
    def resolve_json_references(self):
        for atom in type(self).layer_atoms._get(self):
            atom.resolve_json_references()
        for atom in type(self).interlayer_atoms._get(self):
            atom.resolve_json_references()

        type(self).ucp_a._get(self).resolve_json_references()
        type(self).ucp_a._get(self).update_value()
        type(self).ucp_b._get(self).resolve_json_references()
        type(self).ucp_b._get(self).update_value()

        if getattr(self, "_linked_with_uuid", None):
            self.linked_with = type(type(self)).object_pool.get_object(
                self._linked_with_uuid)
            del self._linked_with_uuid
        elif getattr(self, "_linked_with_index",
                     None) and self._linked_with_index != -1:
            warn(
                "The use of object indeces is deprected since version 0.4. Please switch to using object UUIDs.",
                DeprecationWarning)
            self.linked_with = self.parent.based_on.components.get_user_from_index(
                self._linked_with_index)
            del self._linked_with_index

    @classmethod
    def save_components(cls, components, filename):
        """
            Saves multiple components to a single file.
        """
        Component.export_atom_types = True
        for comp in components:
            comp.save_links = False
        with zipfile.ZipFile(filename, 'w', compression=COMPRESSION) as zfile:
            for component in components:
                zfile.writestr(component.uuid, component.dump_object())
        for comp in components:
            comp.save_links = True
        Component.export_atom_types = False

        # After export we change all the UUID's
        # This way, we're sure that we're not going to import objects with
        # duplicate UUID's!
        type(cls).object_pool.change_all_uuids()

    @classmethod
    def load_components(cls, filename, parent=None):
        """
            Returns multiple components loaded from a single file.
        """
        # Before import, we change all the UUID's
        # This way we're sure that we're not going to import objects
        # with duplicate UUID's!
        type(cls).object_pool.change_all_uuids()
        if zipfile.is_zipfile(filename):
            with zipfile.ZipFile(filename, 'r') as zfile:
                for uuid in zfile.namelist():
                    obj = JSONParser.parse(zfile.open(uuid))
                    obj.parent = parent
                    yield obj
        else:
            obj = JSONParser.parse(filename)
            obj.parent = parent
            yield obj

    def json_properties(self):
        if self.phase == None or not self.save_links:
            retval = Storable.json_properties(self)
            for prop in self.Meta.all_properties:
                if getattr(prop, "inherit_flag", False):
                    retval[prop.inherit_flag] = False
        else:
            retval = Storable.json_properties(self)
            retval[
                "linked_with_uuid"] = self.linked_with.uuid if self.linked_with is not None else ""
        return retval

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def get_factors(self, range_stl):
        """
        Get the structure factor for the given range of sin(theta)/lambda values.
        :param range_stl: A 1D numpy ndarray
        """
        return get_factors(range_stl, self.data_object)

    def get_interlayer_stretch_factors(self):
        z_factor = (self.cell_c - self.lattice_d) / (self.default_c -
                                                     self.lattice_d)
        return self.lattice_d, z_factor

    def update_lattice_d(self):
        """
            Updates the lattice_d attribute for this :class:`~.Component`. 
            Should normally not be called from outside the component.
        """
        for atom in self.layer_atoms:
            self.lattice_d = float(max(self.lattice_d, atom.default_z))

    def _apply_atom_relations(self):
        """
        Applies the :class:`~..atom_relations.AtomRelation` objects
        in this component. Should normally not be called from outside the component.
        """
        with self.data_changed.hold_and_emit():
            for relation in self.atom_relations:
                # Clear the 'driven by' flags:
                relation.driven_by_other = False
            for relation in self.atom_relations:
                # Apply the relations, will also take care of flag setting:
                relation.apply_relation()

    def _update_ucp_values(self):
        """
        Updates the :class:`~..unit_cell_prop.UnitCellProperty` objects in this
        component. Should normally not be called from outside the component.
        """
        with self.data_changed.hold():
            for ucp in [self._ucp_a, self._ucp_b]:
                ucp.update_value()

    def get_volume(self):
        """
        Get the volume for this :class:`~.Component`.
        Will always return a value >= 1e-25, to prevent division-by-zero
        errors in calculation code.  
        """
        return max(self.cell_a * self.cell_b * self.cell_c, 1e-25)

    def get_weight(self):
        """
        Get the total atomic weight for this 
        :class:`~.Component`. 
        """
        weight = 0
        for atom in (self.layer_atoms + self.interlayer_atoms):
            weight += atom.weight
        return weight

    # ------------------------------------------------------------
    #      AtomRelation list related
    # ------------------------------------------------------------
    def move_atom_relation_up(self, relation):
        """
        Move the passed :class:`~..atom_relations.AtomRelation`
        up one slot
        """
        index = self.atom_relations.index(relation)
        del self.atom_relations[index]
        self.atom_relations.insert(max(index - 1, 0), relation)

    def move_atom_relation_down(self, relation):
        """
        Move the passed :class:`~..atom_relations.AtomRelation`
        down one slot
        """
        index = self.atom_relations.index(relation)
        del self.atom_relations[index]
        self.atom_relations.insert(min(index + 1, len(self.atom_relations)),
                                   relation)

    pass  # end of class
Example #10
0
class Goniometer(DataModel, Storable):
    """
    The Goniometer class contains all the information related to the
    X-ray diffraction goniometer, e.g. wavelength, radius, slit sizes, ...
    """

    # MODEL INTEL:
    class Meta(DataModel.Meta):
        store_id = "Goniometer"

    _data_object = None

    @property
    def data_object(self):
        self._data_object.wavelength = self.wavelength
        x, y = self.wavelength_distribution.get_xy_data()
        self._data_object.wavelength_distribution = list(
            zip(x.tolist(), y.tolist()))
        return self._data_object

    specimen = property(DataModel.parent.fget, DataModel.parent.fset)

    # PROPERTIES:

    #: Start angle (in °2-theta, only  used when calculating without
    #: experimental data)
    min_2theta = FloatProperty(default=3.0,
                               text="Start angle",
                               minimum=0.0,
                               maximum=180.0,
                               tabular=True,
                               persistent=True,
                               visible=True,
                               signal_name="data_changed",
                               widget_type="spin",
                               mix_with=(DataMixin, SignalMixin))

    #: End angle (in °2-theta, only  used when calculating without
    #: experimental data)
    max_2theta = FloatProperty(default=3.0,
                               text="End angle",
                               minimum=0.0,
                               maximum=180.0,
                               tabular=True,
                               persistent=True,
                               visible=True,
                               signal_name="data_changed",
                               widget_type="spin",
                               mix_with=(DataMixin, SignalMixin))

    #: The number of steps between start and end angle
    steps = IntegerProperty(default=2500,
                            text="Steps",
                            minimum=0,
                            maximum=10000,
                            tabular=True,
                            persistent=True,
                            visible=True,
                            signal_name="data_changed",
                            widget_type="spin",
                            mix_with=(DataMixin, SignalMixin))

    #: The wavelength distribution
    wavelength_distribution = LabeledProperty(default=None,
                                              text="Wavelength distribution",
                                              tabular=False,
                                              persistent=True,
                                              visible=True,
                                              signal_name="data_changed",
                                              widget_type="xy_list_view",
                                              mix_with=(SignalMixin,
                                                        ObserveMixin))

    @FloatProperty(default=0.154056,
                   text="Wavelength",
                   tabular=True,
                   persistent=False,
                   visible=False,
                   signal_name="data_changed",
                   mix_with=(ReadOnlyMixin, ))
    def wavelength(self):
        """The wavelength of the generated X-rays (in nm)"""
        # Get the dominant wavelength in the distribution:
        x, y = self.wavelength_distribution.get_xy_data()
        wl = float(x[np.argmax(y)])
        return wl

    #: Flag indicating if the first soller slit is present or not
    has_soller1 = BoolProperty(default=True,
                               text="Soller 1",
                               tabular=True,
                               persistent=True,
                               visible=True,
                               signal_name="data_changed",
                               mix_with=(DataMixin, SignalMixin))

    #: The first Soller slit size (in °)
    soller1 = FloatProperty(default=2.3,
                            text="Soller 1",
                            minimum=0.0,
                            maximum=10.0,
                            tabular=True,
                            persistent=True,
                            visible=True,
                            signal_name="data_changed",
                            widget_type="spin",
                            mix_with=(DataMixin, SignalMixin))

    #: Flag indicating if the second soller slit is present or not
    has_soller2 = BoolProperty(default=True,
                               text="Soller 2",
                               tabular=True,
                               persistent=True,
                               visible=True,
                               signal_name="data_changed",
                               mix_with=(DataMixin, SignalMixin))

    #: The second Soller slit size (in °)
    soller2 = FloatProperty(default=2.3,
                            text="Soller 2",
                            minimum=0.0,
                            maximum=10.0,
                            tabular=True,
                            persistent=True,
                            visible=True,
                            signal_name="data_changed",
                            widget_type="spin",
                            mix_with=(DataMixin, SignalMixin))

    #: The radius of the goniometer (in cm)
    radius = FloatProperty(default=24.0,
                           text="Radius",
                           minimum=0.0,
                           maximum=200.0,
                           tabular=True,
                           persistent=True,
                           visible=True,
                           signal_name="data_changed",
                           widget_type="spin",
                           mix_with=(DataMixin, SignalMixin))

    #: The divergence slit mode of the goniometer
    divergence_mode = StringChoiceProperty(
        default=settings.DEFAULT_DIVERGENCE_MODE,
        text="Divergence mode",
        visible=True,
        persistent=True,
        choices=settings.DIVERGENCE_MODES,
        signal_name="data_changed",
        mix_with=(
            DataMixin,
            SignalMixin,
        ))

    #: The divergence slit size (if fixed) or irradiated sample length (if automatic)
    divergence = FloatProperty(default=0.5,
                               text="Divergence",
                               minimum=0.0,
                               maximum=90.0,
                               tabular=True,
                               persistent=True,
                               visible=True,
                               signal_name="data_changed",
                               widget_type="spin",
                               mix_with=(DataMixin, SignalMixin))

    #: Flag indicating if the second soller slit is present or not
    has_absorption_correction = BoolProperty(default=False,
                                             text="Absorption correction",
                                             tabular=True,
                                             persistent=True,
                                             visible=True,
                                             signal_name="data_changed",
                                             mix_with=(DataMixin, SignalMixin))

    #: The actual sample length
    sample_length = FloatProperty(default=1.25,
                                  text="Sample length [cm]",
                                  minimum=0.0,
                                  visible=True,
                                  persistent=True,
                                  tabular=True,
                                  signal_name="data_changed",
                                  widget_type="spin",
                                  mix_with=(DataMixin, SignalMixin))

    #: The sample surface density
    sample_surf_density = FloatProperty(default=20.0,
                                        text="Sample surface density [mg/cm²]",
                                        minimum=0.0,
                                        visible=True,
                                        persistent=True,
                                        tabular=True,
                                        signal_name="data_changed",
                                        widget_type="spin",
                                        mix_with=(DataMixin, SignalMixin))

    #: The sample mass absorption coefficient
    absorption = FloatProperty(default=45.0,
                               text="Mass attenuation coeff. [cm²/g]",
                               minimum=0.0,
                               visible=True,
                               persistent=True,
                               tabular=True,
                               signal_name="data_changed",
                               widget_type="spin",
                               mix_with=(DataMixin, SignalMixin))

    #: Angular value (in degrees) for a monochromator correction - use 28.44 for silicon and 26.53 for carbon.
    mcr_2theta = FloatProperty(default=0.0,
                               text="Monochromator 2θ",
                               minimum=0.0,
                               maximum=90.0,
                               tabular=True,
                               persistent=True,
                               visible=True,
                               signal_name="data_changed",
                               widget_type="spin",
                               mix_with=(
                                   DataMixin,
                                   SignalMixin,
                               ))

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        """
            Constructor takes any of its properties as a keyword argument.
            
            In addition to the above, the constructor still supports the 
            following deprecated keywords, mapping to a current keyword:
                - lambda: maps to wavelength
                
            Any other arguments or keywords are passed to the base class.
        """
        my_kwargs = self.pop_kwargs(
            kwargs, "data_radius", "data_divergence", "data_soller1",
            "data_soller2", "data_min_2theta", "data_max_2theta",
            "data_lambda", "lambda", "wavelength", "has_ads", "ads_fact",
            "ads_phase_fact", "ads_phase_shift", "ads_const", *[
                prop.label
                for prop in Goniometer.Meta.get_local_persistent_properties()
            ])
        super(Goniometer, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        self._data_object = GonioData()

        with self.data_changed.hold():
            self.radius = self.get_kwarg(kwargs, 24.0, "radius", "data_radius")

            #: Parse divergence mode (including old-style keywords):
            new_div_mode = self.get_kwarg(kwargs, None, "divergence_mode")
            if new_div_mode is None:  # old style project
                old_ads = self.get_kwarg(kwargs, None, "has_ads")
                if old_ads is not None and old_ads:  # if we have ads, set as such:
                    new_div_mode = "AUTOMATIC"
                else:  # otherwise it was angular fixed slits
                    new_div_mode = settings.DEFAULT_DIVERGENCE_MODE
            self.divergence_mode = new_div_mode

            # Divergence value:
            self.divergence = self.get_kwarg(kwargs, 0.5, "divergence",
                                             "data_divergence")

            # Monochromator correction:
            self.mcr_2theta = float(self.get_kwarg(kwargs, 0, "mcr_2theta"))

            # Soller slits:
            self.has_soller1 = self.get_kwarg(kwargs, True, "has_soller1")
            self.soller1 = float(
                self.get_kwarg(kwargs, 2.3, "soller1", "data_soller1"))
            self.has_soller2 = self.get_kwarg(kwargs, True, "has_soller2")
            self.soller2 = float(
                self.get_kwarg(kwargs, 2.3, "soller2", "data_soller2"))

            # Angular range settings for calculated patterns:
            self.min_2theta = float(
                self.get_kwarg(kwargs, 3.0, "min_2theta", "data_min_2theta"))
            self.max_2theta = float(
                self.get_kwarg(kwargs, 45.0, "max_2theta", "data_max_2theta"))
            self.steps = int(self.get_kwarg(kwargs, 2500, "steps"))

            # Sample characteristics
            self.sample_length = float(
                self.get_kwarg(kwargs, settings.DEFAULT_SAMPLE_LENGTH,
                               "sample_length"))
            self.absorption = float(self.get_kwarg(kwargs, 45.0, "absorption"))
            self.sample_surf_density = float(
                self.get_kwarg(kwargs, 20.0, "sample_surf_density"))
            self.has_absorption_correction = bool(
                self.get_kwarg(kwargs, False, "has_absorption_correction"))

            wavelength = self.get_kwarg(kwargs, None, "wavelength",
                                        "data_lambda", "lambda")
            if not "wavelength_distribution" in kwargs and wavelength is not None:
                default_wld = [
                    [wavelength, 1.0],
                ]
            else:
                # A Cu wld:
                default_wld = [
                    [0.1544426, 0.955148885],
                    [0.153475, 0.044851115],
                ]
            self.wavelength_distribution = StorableXYData(
                data=self.get_kwarg(kwargs, list(zip(
                    *default_wld)), "wavelength_distribution"))

    # ------------------------------------------------------------
    #      Input/Output stuff
    # ------------------------------------------------------------
    def __reduce__(self):
        return (type(self), ((), self.json_properties()))

    def json_properties(self):
        props = Storable.json_properties(self)
        props[
            "wavelength_distribution"] = self.wavelength_distribution._serialize_data(
            )
        return props

    def reset_from_file(self, gonfile):
        """
        Loads & sets the parameters from the goniometer JSON file
        specified by `gonfile`, can be a filename or a file-like object.
        """
        new_gonio = JSONParser.parse(gonfile)
        with self.data_changed.hold():
            for prop in self.Meta.all_properties:
                if prop.persistent:
                    if prop.label == "wavelength_distribution":
                        self.wavelength_distribution.clear()
                        self.wavelength_distribution.set_data(
                            *new_gonio.wavelength_distribution.get_xy_data())
                    elif prop.label != "uuid":
                        setattr(self, prop.label,
                                getattr(new_gonio, prop.label))

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def get_nm_from_t(self, theta):
        """Converts a theta position to a nanometer value"""
        return get_nm_from_t(theta,
                             wavelength=self.wavelength,
                             zero_for_inf=True)

    def get_nm_from_2t(self, twotheta):
        """Converts a 2-theta position to a nanometer value"""
        return get_nm_from_2t(twotheta,
                              wavelength=self.wavelength,
                              zero_for_inf=True)

    def get_t_from_nm(self, nm):
        """ Converts a nanometer value to a theta position"""
        return get_t_from_nm(nm, wavelength=self.wavelength)

    def get_2t_from_nm(self, nm):
        """ Converts a nanometer value to a 2-theta position"""
        return get_2t_from_nm(nm, wavelength=self.wavelength)

    def get_default_theta_range(self, as_radians=True):
        """
        Returns a numpy array containing the theta values as radians from
        `min_2theta` to `max_2theta` with `steps` controlling the interval.
        When `as_radians` is set to False the values are returned as degrees. 
        """
        def torad(val):
            if as_radians:
                return radians(val)
            else:
                return val

        min_theta = torad(self.min_2theta * 0.5)
        max_theta = torad(self.max_2theta * 0.5)
        delta_theta = float(max_theta - min_theta) / float(self.steps)
        theta_range = (min_theta + delta_theta * np.arange(
            0, self.steps, dtype=float)) + delta_theta * 0.5
        return theta_range

    def get_lorentz_polarisation_factor(self, range_theta, sigma_star):
        """
            Calculates Lorentz polarization factor for the given theta range
            and sigma-star value using the information about the goniometer's
            geometry.
        """
        return get_lorentz_polarisation_factor(range_theta, sigma_star,
                                               self.soller1, self.soller2,
                                               self.mcr_2theta)

    def get_ADS_to_fixed_correction(self, range_theta):
        """
            Returns a correction range that will convert ADS data to fixed slit
            data. Use with caution.
        """
        return 1.0 / get_fixed_to_ads_correction_range(range_theta,
                                                       self.data_object)

    pass  # end of class
Example #11
0
class Statistics(ChildModel):
    # PROPERTIES:
    specimen = property(ChildModel.parent.fget, ChildModel.parent.fset)

    @IntegerProperty(default=0,
                     label="Points",
                     visible=False,
                     mix_with=(ReadOnlyMixin, ))
    def points(self):
        try:
            e_ex, e_ey, e_cx, e_cy = self.specimen.get_exclusion_xy(
            )  #@UnusedVariable
            return e_ex.size
        except:
            pass
        return 0

    Rp = FloatProperty(default=None, label="Rp", visible=True)
    Rwp = FloatProperty(default=None, label="Rwp", visible=True)
    Rpder = FloatProperty(default=None, label="Rpder", visible=True)

    residual_pattern = LabeledProperty(default=None, label="Residual pattern")
    der_exp_pattern = LabeledProperty(default=None,
                                      label="Derived experimental pattern")
    der_calc_pattern = LabeledProperty(default=None,
                                       label="Derived calculated pattern")
    der_residual_pattern = LabeledProperty(default=None,
                                           label="Derived residual pattern")

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        super(Statistics, self).__init__(*args, **kwargs)
        self.observe_model(self.parent)

    # ------------------------------------------------------------
    #      Notifications of observable properties
    # ------------------------------------------------------------
    @ChildModel.observe("parent", assign=True, after=True)
    def on_parent_changed(self, model, prop_name, info):
        self.update_statistics()

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def _get_experimental(self):
        if self.specimen is not None:
            x, y = self.specimen.experimental_pattern.get_xy_data()
            return x.copy(), y.copy()
        else:
            return None, None

    def _get_calculated(self):
        if self.specimen is not None:
            x, y = self.specimen.calculated_pattern.get_xy_data()
            return x.copy(), y.copy()
        else:
            return None, None

    def scale_factor_y(self, offset):
        return self.specimen.scale_factor_y(offset) if self.specimen else (
            1.0, offset)

    def update_statistics(self, derived=False):
        # Clear factors:
        self.Rp = 0
        self.Rwp = 0
        self.Rpder = 0

        # Setup lines if not yet done:
        if self.residual_pattern == None:
            self.residual_pattern = PyXRDLine(label="Residual",
                                              color="#000000",
                                              lw=0.5,
                                              parent=self)

        if self.der_exp_pattern == None:
            self.der_exp_pattern = PyXRDLine(label="Exp. 1st der.",
                                             color="#000000",
                                             lw=2,
                                             parent=self)

        if self.der_calc_pattern == None:
            self.der_calc_pattern = PyXRDLine(label="Calc. 1st der.",
                                              color="#AA0000",
                                              lw=2,
                                              parent=self)

        if self.der_residual_pattern == None:
            self.der_residual_pattern = PyXRDLine(label="1st der. residual",
                                                  color="#AA00AA",
                                                  lw=1,
                                                  parent=self)

        # Get data:
        exp_x, exp_y = self._get_experimental()
        cal_x, cal_y = self._get_calculated()
        der_exp_y, der_cal_y = None, None
        del cal_x  # don't need this, is the same as exp_x

        # Try to get statistics, if it fails, just clear and inform the user
        try:
            if cal_y is not None and exp_y is not None and cal_y.size > 0 and exp_y.size > 0:
                # Get the selector for areas to consider in the statistics:
                selector = self.specimen.get_exclusion_selector()

                if derived:
                    # Calculate and set first derivate patterns:
                    der_exp_y, der_cal_y = derive(exp_y), derive(cal_y)
                    self.der_exp_pattern.set_data(exp_x, der_exp_y)
                    self.der_calc_pattern.set_data(exp_x, der_cal_y)

                # Calculate and set residual pattern:
                self.residual_pattern.set_data(exp_x, exp_y - cal_y)
                if derived:
                    self.der_residual_pattern.set_data(exp_x,
                                                       der_exp_y - der_cal_y)

                # Calculate 'included' R values:
                self.Rp = Rp(exp_y[selector], cal_y[selector])
                self.Rwp = Rpw(exp_y[selector], cal_y[selector])
                if derived:
                    self.Rpder = Rp(der_exp_y[selector], der_cal_y[selector])
            else:
                self.residual_pattern.clear()
                self.der_exp_pattern.clear()
                self.der_calc_pattern.clear()
        except:
            self.residual_pattern.clear()
            self.der_exp_pattern.clear()
            self.der_calc_pattern.clear()
            logger.error(
                "Error occurred when trying to calculate statistics, aborting calculation!"
            )
            raise

    pass  # end of class
Example #12
0
class RefinableWrapper(ChildModel):
    """
        Wrapper class for refinables easing the retrieval of certain
        properties for the different types of refinables.
        Can be used with an ObjectTreeStore.
    """

    # MODEL INTEL:
    class Meta(ChildModel.Meta):
        parent_alias = "mixture"

    # PROPERTIES:

    #: The wrapped object
    obj = LabeledProperty(default=None, text="Wrapped object", tabular=True)

    #: The property descriptor object for the attribute
    prop_descr = LabeledProperty(default=None,
                                 text="Property descriptor",
                                 tabular=True)

    #: The Property label:
    @StringProperty(default="",
                    text="Property label",
                    tabular=True,
                    mix_with=(ReadOnlyMixin, ))
    def label(self):
        return self.prop_descr.label

    #: A flag indicating whether this is wrapper is representing the group
    #: (True) or a member of the group (False):
    is_grouper = BoolProperty(default=False,
                              text="Is grouper",
                              tabular=True,
                              mix_with=(ReadOnlyMixin, ))

    #: The inherit attribute name:
    @LabeledProperty(default=None,
                     text="Inherit from label",
                     mix_with=(ReadOnlyMixin, ))
    def inherit_from(self):
        return self.prop_descr.inherit_from if self.prop_descr else None

    #: The (possibly mathtext) label for the refinable property:
    @StringProperty(default="",
                    text="Title",
                    tabular=True,
                    mix_with=(ReadOnlyMixin, ))
    def title(self):
        if (isinstance(self.obj, RefinementGroup)
                and self.is_grouper) or isinstance(self.obj, RefinementValue):
            return self.obj.refine_title
        else:
            if getattr(self.prop_descr, "math_text", None) is not None:
                return self.prop_descr.math_text
            else:
                return self.prop_descr.text

    #: The (pure text) label for the refinable property:
    @StringProperty(default="",
                    text="Text title",
                    tabular=True,
                    mix_with=(ReadOnlyMixin, ))
    def text_title(self):
        if (isinstance(self.obj, RefinementGroup)
                and self.is_grouper) or isinstance(self.obj, RefinementValue):
            return self.obj.refine_title
        else:
            return self.prop_descr.text

    @StringProperty(default="",
                    text="Descriptor",
                    tabular=True,
                    mix_with=(ReadOnlyMixin, ))
    def text_descriptor(self):
        """ Return a longer title that also describes this property's relations """

        # This gets the phase and/or component name for the group or value:
        data = self.obj.refine_descriptor_data

        # Here we still need to get the actual property title:
        data["property_name"] = self.text_title

        return "%(phase_name)s | %(component_name)s | %(property_name)s" % data

    #: The actual value of the refinable property:
    @LabeledProperty(default=None, text="Value", tabular=True)
    def value(self):
        if isinstance(self.obj, RefinementValue):
            return self.obj.refine_value
        elif not self.is_grouper:
            return getattr(self.obj, self.label)
        else:
            return ""

    @value.setter
    def value(self, value):
        value = max(min(value, self.value_max), self.value_min)
        if self.is_grouper:
            raise AttributeError(
                "Cannot set the value for a grouping RefinableWrapper")
        elif isinstance(self.obj, RefinementValue):
            self.obj.refine_value = value
        else:
            setattr(self.obj, self.label, value)

    #: Whether or not this property is inherited from another object
    @BoolProperty(default=False,
                  text="Inherited",
                  tabular=True,
                  mix_with=(ReadOnlyMixin, ))
    def inherited(self):
        return self.inherit_from is not None and hasattr(
            self.obj, self.inherit_from) and getattr(self.obj,
                                                     self.inherit_from)

    #: Whether or not this property is actually refinable
    @BoolProperty(default=False,
                  text="Refinable",
                  tabular=True,
                  mix_with=(ReadOnlyMixin, ))
    def refinable(self):
        if isinstance(self.obj, _RefinementBase):
            # We have a _RefinementBase property (group or value)
            if isinstance(self.obj, RefinementGroup):
                if self.is_grouper:  # the grouper itself
                    return False
                else:  # attribute of the grouper
                    return (not self.inherited) and self.obj.children_refinable
            elif isinstance(self.obj, RefinementValue):
                return (not self.inherited) and self.obj.is_refinable
        else:
            # This is actually impossible, but what the hack...
            return (not self.inherited)

    #: The refinement info object for the refinable property
    @LabeledProperty(default=None,
                     text="Refinement info",
                     tabular=True,
                     mix_with=(ReadOnlyMixin, ))
    def ref_info(self):
        if (isinstance(self.obj, RefinementGroup)
                and self.is_grouper) or isinstance(self.obj, RefinementValue):
            return self.obj.refine_info
        else:
            name = self.prop_descr.get_refinement_info_name()
            if name is not None:
                ref_info = getattr(self.obj, name)
                return ref_info
            else:
                raise AttributeError(
                    "Cannot find refine info model for attribute '%s' on '%s'"
                    % (self.label, self.obj))

    #: The minimum value for the refinable property
    @LabeledProperty(default=None, text="Minimum value", tabular=True)
    def value_min(self):
        return self.ref_info.minimum if self.ref_info else None

    @value_min.setter
    def value_min(self, value):
        if self.ref_info:
            self.ref_info.minimum = value

    #: The maximum value of the refinable property
    @LabeledProperty(default=None, text="Maximum value", tabular=True)
    def value_max(self):
        return self.ref_info.maximum if self.ref_info else None

    @value_max.setter
    def value_max(self, value):
        if self.ref_info:
            self.ref_info.maximum = value

    #: Wether this property is selected for refinement
    @BoolProperty(default=False, text="Refine", tabular=True)
    def refine(self):
        return self.ref_info.refine if self.ref_info else False

    @refine.setter
    def refine(self, value):
        if self.ref_info:
            self.ref_info.refine = value and self.refinable

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        """
            Valid keyword arguments for a RefinableWrapper are:
                obj: the object we are wrapping a parameter for
                prop or prop_descr: the property descriptor
                is_grouper: whether or not this is a grouper object
        """
        my_kwargs = self.pop_kwargs(kwargs, "obj", "prop", "prop_descr",
                                    "is_grouper")
        super(RefinableWrapper, self).__init__(**kwargs)
        kwargs = my_kwargs

        self.obj = self.get_kwarg(kwargs, None, "obj")
        self.prop_descr = self.get_kwarg(kwargs, None, "prop_descr", "prop")
        self._is_grouper = self.get_kwarg(kwargs, False, "is_grouper")

    pass  # end of class
Example #13
0
class UnitCellProperty(ComponentPropMixin,
                       RefinementValue,
                       DataModel,
                       Storable,
                       metaclass=PyXRDRefinableMeta):
    """
        UnitCellProperty's are an integral part of a component and allow to 
        calculate the dimensions of the unit cell based on compositional
        information such as the iron content.
        This class is not responsible for keeping its value up-to-date.
        With other words, it is the responsibility of the higher-level class
        to call the 'update_value' method on this object whenever it emits a
        'data_changed' signal. The reason for this is to prevent infinite 
        recursion errors. 
    """
    class Meta(DataModel.Meta):
        store_id = "UnitCellProperty"

    component = property(DataModel.parent.fget, DataModel.parent.fset)

    # PROPERTIES:

    #: The UnitCellProperty name
    name = StringProperty(default="",
                          text="Name",
                          visible=False,
                          persistent=False,
                          signal_name="visuals_changed",
                          mix_with=(SignalMixin, ))

    #: Flag indicating if this UnitCellProperty is enabled
    enabled = BoolProperty(default=False,
                           text="Enabled",
                           visible=True,
                           persistent=True,
                           set_action_name="update_value",
                           mix_with=(SetActionMixin, ))

    #: Flag indicating if this UnitCellProperty is inherited
    inherited = BoolProperty(default=False,
                             text="Inherited",
                             visible=False,
                             persistent=False,
                             set_action_name="update_value",
                             mix_with=(SetActionMixin, ))

    #: The value of the UnitCellProperty
    value = FloatProperty(default=0.0,
                          text="Value",
                          visible=True,
                          persistent=True,
                          refinable=True,
                          widget_type='float_entry',
                          set_action_name="update_value",
                          mix_with=(SetActionMixin, RefinableMixin))

    #: The factor of the UnitCellProperty (if enabled and not constant)
    factor = FloatProperty(default=1.0,
                           text="Factor",
                           visible=True,
                           persistent=True,
                           widget_type='float_entry',
                           set_action_name="update_value",
                           mix_with=(SetActionMixin, ))

    #: The constant of the UnitCellProperty (if enabled and not constant)
    constant = FloatProperty(default=0.0,
                             text="Constant",
                             visible=True,
                             persistent=True,
                             widget_type='float_entry',
                             set_action_name="update_value",
                             mix_with=(SetActionMixin, ))

    _temp_prop = None  # temporary, JSON-style prop
    prop = LabeledProperty(default=None,
                           text="Property",
                           visible=True,
                           persistent=True,
                           widget_type='combo',
                           set_action_name="update_value",
                           mix_with=(SetActionMixin, ))

    # REFINEMENT VALUE IMPLEMENTATION:
    @property
    def refine_title(self):
        return self.name

    @property
    def refine_descriptor_data(self):
        return dict(phase_name=self.component.phase.refine_title,
                    component_name=self.component.refine_title)

    @property
    def refine_value(self):
        return self.value

    @refine_value.setter
    def refine_value(self, value):
        if not self.enabled:
            self.value = value

    @property
    def refine_info(self):
        return self.value_ref_info

    @property
    def is_refinable(self):
        return not (self.enabled or self.inherited)

    # ------------------------------------------------------------
    #      Initialisation and other internals
    # ------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        keys = [
            prop.label for prop in
            UnitCellProperty.Meta.get_local_persistent_properties()
        ]
        keys.extend([
            "data_%s" % prop.label for prop in
            UnitCellProperty.Meta.get_local_persistent_properties()
        ])
        my_kwargs = self.pop_kwargs(kwargs, "name", *keys)
        super(UnitCellProperty, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        with self.data_changed.hold_and_emit():
            self.name = self.get_kwarg(kwargs, self.name, "name", "data_name")
            self.value = self.get_kwarg(kwargs, self.value, "value",
                                        "data_value")
            self.factor = self.get_kwarg(kwargs, self.factor, "factor",
                                         "data_factor")
            self.constant = self.get_kwarg(kwargs, self.constant, "constant",
                                           "data_constant")
            self.enabled = self.get_kwarg(kwargs, self.enabled, "enabled",
                                          "data_enabled")

            self._temp_prop = self.get_kwarg(kwargs, self.prop, "prop",
                                             "data_prop")

    # ------------------------------------------------------------
    #      Input/Output stuff
    # ------------------------------------------------------------
    def json_properties(self):
        retval = Storable.json_properties(self)
        if retval["prop"]:
            # Try to replace objects with their uuid's:
            try:
                retval["prop"] = [
                    getattr(retval["prop"][0], 'uuid', retval["prop"][0]),
                    retval["prop"][1]
                ]
            except:
                logger.exception(
                    "Error when trying to interpret UCP JSON properties")
                pass  # ignore
        return retval

    def resolve_json_references(self):
        if getattr(self, "_temp_prop", None):
            self._temp_prop = list(self._temp_prop)
            if isinstance(self._temp_prop[0], str):
                obj = type(type(self)).object_pool.get_object(
                    self._temp_prop[0])
                if obj:
                    self._temp_prop[0] = obj
                    self.prop = self._temp_prop
                else:
                    self._temp_prop = None
            self.prop = self._temp_prop
            del self._temp_prop

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def create_prop_store(self, extra_props=[]):
        assert (self.component is not None)
        store = Gtk.ListStore(object, str, str)
        # use private properties so we connect to the actual object stores and not the inherited ones
        for atom in self.component._layer_atoms:
            store.append([atom, "pn", atom.name])
        for atom in self.component._interlayer_atoms:
            store.append([atom, "pn", atom.name])
        for prop in extra_props:
            store.append(prop)
        return store

    def get_value_of_prop(self):
        try:
            return getattr(*self.prop)
        except:
            return 0.0

    def update_value(self):
        if self.enabled:
            self._value = float(self.factor * self.get_value_of_prop() +
                                self.constant)
            self.data_changed.emit()

    pass  # end of class
Example #14
0
class AtomRatio(AtomRelation):

    # MODEL INTEL:
    class Meta(AtomRelation.Meta):
        store_id = "AtomRatio"
        allowed_relations = {
            "AtomRatio": [
                ("__internal_sum__", lambda o: "%s: SUM" % o.name),
                ("value", lambda o: "%s: RATIO" % o.name),
            ],
            "AtomContents": [("value", lambda o: o.name)],
        }

    # SIGNALS:

    # PROPERTIES:
    #: The sum of the two atoms
    sum = FloatProperty(default=1.0,
                        text="Sum",
                        minimum=0.0,
                        visible=True,
                        persistent=True,
                        tabular=True,
                        widget_type='float_entry',
                        signal_name="data_changed",
                        mix_with=(SignalMixin, ))

    def __internal_sum__(self, value):
        """
        Special setter for other AtomRelation objects depending on the value of
        the sum of the AtomRatio. This can be used to have multi-substitution by
        linking two (or more) AtomRatio's. Eg Al-by-Mg-&-Fe:
        
        AtomRatioMgAndFeForAl -> links together Al content and Fe+Mg content
        
                              => sum = e.g. 4 set by user
                              
        AtomRatioMgForFe      -> links together the Fe and Mg content
        
                              => sum = set by previous ratio.
        """
        self._sum = float(value)
        self.apply_relation()

    __internal_sum__ = property(fset=__internal_sum__)

    def _set_driven_flag_for_prop(self, prop, *args):
        """Internal method used to safely set the driven_by_other flag on an object.
        Subclasses can override to provide a check on the property set by the driver."""
        if prop != "__internal_sum__":
            super(AtomRatio, self)._set_driven_flag_for_prop(prop)

    def _set_atom(self, value, label=None):
        if not self._safe_is_referring(value[0]):
            getattr(type(self), label)._set(self, value)

    #: The substituting atom
    atom1 = LabeledProperty(default=[None, None],
                            text="Substituting Atom",
                            visible=True,
                            persistent=True,
                            tabular=True,
                            signal_name="data_changed",
                            fset=partial(_set_atom, label="atom1"),
                            mix_with=(SignalMixin, ))

    #: The Original atom
    atom2 = LabeledProperty(default=[None, None],
                            text="Original Atom",
                            visible=True,
                            persistent=True,
                            tabular=True,
                            signal_name="data_changed",
                            fset=partial(_set_atom, label="atom2"),
                            mix_with=(SignalMixin, ))

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self, *args, **kwargs):  # @ReservedAssignment
        """
            Valid keyword arguments for an AtomRatio are:
                sum: the sum of the atoms contents
                atom1: a tuple containing the first atom and its property name to read/set
                atom2: a tuple containing the first atom and its property name to read/set
            The value property is the 'ratio' of the first atom over the sum of both
        """
        my_kwargs = self.pop_kwargs(
            kwargs, "data_sum", "prop1", "data_prop1", "data_prop2", "prop2",
            *[
                prop.label
                for prop in AtomRatio.Meta.get_local_persistent_properties()
            ])
        super(AtomRatio, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        self.sum = self.get_kwarg(kwargs, self.sum, "sum", "data_sum")

        self._unresolved_atom1 = self._parseattr(
            self.get_kwarg(kwargs, [None, None], "atom1", "prop1",
                           "data_prop1"))
        self._unresolved_atom2 = self._parseattr(
            self.get_kwarg(kwargs, [None, None], "atom2", "prop2",
                           "data_prop2"))

    # ------------------------------------------------------------
    #      Input/Output stuff
    # ------------------------------------------------------------
    def json_properties(self):
        retval = Storable.json_properties(self)
        retval["atom1"] = [
            retval["atom1"][0].uuid if retval["atom1"][0] else None,
            retval["atom1"][1]
        ]
        retval["atom2"] = [
            retval["atom2"][0].uuid if retval["atom2"][0] else None,
            retval["atom2"][1]
        ]
        return retval

    def resolve_relations(self):
        if isinstance(self._unresolved_atom1[0], str):
            self._unresolved_atom1[0] = type(
                type(self)).object_pool.get_object(self._unresolved_atom1[0])
        self.atom1 = list(self._unresolved_atom1)
        del self._unresolved_atom1
        if isinstance(self._unresolved_atom2[0], str):
            self._unresolved_atom2[0] = type(
                type(self)).object_pool.get_object(self._unresolved_atom2[0])
        self.atom2 = list(self._unresolved_atom2)
        del self._unresolved_atom2

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def apply_relation(self):
        if self.enabled and self.applicable:
            for value, (atom, prop) in [(self.value, self.atom1),
                                        (1.0 - self.value, self.atom2)]:
                if atom and prop:
                    # do not fire events, just set attributes:
                    with atom.data_changed.ignore():
                        setattr(atom, prop, value * self.sum)
                        if hasattr(atom, "_set_driven_flag_for_prop"):
                            atom._set_driven_flag_for_prop(prop)

    def iter_references(self):
        for atom in [self.atom1[0], self.atom2[0]]:
            yield atom

    pass  # end of class
Example #15
0
class _AbstractCSDSDistribution(DataModel,
                                Storable,
                                metaclass=PyXRDRefinableMeta):

    # MODEL INTEL:
    class Meta(DataModel.Meta):
        description = "Abstract CSDS distr."
        explanation = ""

    phase = property(DataModel.parent.fget, DataModel.parent.fset)

    # PROPERTIES:
    _data_object = None

    @property
    def data_object(self):
        return self._data_object

    inherited = BoolProperty(default=False,
                             text="Inherited",
                             visible=False,
                             persistent=False,
                             signal_name="data_changed",
                             mix_with=(SignalMixin, ))

    distrib = LabeledProperty(default=None,
                              text="CSDS Distribution",
                              tabular=True,
                              visible=False,
                              persistent=False,
                              get_action_name="_update_distribution",
                              signal_name="data_changed",
                              mix_with=(
                                  SignalMixin,
                                  GetActionMixin,
                              ))

    # PROPERTIES:
    #: The maximum value of this distribution
    maximum = FloatProperty(default=0.0,
                            text="Maximum CSDS",
                            minimum=1,
                            maximum=1000,
                            tabular=True,
                            persistent=False,
                            visible=False,
                            mix_with=(ReadOnlyMixin, DataMixin))

    #: The minimum value of this distribution
    minimum = FloatProperty(default=0.0,
                            text="Maximum CSDS",
                            minimum=1,
                            maximum=1000,
                            tabular=True,
                            persistent=False,
                            visible=False,
                            mix_with=(ReadOnlyMixin, DataMixin))

    average = FloatProperty(default=0.0,
                            text="Average CSDS",
                            minimum=1,
                            maximum=200,
                            tabular=True,
                            persistent=True,
                            visible=True,
                            refinable=True,
                            signal_name="data_changed",
                            set_action_name="_update_distribution",
                            mix_with=(SignalMixin, DataMixin, RefinableMixin,
                                      SetActionMixin))

    alpha_scale = FloatProperty(default=0.0,
                                text="α scale factor",
                                minimum=0.0,
                                maximum=10.0,
                                tabular=True,
                                persistent=True,
                                visible=True,
                                refinable=True,
                                signal_name="data_changed",
                                set_action_name="_update_distribution",
                                mix_with=(SignalMixin, DataMixin,
                                          RefinableMixin, SetActionMixin))

    alpha_offset = FloatProperty(default=0.0,
                                 text="α offset factor",
                                 minimum=-5,
                                 maximum=5,
                                 tabular=True,
                                 persistent=True,
                                 visible=True,
                                 refinable=True,
                                 signal_name="data_changed",
                                 set_action_name="_update_distribution",
                                 mix_with=(SignalMixin, DataMixin,
                                           RefinableMixin, SetActionMixin))

    beta_scale = FloatProperty(default=0.0,
                               text="β² scale factor",
                               minimum=0.0,
                               maximum=10.0,
                               tabular=True,
                               persistent=True,
                               visible=True,
                               refinable=True,
                               signal_name="data_changed",
                               set_action_name="_update_distribution",
                               mix_with=(SignalMixin, DataMixin,
                                         RefinableMixin, SetActionMixin))

    beta_offset = FloatProperty(default=0.0,
                                text="β² offset factor",
                                minimum=-5,
                                maximum=5,
                                tabular=True,
                                persistent=True,
                                visible=True,
                                refinable=True,
                                signal_name="data_changed",
                                set_action_name="_update_distribution",
                                mix_with=(SignalMixin, DataMixin,
                                          RefinableMixin, SetActionMixin))

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self,
                 average=10,
                 alpha_scale=0.9485,
                 alpha_offset=-0.0017,
                 beta_scale=0.1032,
                 beta_offset=0.0034,
                 *args,
                 **kwargs):

        super(_AbstractCSDSDistribution, self).__init__(*args, **kwargs)

        self._data_object = CSDSData()

        type(self).average._set(self, average)
        type(self).maximum._set(
            self, int(settings.LOG_NORMAL_MAX_CSDS_FACTOR * average))
        type(self).minimum._set(self, 1)
        type(self).alpha_scale._set(self, alpha_scale)
        type(self).alpha_offset._set(self, alpha_offset)
        type(self).beta_scale._set(self, beta_scale)
        type(self).beta_offset._set(self, beta_offset)

        self._update_distribution()

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def _update_distribution(self):
        type(self).maximum._set(
            self, int(settings.LOG_NORMAL_MAX_CSDS_FACTOR * self.average))
        self._distrib = calculate_distribution(self.data_object)

    pass  # end of class
Example #16
0
class Phase(RefinementGroup, AbstractPhase, metaclass=PyXRDRefinableMeta):

    # MODEL INTEL:
    class Meta(AbstractPhase.Meta):
        store_id = "Phase"
        file_filters = [
            ("Phase file", get_case_insensitive_glob("*.PHS")),
        ]

    _data_object = None

    @property
    def data_object(self):
        self._data_object.type = "Phase"
        self._data_object.valid_probs = (all(self.probabilities.P_valid)
                                         and all(self.probabilities.W_valid))

        if self._data_object.valid_probs:
            self._data_object.sigma_star = self.sigma_star
            self._data_object.CSDS = self.CSDS_distribution.data_object

            self._data_object.G = self.G
            self._data_object.W = self.probabilities.get_distribution_matrix()
            self._data_object.P = self.probabilities.get_probability_matrix()

            self._data_object.components = [None] * len(self.components)
            for i, comp in enumerate(self.components):
                self._data_object.components[i] = comp.data_object
        else:
            self._data_object.sigma_star = None
            self._data_object.CSDS = None
            self._data_object.G = None
            self._data_object.W = None
            self._data_object.P = None
            self._data_object.components = None

        return self._data_object

    project = property(AbstractPhase.parent.fget, AbstractPhase.parent.fset)

    # PROPERTIES:

    #: Flag indicating whether the CSDS distribution is inherited from the
    #: :attr:`based_on` phase or not.
    @BoolProperty(default=False,
                  text="Inh. mean CSDS",
                  visible=True,
                  persistent=True,
                  tabular=True)
    def inherit_CSDS_distribution(self):
        return self._CSDS_distribution.inherited

    @inherit_CSDS_distribution.setter
    def inherit_CSDS_distribution(self, value):
        self._CSDS_distribution.inherited = value

    #: Flag indicating whether to inherit the display color from the
    #: :attr:`based_on` phase or not.
    inherit_display_color = BoolProperty(default=False,
                                         text="Inh. display color",
                                         visible=True,
                                         persistent=True,
                                         tabular=True,
                                         signal_name="visuals_changed",
                                         mix_with=(SignalMixin, ))

    #: Flag indicating whether to inherit the sigma start value from the
    #: :attr:`based_on` phase or not.
    inherit_sigma_star = BoolProperty(default=False,
                                      text="Inh. sigma star",
                                      visible=True,
                                      persistent=True,
                                      tabular=True,
                                      signal_name="data_changed",
                                      mix_with=(SignalMixin, ))

    _based_on_index = None  # temporary property
    _based_on_uuid = None  # temporary property

    #: The :class:`~Phase` instance this phase is based on
    based_on = LabeledProperty(default=None,
                               text="Based on phase",
                               visible=True,
                               persistent=False,
                               tabular=True,
                               signal_name="data_changed",
                               mix_with=(SignalMixin, ObserveChildMixin))

    @based_on.setter
    def based_on(self, value):
        old = type(self).based_on._get(self)
        if value == None or value.get_based_on_root(
        ) == self or value.parent != self.parent:
            value = None
        if value != old:
            type(self).based_on._set(self, value)
            for component in self.components:
                component.linked_with = None

    # INHERITABLE PROPERTIES:

    #: The sigma star orientation factor
    sigma_star = FloatProperty(default=3.0,
                               text="σ* [°]",
                               math_text="$\sigma^*$ [°]",
                               minimum=0.0,
                               maximum=90.0,
                               visible=True,
                               persistent=True,
                               tabular=True,
                               refinable=True,
                               inheritable=True,
                               inherit_flag="inherit_sigma_star",
                               inherit_from="based_on.sigma_star",
                               signal_name="data_changed",
                               mix_with=(SignalMixin, RefinableMixin,
                                         InheritableMixin))

    # A :class:`~pyxrd.phases.models.CSDS` instance
    CSDS_distribution = LabeledProperty(
        default=None,
        text="CSDS Distribution",
        visible=True,
        persistent=True,
        tabular=True,
        refinable=True,
        inheritable=True,
        inherit_flag="inherit_CSDS_distribution",
        inherit_from="based_on.CSDS_distribution",
        signal_name="data_changed",
        mix_with=(SignalMixin, RefinableMixin, InheritableMixin,
                  ObserveChildMixin))

    # A :class:`~pyxrd._probabilities.models._AbstractProbability` subclass instance
    probabilities = LabeledProperty(default=None,
                                    text="Probablities",
                                    visible=True,
                                    persistent=True,
                                    tabular=True,
                                    refinable=True,
                                    signal_name="data_changed",
                                    mix_with=(SignalMixin, RefinableMixin,
                                              ObserveChildMixin))

    @probabilities.setter
    def probabilities(self, value):
        type(self).probabilities._set(self, value)
        if value is not None:
            value.update()

    #: The color this phase's X-ray diffraction pattern should have.
    display_color = StringProperty(fset=AbstractPhase.display_color.fset,
                                   fget=AbstractPhase.display_color.fget,
                                   fdel=AbstractPhase.display_color.fdel,
                                   doc=AbstractPhase.display_color.__doc__,
                                   default="#008600",
                                   text="Display color",
                                   visible=True,
                                   persistent=True,
                                   tabular=True,
                                   widget_type='color',
                                   inheritable=True,
                                   inherit_flag="inherit_display_color",
                                   inherit_from="based_on.display_color",
                                   signal_name="visuals_changed",
                                   mix_with=(SignalMixin, InheritableMixin))

    #: The list of components this phase consists of
    components = ListProperty(default=None,
                              text="Components",
                              visible=True,
                              persistent=True,
                              tabular=True,
                              refinable=True,
                              widget_type="custom",
                              data_type=Component,
                              mix_with=(RefinableMixin, ))

    #: The # of components
    @AbstractPhase.G.getter
    def G(self):
        if self.components is not None:
            return len(self.components)
        else:
            return 0

    #: The # of components
    @AbstractPhase.R.getter
    def R(self):
        if self.probabilities:
            return self.probabilities.R

    # Flag indicating whether or not the links (based_on and linked_with) should
    # be saved as well.
    save_links = True

    # REFINEMENT GROUP IMPLEMENTATION:
    @property
    def refine_title(self):
        return self.name

    @property
    def refine_descriptor_data(self):
        return dict(phase_name=self.refine_title, component_name="*")

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self, *args, **kwargs):

        my_kwargs = self.pop_kwargs(
            kwargs, "data_CSDS_distribution", "data_sigma_star",
            "data_components", "data_G", "G", "data_R", "R",
            "data_probabilities", "based_on_uuid", "based_on_index",
            "inherit_probabilities", *[
                prop.label
                for prop in Phase.Meta.get_local_persistent_properties()
            ])
        super(Phase, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        with self.data_changed.hold():

            CSDS_distribution = self.get_kwarg(kwargs, None,
                                               "CSDS_distribution",
                                               "data_CSDS_distribution")
            self.CSDS_distribution = self.parse_init_arg(CSDS_distribution,
                                                         DritsCSDSDistribution,
                                                         child=True,
                                                         default_is_class=True,
                                                         parent=self)
            self.inherit_CSDS_distribution = self.get_kwarg(
                kwargs, False, "inherit_CSDS_distribution")

            self.display_color = self.get_kwarg(kwargs,
                                                choice(self.line_colors),
                                                "display_color")
            self.inherit_display_color = self.get_kwarg(
                kwargs, False, "inherit_display_color")

            self.sigma_star = self.get_kwarg(kwargs, self.sigma_star,
                                             "sigma_star", "data_sigma_star")
            self.inherit_sigma_star = self.get_kwarg(kwargs, False,
                                                     "inherit_sigma_star")

            self.components = self.get_list(kwargs, [],
                                            "components",
                                            "data_components",
                                            parent=self)

            G = int(self.get_kwarg(kwargs, 1, "G", "data_G"))
            R = int(self.get_kwarg(kwargs, 0, "R", "data_R"))
            if G is not None and G > 0:
                for i in range(len(self.components), G):
                    new_comp = Component(name="Component %d" % (i + 1),
                                         parent=self)
                    self.components.append(new_comp)
                    self.observe_model(new_comp)

            # Observe components
            for component in self.components:
                self.observe_model(component)

            # Connect signals to lists and dicts:
            self._components_observer = ListObserver(
                self.on_component_inserted,
                self.on_component_removed,
                prop_name="components",
                model=self)

            self.probabilities = self.parse_init_arg(
                self.get_kwarg(kwargs, None, "probabilities",
                               "data_probabilities"),
                get_correct_probability_model(R, G),
                default_is_class=True,
                child=True)
            self.probabilities.update()  # force an update
            inherit_probabilities = kwargs.pop("inherit_probabilities", None)
            if inherit_probabilities is not None:
                for prop in self.probabilities.Meta.get_inheritable_properties(
                ):
                    setattr(self.probabilities, prop.inherit_flag,
                            bool(inherit_probabilities))

            self._based_on_uuid = self.get_kwarg(kwargs, None, "based_on_uuid")
            self._based_on_index = self.get_kwarg(kwargs, None,
                                                  "based_on_index")

    def __repr__(self):
        return "Phase(name='%s', based_on=%r)" % (self.name, self.based_on)

    # ------------------------------------------------------------
    #      Notifications of observable properties
    # ------------------------------------------------------------
    def on_component_inserted(self, item):
        # Set parent and observe the new component (visuals changed signals):
        if item.parent != self: item.parent = self
        self.observe_model(item)

    def on_component_removed(self, item):
        with self.data_changed.hold_and_emit():
            # Clear parent & stop observing:
            item.parent = None
            self.relieve_model(item)

    @Observer.observe("data_changed", signal=True)
    def notify_data_changed(self, model, prop_name, info):
        if isinstance(model, Phase) and model == self.based_on:
            with self.data_changed.hold():
                # make sure inherited probabilities are up-to-date
                self.probabilities.update()
                self.data_changed.emit(arg="based_on")
        else:
            self.data_changed.emit()

    @Observer.observe("visuals_changed", signal=True)
    def notify_visuals_changed(self, model, prop_name, info):
        self.visuals_changed.emit()

    # ------------------------------------------------------------
    #      Input/Output stuff
    # ------------------------------------------------------------
    def resolve_json_references(self):
        # Set the based on and linked with variables:
        if hasattr(self, "_based_on_uuid") and self._based_on_uuid is not None:
            self.based_on = type(type(self)).object_pool.get_object(
                self._based_on_uuid)
            del self._based_on_uuid
        elif hasattr(
                self, "_based_on_index"
        ) and self._based_on_index is not None and self._based_on_index != -1:
            warn(
                "The use of object indices is deprecated since version 0.4. Please switch to using object UUIDs.",
                DeprecationWarning)
            self.based_on = self.parent.phases.get_user_from_index(
                self._based_on_index)
            del self._based_on_index
        for component in self.components:
            component.resolve_json_references()
        with self.data_changed.hold():
            # make sure inherited probabilities are up-to-date
            self.probabilities.update()

    def _pre_multi_save(self, phases, ordered_phases):
        ## Override from base class

        if self.based_on != "" and not self.based_on in phases:
            self.save_links = False
        Component.export_atom_types = True
        for component in self.components:
            component.save_links = self.save_links

        # Make sure parent is first in ordered list:
        if self.based_on in phases:
            index = ordered_phases.index(self)
            index2 = ordered_phases.index(self.based_on)
            if index < index2:
                ordered_phases.remove(self.based_on)
                ordered_phases.insert(index, self.based_on)

    def _post_multi_save(self):
        ## Override from base class
        self.save_links = True
        for component in self.components:
            component.save_links = True
        Component.export_atom_types = False

    def json_properties(self):
        retval = super(Phase, self).json_properties()
        if not self.save_links:
            for prop in self.Meta.all_properties:
                if getattr(prop, "inherit_flag", False):
                    retval[prop.inherit_flag] = False
            retval["based_on_uuid"] = ""
        else:
            retval[
                "based_on_uuid"] = self.based_on.uuid if self.based_on else ""
        return retval

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def _update_interference_distributions(self):
        return self.CSDS_distribution.distrib

    def get_based_on_root(self):
        """
            Gets the root object in the based_on chain
        """
        if self.based_on is not None:
            return self.based_on.get_based_on_root()
        else:
            return self

    pass  # end of class
Example #17
0
class _AbstractProbability(RefinementGroup,
                           DataModel,
                           Storable,
                           metaclass=PyXRDRefinableMeta):

    # MODEL INTEL:
    class Meta(DataModel.Meta):
        pass

    phase = property(DataModel.parent.fget, DataModel.parent.fset)

    # PROPERTIES:
    name = StringProperty(default="Probabilities", text="Name")
    W_valid = LabeledProperty(default=None, text="Valid W matrix")
    W_valid_mask = None
    P_valid = LabeledProperty(default=None, text="Valid P matrix")
    P_valid_mask = None

    _R = -1

    @property
    def R(self):
        return self._R

    @property
    def rank(self):
        return self.G**max(self.R, 1)

    _G = 0

    @property
    def G(self):
        return self._G

    _W = None
    _P = None

    @IndexProperty
    def mP(self, indeces):
        r, ind = self._get_Pxy_from_indeces(indeces)
        return self._lP[r][ind]

    @mP.setter
    def mP(self, indeces, value):
        r, ind = self._get_Pxy_from_indeces(indeces)
        self._lP[r][ind] = value

    @IndexProperty
    def mW(self, indeces):
        r, ind = self._get_Wxy_from_indeces(indeces)
        return self._lW[r][ind]

    @mW.setter
    def mW(self, indeces, value):
        r, ind = self._get_Wxy_from_indeces(indeces)
        self._lW[r][ind] = value

    @property
    def _flags(self):
        return [
            1 if getattr(self, prop.inherit_flag, False) else 0
            for prop in self.Meta.all_properties
            if getattr(prop, "inheritable", False)
        ]

    # REFINEMENT GROUP IMPLEMENTATION:
    @property
    def refine_title(self):
        return self.name

    @property
    def refine_descriptor_data(self):
        return dict(phase_name=self.phase.name, component_name="*")

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self, R=-1, *args, **kwargs):
        super(_AbstractProbability, self).__init__(*args, **kwargs)
        self._R = R
        self._create_matrices()

    def _create_matrices(self):
        """
            Creates a list of matrices for different 'levels' of R:
                e.g. when R=3 with g layers there can be 4 different W matrixes:
                    Wi = gxg matrix
                    Wij = g²xg² matrix
                    Wijk = g³xg³ matrix
                    Wijkl = another g³xg³ matrix (= Wijk * Pijkl)
                and 3 different P matrices:
                    Pij = gxg matrix
                    Pijk = g²xg² matrix
                    Pijkl = g³xg³ matrix
        """
        R = max(self.R, 1)
        self._lW = [None] * (R + 1)
        self._lP = [None] * R
        for r in range(R):
            lrank = self.G**(r + 1)
            self._lW[r] = np.zeros(shape=(lrank, lrank), dtype=float)
            self._lP[r] = np.zeros(shape=(lrank, lrank), dtype=float)
        self._lW[-1] = np.zeros(shape=(lrank, lrank), dtype=float)
        self._W = self._lW[-2]
        self._P = self._lP[-1]

        # validity matrices:
        self.W_valid = np.array([False] * (R + 1))
        self.W_valid_mask = np.array([None] * (R + 1))
        self.P_valid = np.array([False] * R)
        self.P_valid_mask = np.array([None] * R)

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def update(self):
        raise NotImplementedError

    def _clamp_set_and_update(self, name, value, minimum=0.0, maximum=1.0):
        clamped = min(max(value, minimum), maximum)
        if getattr(self, name) != clamped:
            setattr(self, name, clamped)
            self.update()

    def solve(self):
        """
            This 'solves' the other W and P matrices using the 'top' P and W
            matrix calculated in the update method.
        """

        for num in range(1, self.R):
            # W matrices:
            for base in product(list(range(self.G)), repeat=num):
                self.mW[base] = 0
                for i in range(self.G):
                    self.mW[base] += self.mW[(i, ) + base]
            # P matrices:
            p_num = num + 1
            for base in product(list(range(self.G)), repeat=p_num):
                W = self.mW[base[:-1]]
                self.mP[base] = self.mW[base] / W if W > 0 else 0.0

        # one extra W matrix:
        self._lW[-1][:] = np.dot(self._W, self._P)

    def validate(self):
        """
            Checks wether the calculated matrices are valid, and stores the
            validation results in 'masks': matrices of the same size, in which
            the values correspond with 1 minus the number of validation rules
            that specific W or P value has failed for.
        """
        def _validate_WW(W, R):
            """Validation rules for the product of a W and P matrix"""
            W_valid_mask = np.ones_like(W)
            rank = self.G**max(R, 1)

            # sum of the cols (W...x's) need to equal W...
            for i in range(rank):
                if abs(np.sum(W[..., i]) - self._W[i, i]) > 1e4:
                    W_valid_mask[..., i] -= 1

            # sum of the entire matrix must equal one:
            if abs(np.sum(W) - 1.0) > 1e4:
                W_valid_mask -= 1

            # values need to be between 0 and 1
            for i in range(rank):
                for j in range(rank):
                    if W[i, j] < 0.0 or W[i, j] > 1.0:
                        W_valid_mask[i, i] -= 1

            # if the sum of the mask values equals the square of the rank,
            # no rules have been broken:
            W_valid = (np.sum(W_valid_mask) == rank**2)

            return W_valid, W_valid_mask

        def _validate_W(W, R):
            """Validation rules for a diagonal W matrix"""
            W_valid_mask = np.ones_like(W)
            rank = self.G**max(R, 1)

            # sum of the diagonal nees to be one
            if abs(np.sum(W) - 1.0) > 1e6:
                for i in range(rank):
                    W_valid_mask[i, i] -= 1

            # values need to be between 0 and 1
            for i in range(rank):
                for j in range(rank):
                    if W[i, j] < 0.0 or W[i, j] > 1.0:
                        W_valid_mask[i, i] -= 1

            # if the sum of the mask values equals the square of the rank,
            # no rules have been broken:
            W_valid = (np.sum(W_valid_mask) == rank**2)

            return W_valid, W_valid_mask

        def _validate_P(P, R):
            P_valid_mask = np.ones_like(P)
            rank = self.G**max(R, 1)

            # sum of the rows need to be one
            for i in range(rank):
                if abs(np.sum(P[i, ...]) - 1.0) > 1e6:
                    P_valid_mask[i, ...] -= 1

            # values need to be between 0 and 1
            for i in range(rank):
                for j in range(rank):
                    if P[i, j] < 0.0 or P[i, j] > 1.0:
                        P_valid_mask[i, j] -= 1

            # if the sum of the mask values equals the square of the rank,
            # no rules have been broken:
            P_valid = (np.sum(P_valid_mask) == rank**2)

            return P_valid, P_valid_mask

        for i in range(max(self.R, 1)):
            self.W_valid[i], self.W_valid_mask[i] = _validate_W(
                self._lW[i], i + 1)
            self.P_valid[i], self.P_valid_mask[i] = _validate_P(
                self._lP[i], i + 1)

        # the extra W matrix validates differently:
        self.W_valid[-1], self.W_valid_mask[-1] = _validate_WW(
            self._lW[-1], self.R)

    def _get_Pxy_from_indeces(self, indeces):
        if not hasattr(indeces, "__iter__"):
            indeces = [indeces]
        l = len(indeces)
        assert (
            l > 1
        ), "Two or more indeces needed to acces P elements, not %s" % indeces
        assert (l <= max(self.R, 1) +
                1), "Too many indeces for an R%d model: %s" % (self.R, indeces)
        R = max(l - 1, 1)
        x, y = 0, 0
        for i in range(1, R + 1):
            f = self.G**(R - i)
            x += indeces[i - 1] * f
            y += indeces[i] * f
        return (l - 2), (x, y)

    def _get_Wxy_from_indeces(self, indeces):
        if not hasattr(indeces, "__iter__"):
            indeces = [indeces]
        l = len(indeces)
        assert (l > 0), "One or more indeces needed to acces W elements"
        assert (l <= max(self.R, 1) +
                1), "Too many indeces for an R%d model: %s" % (self.R, indeces)
        if l == (max(self.R, 1) + 1):
            R = max(l - 1, 1)
            x, y = 0, 0
            for i in range(1, R + 1):
                f = self.G**(R - i)
                x += indeces[i - 1] * f
                y += indeces[i] * f
            return (l - 1), (x, y)
        else:
            R = max(l, 1)
            x = 0
            for i in range(R):
                x += indeces[i] * self.G**(R - (i + 1))
            return (l - 1), (x, x)

    def get_all_matrices(self):
        return self._lW, self._lP

    def get_distribution_matrix(self):
        return self._W

    def get_distribution_array(self):
        return np.diag(self._W)

    def get_probability_matrix(self):
        return self._P

    _stashed_lP = None
    _stashed_lW = None
    _stashed_flags = None

    def _stash_matrices(self):
        """ Stashes the matrices for an update """
        self._stashed_lW = deepcopy(np.asanyarray(self._lW))
        self._stashed_lP = deepcopy(np.asanyarray(self._lP))
        self._stashed_flags = deepcopy(np.asarray(self._flags))

    def _compare_stashed_matrices(self):
        """ Unstashed matrices and compares with current values, if identical returns True """
        if self._stashed_lP is not None and self._stashed_lW is not None:
            result = np.array_equal(self._stashed_lW, np.asanyarray(self._lW))
            result = result and np.array_equal(self._stashed_lP,
                                               np.asanyarray(self._lP))
            result = result and np.array_equal(self._stashed_flags,
                                               np.asanyarray(self._flags))
            self._stashed_lW = None
            self._stashed_lP = None
            return result
        else:
            return False

    @contextmanager
    def monitor_changes(self):
        with self.data_changed.hold():
            self._stash_matrices()
            yield
            if not self._compare_stashed_matrices():
                self.data_changed.emit()

    pass  # end of class