class RefinementInfo(PyXRDModel, Storable): """ A model that is used to store the refinement information for each refinable value (in other models): minimum and maximum value and a flag to indicate whether this value is selected for refinement. """ # MODEL INTEL: class Meta(PyXRDModel.Meta, Storable.Meta): store_id = "RefinementInfo" minimum = FloatProperty(default=0.0, text="Minimum", persistent=True) maximum = FloatProperty(default=1.0, text="Maximum", persistent=True) refine = BoolProperty(default=False, text="Refine", persistent=True) def __init__(self, minimum, maximum, refine, *args, **kwargs): """ Valid *positional* arguments for a RefinementInfo are: refine: whether or not the linked parameter is selected for refinement minimum: the minimum allowable value for the linked parameter maximum: the maximum allowable value for the linked parameter """ super(RefinementInfo, self).__init__() self.refine = refine self.minimum = not_none(minimum, 0.0) self.maximum = not_none(maximum, 1.0) def to_json(self): return self.json_properties() def json_properties(self): return [self.minimum, self.maximum, self.refine] pass # end of class
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
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
class R1G3Model(_AbstractProbability): r""" Probability model for Reichweite 1 with 3 components. The 6 (=g*(g-1)) independent variables are: .. math:: :nowrap: \begin{align*} & W_1 & \text{$P_{11} (W_1 < 0.5)$ or $P_{xx} (W_1 > 0.5)$} with P_{xx} = \frac {W_{22} + W_{23} + W_{32} + W_{33} + W_{42} + W_{43}}{W_2 + W_3} \\ & G_1 = \frac{W_2}{W_2 + W_3} & G_2 = \frac{W_{22} + W_{23}}{W_{22} + W_{23} + W_{32} + W_{33}} \\ & G_3 = \frac{W_{22}}{W_{22} + W_{23}} & G_4 = \frac{W_{32}}{W_{32} + W_{33}} \\ \end{align*} Calculation of the other variables happens as follows: .. math:: :nowrap: \begin{align*} & \text{Calculate the 'inverted' ratios of $G_2$, $G_3$ and $G_4$ as follows:} \\ & \quad G_i^{\text{-1}} = \begin{cases} G_i^{-1} - 1.0, & \text{if } G_i > 0 \\ 0, & \text{otherwise} \end{cases} \quad \forall i \in \left\{ {2, 3, 4}\right\} \\ & \\ & \text{Calculate the base weight fractions of each component:} \\ & \quad W_2 = (1 - W_1) \cdot G_1\\ & \quad W_3 = 1.0 - W_1 - W_2 \\ & \\ & \text{if $W_1 \leq 0.5$:} \\ & \quad \text{$P_{11}$ is given and W_xx is derived as} \\ & \quad W_{xx} = W_{22} + W_{23} + W_{32} + W_{23} = W_1 \cdot (1 - P_{11}) + W_2 + W_3 \\ & \\ & \text{if $W_1 > 0.5$:} \\ & \quad \text{$P_{xx}$ is given and $P_{11}$ is derived further down} \\ & \quad W_{xx} = W_{22} + W_{23} + W_{32} + W_{23} = P_{xx} \cdot (W_2 + W_3) \\ & \\ & W_{22} = W_{xx} \cdot G_2 \cdot G_3 \\ & W_{23} = W_{22} \cdot G_3^{-1} \\ & W_{32} = W_{xx} \cdot (1 - G_2) \\ & W_{33} = G_4^{-1} \cdot W_{32} \\ & \\ & P_{23} = \begin{dcases} \dfrac{W_{23}}{W_2}, & \text{if $W_2 > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & P_{12} = 1 - P_{22} - P_{23} \\ & \\ & P_{32} = \begin{dcases} \frac{W_{32}}{W_3}, & \text{if $W_3 > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & P_{33} = \begin{dcases} \frac{W_{33}}{W_3}, & \text{if $W_3 > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & P_{31} = 1 - P_{32} - P_{33} \\ & \\ & P_{12} = \begin{dcases} \frac{W_2 - W_{22} - W_{32}}{W_1}, & \text{if $W_1 > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & P_{13} = \begin{dcases} \frac{W_3 - W_{23} - W_{33}}{W_1}, & \text{if $W_1 > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & \\ & \text{if $W_1 > 0.5$}: \\ & \quad P_{11} = 1 - P_{12} - P_{13} \\ & \\ & \text{Remainder of weight fraction can be calculated as follows:} \\ & \quad W_{ij} = {W_{ii}} \cdot {P_{ij}} \quad \forall {i,j} \in \left[ {1, 3} \right] \\ \end{align*} """ # MODEL METADATA: class Meta(_AbstractProbability.Meta): store_id = "R1G3Model" # PROPERTIES _G = 3 inherit_W1 = BoolProperty(default=False, text="Inherit flag for W1", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) W1 = FloatProperty(default=0.8, text="W1", math_text=r"$W_1$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_W1", inherit_from="parent.based_on.probabilities.W1", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_P11_or_P22 = BoolProperty(default=False, text="Inherit flag for P11_or_P22", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) P11_or_P22 = FloatProperty( default=0.7, text="P11_or_P22", math_text=r"$P_{11} %s$ or $\newline P_{22} %s$" % (mt_range(0.0, "W_1", 0.5), mt_range(0.5, "W_1", 1.0)), persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_P11_or_P22", inherit_from="parent.based_on.probabilities.P11_or_P22", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_G1 = BoolProperty(default=False, text="Inherit flag for G1", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) G1 = FloatProperty(default=0.7, text="W2/(W2+W3)", math_text=r"$\large\frac{W_2}{W_3 + W_2}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G1", inherit_from="parent.based_on.probabilities.G1", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_G2 = BoolProperty(default=False, text="Inherit flag for G2", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) G2 = FloatProperty( default=0.7, text="(W22+W23)/(W22+W23+W32+W33)", math_text= r"$\large\frac{W_{22} + W_{23}}{W_{22} + W_{23} + W_{32} + W_{33}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G2", inherit_from="parent.based_on.probabilities.G2", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_G3 = BoolProperty(default=False, text="Inherit flag for G3", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) G3 = FloatProperty(default=0.7, text="W22/(W22+W23)", math_text=r"$\large\frac{W_{22}}{W_{22} + W_{23}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G3", inherit_from="parent.based_on.probabilities.G3", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_G4 = BoolProperty(default=False, text="Inherit flag for G4", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) G4 = FloatProperty(default=0.7, text="W23/(W32+W33)", math_text=r"$\large\frac{W_{22}}{W_{22} + W_{23}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G4", inherit_from="parent.based_on.probabilities.G4", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, W1=0.8, P11_or_P22=0.7, G1=0.7, G2=0.7, G3=0.7, G4=0.7, inherit_W1=False, inherit_P11_or_P22=False, inherit_G1=False, inherit_G2=False, inherit_G3=False, inherit_G4=False, *args, **kwargs): super(R1G3Model, self).__init__(R=1, *args, **kwargs) with self.data_changed.hold(): self.W1 = not_none(W1, 0.8) self.inherit_W1 = bool(inherit_W1) self.P11_or_P22 = not_none(P11_or_P22, 0.7) self.inherit_P11_or_P22 = bool(inherit_P11_or_P22) self.G1 = not_none(G1, 0.7) self.inherit_G1 = bool(inherit_G1) self.G2 = not_none(G2, 0.7) self.inherit_G2 = bool(inherit_G2) self.G3 = not_none(G3, 0.7) self.inherit_G3 = bool(inherit_G3) self.G4 = not_none(G4, 0.7) self.inherit_G4 = bool(inherit_G4) self.update() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def update(self): with self.monitor_changes(): self.mW[0] = self.W1 self.mW[1] = (1 - self.mW[0]) * self.G1 self.mW[2] = 1.0 - self.mW[0] - self.mW[1] W0inv = 1.0 / self.mW[0] if self.mW[0] > 0.0 else 0.0 Wxx = 0 if self.mW[0] <= 0.5: # P00 given self.mP[0, 0] = self.P11_or_P22 # Wxx = W11 + W12 + W21 + W22 Wxx = self.mW[0] * (self.mP[0, 0] - 1) + self.mW[1] + self.mW[2] else: # Pxx given # Wxx = W11 + W12 + W21 + W22 Wxx = (1.0 - self.mW[0]) * self.P11_or_P22 # W11 + W12 = Wxx * G2: self.mW[1, 1] = Wxx * self.G2 * self.G3 self.mW[1, 2] = Wxx * self.G2 * (1 - self.G3) self.mP[1, 1] = self.mW[1, 1] / self.mW[1] if self.mW[1] > 0.0 else 0.0 self.mW[2, 1] = Wxx * (1 - self.G2) * self.G4 self.mW[2, 2] = Wxx * (1 - self.G2) * (1 - self.G4) self.mP[1, 2] = (self.mW[1, 2] / self.mW[1]) if self.mW[1] > 0.0 else 0.0 self.mP[1, 0] = 1 - self.mP[1, 1] - self.mP[1, 2] self.mP[2, 1] = (self.mW[2, 1] / self.mW[2]) if self.mW[2] > 0.0 else 0.0 self.mP[2, 2] = (self.mW[2, 2] / self.mW[2]) if self.mW[2] > 0.0 else 0.0 self.mP[2, 0] = 1 - self.mP[2, 1] - self.mP[2, 2] self.mP[0, 1] = (self.mW[1] - self.mW[1, 1] - self.mW[2, 1]) * W0inv self.mP[0, 2] = (self.mW[2] - self.mW[1, 2] - self.mW[2, 2]) * W0inv if self.mW[0] > 0.5: self.mP[0, 0] = 1 - self.mP[0, 1] - self.mP[0, 2] for i in range(3): for j in range(3): self.mW[i, j] = self.mW[i, i] * self.mP[i, j] self.solve() self.validate() pass # end of class
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
class Project(DataModel, Storable): """ This is the top-level object that servers the purpose of combining the different objects (most notably :class:`~.atoms.models.AtomType`'s, :class:`~.phases.models.Phase`'s, :class:`~.specimen.models.Specimen`'s and :class:`~.mixture.models.Mixture`'s). It also provides a large number of display-related 'default' properties (e.g. for patterns and their markers, axes etc.). For more details: see the property descriptions. Example usage: .. code-block:: python >>> from pyxrd.project.models import Project >>> from pyxrd.generic.io.xrd_parsers import XRDParser >>> from pyxrd.specimen.models import Specimen >>> project = Project(name="New Project", author="Mr. X", layout_mode="FULL", axes_dspacing=True) >>> for specimen in Specimen.from_experimental_data("/path/to/xrd_data_file.rd", parent=project): ... project.specimens.append(specimen) ... """ # MODEL INTEL: class Meta(DataModel.Meta): store_id = "Project" file_filters = [ ("PyXRD Project files", get_case_insensitive_glob("*.pyxrd", "*.zpd")), ] import_filters = [ ("Sybilla XML files", get_case_insensitive_glob("*.xml")), ] # PROPERTIES: filename = None #: The project name name = StringProperty(default="", text="Name", visible=True, persistent=True) #: The project data (string) date = StringProperty(default="", text="Date", visible=True, persistent=True) #: The project description description = StringProperty( default=None, text="Description", visible=True, persistent=True, widget_type="text_view", ) #: The project author author = StringProperty(default="", text="Author", visible=True, persistent=True) #: Flag indicating whether this project has been changed since it was last saved. needs_saving = BoolProperty(default=True, visible=False, persistent=False) #: The layout mode this project should be displayed in layout_mode = StringChoiceProperty(default=settings.DEFAULT_LAYOUT, text="Layout mode", visible=True, persistent=True, choices=settings.DEFAULT_LAYOUTS, mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The manual lower limit for the X-axis axes_xmin = FloatProperty(default=settings.AXES_MANUAL_XMIN, text="min. [°2T]", visible=True, persistent=True, minimum=0.0, widget_type="spin", mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The manual upper limit for the X-axis axes_xmax = FloatProperty(default=settings.AXES_MANUAL_XMAX, text="max. [°2T]", visible=True, persistent=True, minimum=0.0, widget_type="spin", mix_with=(SignalMixin, ), signal_name="visuals_changed") #: Whether or not to stretch the X-axis over the entire available display axes_xstretch = BoolProperty(default=settings.AXES_XSTRETCH, text="Stetch x-axis to fit window", visible=True, persistent=True, mix_with=(SignalMixin, ), signal_name="visuals_changed") #: Flag toggling between d-spacing (when True) or 2-Theta axes (when False) axes_dspacing = BoolProperty(default=settings.AXES_DSPACING, text="Show d-spacing in x-axis", visible=True, persistent=True, mix_with=(SignalMixin, ), signal_name="visuals_changed") #: Whether or not the y-axis should be shown axes_yvisible = BoolProperty(default=settings.AXES_YVISIBLE, text="Y-axis visible", visible=True, persistent=True, mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The manual lower limit for the Y-axis (in counts) axes_ymin = FloatProperty(default=settings.AXES_MANUAL_YMIN, text="min. [counts]", visible=True, persistent=True, minimum=0.0, widget_type="spin", mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The manual upper limit for the Y-axis (in counts) axes_ymax = FloatProperty(default=settings.AXES_MANUAL_YMAX, text="max. [counts]", visible=True, persistent=True, minimum=0.0, widget_type="spin", mix_with=(SignalMixin, ), signal_name="visuals_changed") #: What type of y-axis to use: raw counts, single or multi-normalized units axes_ynormalize = IntegerChoiceProperty(default=settings.AXES_YNORMALIZE, text="Y scaling", visible=True, persistent=True, choices=settings.AXES_YNORMALIZERS, mix_with=(SignalMixin, ), signal_name="visuals_changed") #: Whether to use automatic or manual Y limits axes_ylimit = IntegerChoiceProperty(default=settings.AXES_YLIMIT, text="Y limit", visible=True, persistent=True, choices=settings.AXES_YLIMITS, mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The offset between patterns as a fraction of the maximum intensity display_plot_offset = FloatProperty(default=settings.PLOT_OFFSET, text="Pattern offset", visible=True, persistent=True, minimum=0.0, widget_type="float_entry", mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The number of patterns to group ( = having no offset) display_group_by = IntegerProperty(default=settings.PATTERN_GROUP_BY, text="Group patterns by", visible=True, persistent=True, minimum=1, widget_type="spin", mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The relative position (from the pattern offset) for pattern labels #: as a fraction of the patterns intensity display_label_pos = FloatProperty(default=settings.LABEL_POSITION, text="Default label position", visible=True, persistent=True, widget_type="float_entry", mix_with=(SignalMixin, ), signal_name="visuals_changed") #: What type of scale to use for X-axis, automatic or manual axes_xlimit = IntegerChoiceProperty(default=settings.AXES_XLIMIT, text="X limit", visible=True, persistent=True, choices=settings.AXES_XLIMITS, mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The default angle at which marker labels are displayed display_marker_angle = FloatProperty(default=settings.MARKER_ANGLE, text="Angle", visible=True, persistent=True, widget_type="float_entry", mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The default offset for marker labels display_marker_top_offset = FloatProperty( default=settings.MARKER_TOP_OFFSET, text="Offset from base", visible=True, persistent=True, widget_type="float_entry", mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The default marker label alignment (one of settings.MARKER_ALIGNS) display_marker_align = StringChoiceProperty(default=settings.MARKER_ALIGN, text="Label alignment", visible=True, persistent=True, choices=settings.MARKER_ALIGNS, mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The default marker label base (one of settings.MARKER_BASES) display_marker_base = IntegerChoiceProperty(default=settings.MARKER_BASE, text="Base connection", visible=True, persistent=True, choices=settings.MARKER_BASES, mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The default marker label top (one of settings.MARKER_TOPS) display_marker_top = IntegerChoiceProperty(default=settings.MARKER_TOP, text="Top connection", visible=True, persistent=True, choices=settings.MARKER_TOPS, mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The default marker style (one of settings.MARKER_STYLES) display_marker_style = StringChoiceProperty(default=settings.MARKER_STYLE, text="Line style", visible=True, persistent=True, choices=settings.MARKER_STYLES, mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The default marker color display_marker_color = StringProperty(default=settings.MARKER_COLOR, text="Color", visible=True, persistent=True, widget_type="color", mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The default calculated profile color display_calc_color = StringProperty(default=settings.CALCULATED_COLOR, text="Calculated color", visible=True, persistent=True, widget_type="color", mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The default experimental profile color display_exp_color = StringProperty(default=settings.EXPERIMENTAL_COLOR, text="Experimental color", visible=True, persistent=True, widget_type="color", mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The default calculated profile line width display_calc_lw = IntegerProperty(default=settings.CALCULATED_LINEWIDTH, text="Calculated line width", visible=True, persistent=True, widget_type="spin", mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The default experimental profile line width display_exp_lw = IntegerProperty(default=settings.EXPERIMENTAL_LINEWIDTH, text="Experimental line width", visible=True, persistent=True, widget_type="spin", mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The default calculated profile line style display_calc_ls = StringChoiceProperty( default=settings.CALCULATED_LINESTYLE, text="Calculated line style", visible=True, persistent=True, choices=settings.PATTERN_LINE_STYLES, mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The default experimental profile line style display_exp_ls = StringChoiceProperty( default=settings.EXPERIMENTAL_LINESTYLE, text="Experimental line style", visible=True, persistent=True, choices=settings.PATTERN_LINE_STYLES, mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The default calculated profile line style display_calc_marker = StringChoiceProperty( default=settings.CALCULATED_MARKER, text="Calculated line marker", visible=True, persistent=True, choices=settings.PATTERN_MARKERS, mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The default calculated profile line style display_exp_marker = StringChoiceProperty( default=settings.EXPERIMENTAL_MARKER, text="Experimental line marker", visible=True, persistent=True, choices=settings.PATTERN_MARKERS, mix_with=(SignalMixin, ), signal_name="visuals_changed") #: The list of specimens specimens = ListProperty( default=[], text="Specimens", data_type=Specimen, visible=True, persistent=True, ) #: The list of phases phases = ListProperty(default=[], text="Phases", data_type=Phase, visible=False, persistent=True) #: The list of atom types atom_types = ListProperty(default=[], text="Atom types", data_type=AtomType, visible=False, persistent=True) #: The list of Behaviours #behaviours = ListProperty( # default=[], text="Behaviours", data_type=InSituBehaviour, # visible=False, persistent=True #) #: The list of mixtures mixtures = ListProperty(default=[], text="Mixture", data_type=Mixture, visible=False, persistent=True) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): """ Constructor takes any of its properties as a keyword argument except for: - needs_saving In addition to the above, the constructor still supports the following deprecated keywords, mapping to a current keyword: - goniometer: the project-level goniometer, is passed on to the specimens - axes_xscale: deprecated alias for axes_xlimit - axes_yscale: deprecated alias for axes_ynormalize Any other arguments or keywords are passed to the base class. """ my_kwargs = self.pop_kwargs( kwargs, "goniometer", "data_goniometer", "data_atom_types", "data_phases", "axes_yscale", "axes_xscale", "filename", "behaviours", *[ prop.label for prop in Project.Meta.get_local_persistent_properties() ]) super(Project, self).__init__(*args, **kwargs) kwargs = my_kwargs with self.data_changed.hold(): with self.visuals_changed.hold(): self.filename = self.get_kwarg(kwargs, self.filename, "filename") self.layout_mode = self.get_kwarg(kwargs, self.layout_mode, "layout_mode") self.display_marker_align = self.get_kwarg( kwargs, self.display_marker_align, "display_marker_align") self.display_marker_color = self.get_kwarg( kwargs, self.display_marker_color, "display_marker_color") self.display_marker_base = self.get_kwarg( kwargs, self.display_marker_base, "display_marker_base") self.display_marker_top = self.get_kwarg( kwargs, self.display_marker_top, "display_marker_top") self.display_marker_top_offset = self.get_kwarg( kwargs, self.display_marker_top_offset, "display_marker_top_offset") self.display_marker_angle = self.get_kwarg( kwargs, self.display_marker_angle, "display_marker_angle") self.display_marker_style = self.get_kwarg( kwargs, self.display_marker_style, "display_marker_style") self.display_calc_color = self.get_kwarg( kwargs, self.display_calc_color, "display_calc_color") self.display_exp_color = self.get_kwarg( kwargs, self.display_exp_color, "display_exp_color") self.display_calc_lw = self.get_kwarg(kwargs, self.display_calc_lw, "display_calc_lw") self.display_exp_lw = self.get_kwarg(kwargs, self.display_exp_lw, "display_exp_lw") self.display_calc_ls = self.get_kwarg(kwargs, self.display_calc_ls, "display_calc_ls") self.display_exp_ls = self.get_kwarg(kwargs, self.display_exp_ls, "display_exp_ls") self.display_calc_marker = self.get_kwarg( kwargs, self.display_calc_marker, "display_calc_marker") self.display_exp_marker = self.get_kwarg( kwargs, self.display_exp_marker, "display_exp_marker") self.display_plot_offset = self.get_kwarg( kwargs, self.display_plot_offset, "display_plot_offset") self.display_group_by = self.get_kwarg(kwargs, self.display_group_by, "display_group_by") self.display_label_pos = self.get_kwarg( kwargs, self.display_label_pos, "display_label_pos") self.axes_xlimit = self.get_kwarg(kwargs, self.axes_xlimit, "axes_xlimit", "axes_xscale") self.axes_xmin = self.get_kwarg(kwargs, self.axes_xmin, "axes_xmin") self.axes_xmax = self.get_kwarg(kwargs, self.axes_xmax, "axes_xmax") self.axes_xstretch = self.get_kwarg(kwargs, self.axes_xstretch, "axes_xstretch") self.axes_ylimit = self.get_kwarg(kwargs, self.axes_ylimit, "axes_ylimit") self.axes_ynormalize = self.get_kwarg(kwargs, self.axes_ynormalize, "axes_ynormalize", "axes_yscale") self.axes_yvisible = self.get_kwarg(kwargs, self.axes_yvisible, "axes_yvisible") self.axes_ymin = self.get_kwarg(kwargs, self.axes_ymin, "axes_ymin") self.axes_ymax = self.get_kwarg(kwargs, self.axes_ymax, "axes_ymax") goniometer = None goniometer_kwargs = self.get_kwarg(kwargs, None, "goniometer", "data_goniometer") if goniometer_kwargs: goniometer = self.parse_init_arg(goniometer_kwargs, None, child=True) # Set up and observe atom types: self.atom_types = self.get_list(kwargs, [], "atom_types", "data_atom_types", parent=self) self._atom_types_observer = ListObserver( self.on_atom_type_inserted, self.on_atom_type_removed, prop_name="atom_types", model=self) # Resolve json references & observe phases self.phases = self.get_list(kwargs, [], "phases", "data_phases", parent=self) for phase in self.phases: phase.resolve_json_references() self.observe_model(phase) self._phases_observer = ListObserver(self.on_phase_inserted, self.on_phase_removed, prop_name="phases", model=self) # Set goniometer if required & observe specimens self.specimens = self.get_list(kwargs, [], "specimens", "data_specimens", parent=self) for specimen in self.specimens: if goniometer: specimen.goniometer = goniometer self.observe_model(specimen) self._specimens_observer = ListObserver( self.on_specimen_inserted, self.on_specimen_removed, prop_name="specimens", model=self) # Observe behaviours: #self.behaviours = self.get_list(kwargs, [], "behaviours", parent=self) #for behaviour in self.behaviours: # self.observe_model(behaviour) #self._behaviours_observer = ListObserver( # self.on_behaviour_inserted, # self.on_behaviour_removed, # prop_name="behaviours", # model=self #) # Observe mixtures: self.mixtures = self.get_list(kwargs, [], "mixtures", "data_mixtures", parent=self) for mixture in self.mixtures: self.observe_model(mixture) self._mixtures_observer = ListObserver( self.on_mixture_inserted, self.on_mixture_removed, prop_name="mixtures", model=self) self.name = str( self.get_kwarg(kwargs, "Project name", "name", "data_name")) self.date = str( self.get_kwarg(kwargs, time.strftime("%d/%m/%Y"), "date", "data_date")) self.description = str( self.get_kwarg(kwargs, "Project description", "description", "data_description")) self.author = str( self.get_kwarg(kwargs, "Project author", "author", "data_author")) load_default_data = self.get_kwarg(kwargs, True, "load_default_data") if load_default_data and self.layout_mode != 1 and \ len(self.atom_types) == 0: self.load_default_data() self.needs_saving = True pass # end with visuals_changed pass # end with data_changed def load_default_data(self): for atom_type in AtomType.get_from_csv( settings.DATA_REG.get_file_path("ATOM_SCAT_FACTORS")): self.atom_types.append(atom_type) # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ def on_phase_inserted(self, item): # Set parent on the new phase: if item.parent != self: item.parent = self item.resolve_json_references() def on_phase_removed(self, item): with self.data_changed.hold_and_emit(): # Clear parent: item.parent = None # Clear links with other phases: if getattr(item, "based_on", None) is not None: item.based_on = None for phase in self.phases: if getattr(phase, "based_on", None) == item: phase.based_on = None # Remove phase from mixtures: for mixture in self.mixtures: mixture.unset_phase(item) def on_atom_type_inserted(self, item, *data): if item.parent != self: item.parent = self # We do not observe AtomType's directly, if they change, # Atoms containing them will be notified, and that event should bubble # up to the project level. def on_atom_type_removed(self, item, *data): item.parent = None # We do not emit a signal for AtomType's, if it was part of # an Atom, the Atom will be notified, and the event should bubble # up to the project level def on_specimen_inserted(self, item): # Set parent and observe the new specimen (visuals changed signals): if item.parent != self: item.parent = self self.observe_model(item) def on_specimen_removed(self, item): with self.data_changed.hold_and_emit(): # Clear parent & stop observing: item.parent = None self.relieve_model(item) # Remove specimen from mixtures: for mixture in self.mixtures: mixture.unset_specimen(item) def on_mixture_inserted(self, item): # Set parent and observe the new mixture: if item.parent != self: item.parent = self self.observe_model(item) def on_mixture_removed(self, item): with self.data_changed.hold_and_emit(): # Clear parent & stop observing: item.parent = None self.relieve_model(item) def on_behaviour_inserted(self, item): # Set parent and observe the new mixture: if item.parent != self: item.parent = self self.observe_model(item) def on_behaviour_removed(self, item): with self.data_changed.hold_and_emit(): # Clear parent & stop observing: item.parent = None self.relieve_model(item) @DataModel.observe("data_changed", signal=True) def notify_data_changed(self, model, prop_name, info): self.needs_saving = True if isinstance(model, Mixture): self.data_changed.emit() @DataModel.observe("visuals_changed", signal=True) def notify_visuals_changed(self, model, prop_name, info): self.needs_saving = True self.visuals_changed.emit() # propagate signal # ------------------------------------------------------------ # Input/Output stuff # ------------------------------------------------------------ @classmethod def from_json(type, **kwargs): # @ReservedAssignment project = type(**kwargs) project.needs_saving = False # don't mark this when just loaded return project def to_json_multi_part(self): to_json = self.to_json() properties = to_json["properties"] for name in ("phases", "specimens", "atom_types", "mixtures"): #"behaviours" yield (name, properties.pop(name)) properties[name] = "file://%s" % name yield ("content", to_json) yield ("version", __version__) @staticmethod def create_from_sybilla_xml(filename, **kwargs): from pyxrd.project.importing import create_project_from_sybilla_xml return create_project_from_sybilla_xml(filename, **kwargs) # ------------------------------------------------------------ # Draggable mix-in hook: # ------------------------------------------------------------ def on_label_dragged(self, delta_y, button=1): if button == 1: self.display_label_pos += delta_y pass # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def get_scale_factor(self, specimen=None): """ Get the factor with which to scale raw data and the scaled offset :rtype: tuple containing the scale factor and the (scaled) offset """ if self.axes_ynormalize == 0 or (self.axes_ynormalize == 1 and specimen is None): return (1.0 / (self.get_max_display_y() or 1.0), 1.0) elif self.axes_ynormalize == 1: return (1.0 / (specimen.get_max_display_y or 1.0), 1.0) elif self.axes_ynormalize == 2: return (1.0, self.get_max_display_y()) else: raise ValueError( "Wrong value for 'axes_ysnormalize' in %s: is `%d`; should be 0, 1 or 2" % (self, self.axes_ynormalize)) def get_max_display_y(self): max_display_y = 0 if self.parent is not None: for specimen in self.parent.current_specimens: max_display_y = max(specimen.max_display_y, max_display_y) return max_display_y @contextmanager def hold_child_signals(self): logger.info("Holding back all project child object signals") with self.hold_mixtures_needs_update(): with self.hold_mixtures_data_changed(): with self.hold_phases_data_changed(): with self.hold_specimens_data_changed(): with self.hold_atom_types_data_changed(): yield @contextmanager def hold_mixtures_needs_update(self): logger.info("Holding back all 'needs_update' signals from Mixtures") with EventContextManager( *[mixture.needs_update.hold() for mixture in self.mixtures]): yield @contextmanager def hold_mixtures_data_changed(self): logger.info("Holding back all 'data_changed' signals from Mixtures") with EventContextManager( *[mixture.data_changed.hold() for mixture in self.mixtures]): yield @contextmanager def hold_phases_data_changed(self): logger.info("Holding back all 'data_changed' signals from Phases") with EventContextManager( *[phase.data_changed.hold() for phase in self.phases]): yield @contextmanager def hold_atom_types_data_changed(self): logger.info("Holding back all 'data_changed' signals from AtomTypes") with EventContextManager( * [atom_type.data_changed.hold() for atom_type in self.atom_types]): yield @contextmanager def hold_specimens_data_changed(self): logger.info("Holding back all 'data_changed' signals from Specimens") with EventContextManager( *[specimen.data_changed.hold() for specimen in self.specimens]): yield def update_all_mixtures(self): """ Forces all mixtures in this project to update. If they have auto optimization enabled, this will also optimize them. """ for mixture in self.mixtures: with self.data_changed.ignore(): mixture.update() def get_mixtures_by_name(self, mixture_name): """ Convenience method that returns all the mixtures who's name match the passed name as a list. """ return [ mixture for mixture in self.mixtures if (mixture.name == mixture_name) ] # ------------------------------------------------------------ # Specimen list related # ------------------------------------------------------------ def move_specimen_up(self, specimen): """ Move the passed :class:`~pyxrd.specimen.models.Specimen` up one slot. Will raise and IndexError if the passed specimen is not in this project. """ index = self.specimens.index(specimen) self.specimens.insert(min(index + 1, len(self.specimens)), self.specimens.pop(index)) def move_specimen_down(self, specimen): """ Move the passed :class:`~pyxrd.specimen.models.Specimen` down one slot Will raise and IndexError if the passed specimen is not in this project. """ index = self.specimens.index(specimen) self.specimens.insert(max(index - 1, 0), self.specimens.pop(index)) pass # ------------------------------------------------------------ # Phases list related # ------------------------------------------------------------ def load_phases(self, filename, parser, insert_index=0): """ Loads all :class:`~pyxrd.phase.models.Phase` objects from the file 'filename'. An optional index can be given where the phases need to be inserted at. """ # make sure we have no duplicate UUID's insert_index = not_none(insert_index, 0) type(Project).object_pool.change_all_uuids() for phase in parser.parse(filename): phase.parent = self self.phases.insert(insert_index, phase) insert_index += 1 # ------------------------------------------------------------ # AtomType's list related # ------------------------------------------------------------ def load_atom_types(self, filename, parser): """ Loads all :class:`~pyxrd.atoms.models.AtomType` objects from the file specified by *filename*. """ # make sure we have no duplicate UUID's type(Project).object_pool.change_all_uuids() for atom_type in parser.parse(filename): atom_type.parent = self self.atom_types.append(atom_type) pass # end of class
class DritsCSDSDistribution(_AbstractCSDSDistribution, RefinementValue): # MODEL INTEL: class Meta(_AbstractCSDSDistribution.Meta): description = "Log-normal CSDS distr. (Drits et. al, 1997)" store_id = "DritsCSDSDistribution" # PROPERTIES: alpha_scale = FloatProperty(default=0.9485, text="α scale factor", minimum=0.0, maximum=10.0, tabular=True, persistent=False, visible=False, refinable=False, mix_with=(ReadOnlyMixin, DataMixin, RefinableMixin)) alpha_offset = FloatProperty(default=0.017, text="α offset factor", minimum=-5, maximum=5, tabular=True, persistent=False, visible=False, refinable=False, mix_with=(ReadOnlyMixin, DataMixin, RefinableMixin)) beta_scale = FloatProperty(default=0.1032, text="β² scale factor", minimum=0.0, maximum=10.0, tabular=True, persistent=False, visible=False, refinable=False, mix_with=(ReadOnlyMixin, DataMixin, RefinableMixin)) beta_offset = FloatProperty(default=0.0034, text="β² offset factor", minimum=-5, maximum=5, tabular=True, persistent=False, visible=False, refinable=False, mix_with=(ReadOnlyMixin, DataMixin, RefinableMixin)) # REFINEMENT VALUE IMPLEMENTATION: @property def refine_title(self): return "Average CSDS" @property def refine_descriptor_data(self): return dict(phase_name=self.phase.name, component_name="*", property_name=self.refine_title) @property def refine_value(self): return self.average @refine_value.setter def refine_value(self, value): self.average = value @property def refine_info(self): return self.average_ref_info @property def is_refinable(self): return not self.inherited # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): for key in [ "alpha_scale", "alpha_offset", "beta_scale", "beta_offset" ]: kwargs.pop(key, None) super(DritsCSDSDistribution, self).__init__(*args, **kwargs) pass # end of class
class PyXRDLine(StorableXYData): """ A PyXRDLine is an abstract attribute holder for a real 'Line' object, whatever the plotting library used may be. Attributes are line width and color. """ # MODEL INTEL: class Meta(StorableXYData.Meta): store_id = "PyXRDLine" # OBSERVABLE PROPERTIES: #: The line label label = StringProperty(default="", text="Label", persistent=True) #: The line color color = StringProperty(default="#000000", text="Label", visible=True, persistent=True, widget_type="color", inherit_flag="inherit_color", inherit_from="parent.parent.display_exp_color", signal_name="visuals_changed", mix_with=(InheritableMixin, SignalMixin)) #: Flag indicating whether to use the grandparents color yes/no inherit_color = BoolProperty(default=True, text="Inherit color", visible=True, persistent=True, signal_name="visuals_changed", mix_with=(SignalMixin, )) #: The linewidth in points lw = FloatProperty( default=2.0, text="Linewidth", visible=True, persistent=True, widget_type="spin", inherit_flag="inherit_lw", inherit_from="parent.parent.display_exp_lw", signal_name="visuals_changed", mix_with=(InheritableMixin, SignalMixin), ) #: Flag indicating whether to use the grandparents linewidth yes/no inherit_lw = BoolProperty( default=True, text="Inherit linewidth", visible=True, persistent=True, signal_name="visuals_changed", mix_with=(SignalMixin, ), ) #: A short string describing the (matplotlib) linestyle ls = StringChoiceProperty( default=settings.EXPERIMENTAL_LINESTYLE, text="Linestyle", visible=True, persistent=True, choices=settings.PATTERN_LINE_STYLES, mix_with=( InheritableMixin, SignalMixin, ), signal_name="visuals_changed", inherit_flag="inherit_ls", inherit_from="parent.parent.display_exp_ls", ) #: Flag indicating whether to use the grandparents linestyle yes/no inherit_ls = BoolProperty(default=True, text="Inherit linestyle", visible=True, persistent=True, mix_with=(SignalMixin, ), signal_name="visuals_changed") #: A short string describing the (matplotlib) marker marker = StringChoiceProperty( default=settings.EXPERIMENTAL_MARKER, text="Marker", visible=True, persistent=True, choices=settings.PATTERN_MARKERS, mix_with=( InheritableMixin, SignalMixin, ), signal_name="visuals_changed", inherit_flag="inherit_marker", inherit_from="parent.parent.display_exp_marker", ) #: Flag indicating whether to use the grandparents linewidth yes/no inherit_marker = BoolProperty( default=True, text="Inherit marker", visible=True, persistent=True, mix_with=(SignalMixin, ), signal_name="visuals_changed", ) #: z-data (e.g. relative humidity, temperature, for multi-column 'lines') z_data = ListProperty(default=None, text="Z data", data_type=float, persistent=True, visible=False) # REGULAR PROPERTIES: @property def max_display_y(self): if self.num_columns > 2: # If there's several y-columns, check if we have z-data associated with them # if so, it is a 2D pattern, otherwise this is a multi-line pattern if len(self.z_data) > 2: return np.max(self.z_data) else: return self.max_y else: # If there's a single comumn of y-data, just get the max value return self.max_y @property def min_intensity(self): if self.num_columns > 2: return np.min(self.z_data) else: return self.min_y @property def abs_max_intensity(self): return self.abs_max_y # ------------------------------------------------------------ # Initialisation and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): """ Valid keyword arguments for a PyXRDLine are: data: the actual data containing x and y values label: the label for this line color: the color of this line inherit_color: whether to use the parent-level color or its own lw: the line width of this line inherit_lw: whether to use the parent-level line width or its own ls: the line style of this line inherit_ls: whether to use the parent-level line style or its own marker: the line marker of this line inherit_marker: whether to use the parent-level line marker or its own z_data: the z-data associated with the columns in a multi-column pattern """ my_kwargs = self.pop_kwargs( kwargs, *[ prop.label for prop in PyXRDLine.Meta.get_local_persistent_properties() ]) super(PyXRDLine, self).__init__(*args, **kwargs) kwargs = my_kwargs with self.visuals_changed.hold(): self.label = self.get_kwarg(kwargs, self.label, "label") self.color = self.get_kwarg(kwargs, self.color, "color") self.inherit_color = bool( self.get_kwarg(kwargs, self.inherit_color, "inherit_color")) self.lw = float(self.get_kwarg(kwargs, self.lw, "lw")) self.inherit_lw = bool( self.get_kwarg(kwargs, self.inherit_lw, "inherit_lw")) self.ls = self.get_kwarg(kwargs, self.ls, "ls") self.inherit_ls = bool( self.get_kwarg(kwargs, self.inherit_ls, "inherit_ls")) self.marker = self.get_kwarg(kwargs, self.marker, "marker") self.inherit_marker = bool( self.get_kwarg(kwargs, self.inherit_marker, "inherit_marker")) self.z_data = list(self.get_kwarg(kwargs, [0], "z_data")) # ------------------------------------------------------------ # Input/Output stuff # ------------------------------------------------------------ @classmethod def from_json(cls, **kwargs): # @ReservedAssignment if "xy_store" in kwargs: if "type" in kwargs["xy_store"]: kwargs["data"] = kwargs["xy_store"]["properties"]["data"] elif "xy_data" in kwargs: if "type" in kwargs["xy_data"]: kwargs["data"] = kwargs["xy_data"]["properties"]["data"] kwargs["label"] = kwargs["data_label"] del kwargs["data_name"] del kwargs["data_label"] del kwargs["xy_data"] return cls(**kwargs) # ------------------------------------------------------------ # Convenience Methods & Functions # ------------------------------------------------------------ def interpolate(self, *x_vals, **kwargs): """ Returns a list of (x, y) tuples for the passed x values. An optional column keyword argument can be passed to select a column, by default the first y-column is used. Returned y-values are interpolated. """ column = kwargs.get("column", 0) f = interp1d(self.data_x, self.data_y[:, column]) return list(zip(x_vals, f(x_vals))) def get_plotted_y_at_x(self, x): """ Gets the (interpolated) plotted value at the given x position. If this line has not been plotted (or does not have access to a '__plot_line' attribute set by the plotting routines) it will return 0. """ try: xdata, ydata = getattr(self, "__plot_line").get_data() except AttributeError: logging.exception( "Attribute error when trying to get plotter data at x position!" ) else: if len(xdata) > 0 and len(ydata) > 0: return np.interp(x, xdata, ydata) return 0 def calculate_npeaks_for(self, max_threshold, steps): """ Calculates the number of peaks for `steps` threshold values between 0 and `max_threshold`. Returns a tuple containing two lists with the threshold values and the corresponding number of peaks. """ length = self.data_x.size resolution = length / (self.data_x[-1] - self.data_x[0]) delta_angle = 0.05 window = int(delta_angle * resolution) window += (window % 2) * 2 steps = max(steps, 2) - 1 factor = max_threshold / steps deltas = [i * factor for i in range(0, steps)] numpeaks = [] maxtabs, mintabs = multi_peakdetect(self.data_y[:, 0], self.data_x, 5, deltas) for maxtab, _ in zip(maxtabs, mintabs): numpeak = len(maxtab) numpeaks.append(numpeak) numpeaks = list(map(float, numpeaks)) return deltas, numpeaks def get_best_threshold(self, max_threshold=None, steps=None, status_dict=None): """ Estimates the best threshold for peak detection using an iterative algorithm. Assumes there is a linear contribution from noise. Returns a 4-tuple containing the selected threshold, the maximum threshold, a list of threshold values and a list with the corresponding number of peaks. """ length = self.data_x.size steps = not_none(steps, 20) threshold = 0.1 max_threshold = not_none(max_threshold, threshold * 3.2) def get_new_threshold(threshold, deltas, num_peaks, ln): # Left side line: x = deltas[:ln] y = num_peaks[:ln] slope, intercept, R, _, _ = stats.linregress(x, y) return R, -intercept / slope if length > 2: # Adjust the first distribution: deltas, num_peaks = self.calculate_npeaks_for(max_threshold, steps) # Fit several lines with increasing number of points from the # generated threshold / marker count graph. Stop when the # R-coefficiënt drops below 0.95 (past linear increase from noise) # Then repeat this by increasing the resolution of data points # and continue until the result does not change anymore last_threshold = None solution = False max_iters = 10 min_iters = 3 itercount = 0 if status_dict is not None: status_dict["progress"] = 0 while not solution: # Number of points to use for the lin regress: ln = 4 # Maximum number of points to use: max_ln = len(deltas) # Flag indicating if we can stop searching for the linear part stop = False while not stop: R, threshold = get_new_threshold(threshold, deltas, num_peaks, ln) max_threshold = threshold * 3.2 if abs(R) < 0.98 or ln >= max_ln: stop = True else: ln += 1 itercount += 1 # Increase # of iterations if last_threshold: # Check if we have run at least `min_iters`, at most `max_iters` # and have not reached an equilibrium. solution = bool( itercount > min_iters and not (itercount <= max_iters and last_threshold - threshold >= 0.001)) if not solution: deltas, num_peaks = self.calculate_npeaks_for( max_threshold, steps) last_threshold = threshold if status_dict is not None: status_dict["progress"] = float(itercount / max_iters) return (deltas, num_peaks), threshold, max_threshold else: return ([], []), threshold, max_threshold pass # end of class
class R2G2Model(_AbstractProbability): r""" Probability model for Reichweite 2 with 2 components. The 4 (=g^2) independent variables are: .. math:: :nowrap: \begin{align*} & W_1 & P_{112} (W_1 leq \nicefrac{2}{3}) \text{ or }P_{211} (W_1 > \nicefrac{2}{3}) \\ & P_{21} & P_{122} (P_{21} leq \nicefrac{1}{2}) \text{ or }P_{221} (P_{21} > \nicefrac{1}{2}) \\ \end{align*} Calculation of the other variables happens as follows: .. math:: :nowrap: \begin{align*} & W_2 = 1 - W_1 \\ & P_{22} = 1 - P_{21} \\ & \\ & W_{21} = W_2 \cdot P_{21} \\ & W_{21} = W_{12} \\ & W_{11} = W_1 - W_{21} \\ & W_{22} = W_{2} \cdot P_{22} \\ & \\ & \text{if $W_1 leq \nicefrac{2}{3}$:} \\ & \quad \text{$P_{112}$ is given}\\ & \quad P_{211} = \begin{dcases} \frac{W_{11}}{W_{21}} \cdot P_{112} , & \text{if $W_{21} > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & \\ & \text{if $W_1 > \nicefrac{2}{3}$:} \\ & \quad \text{$P_{211}$ is given}\\ & \quad P_{112} = \begin{dcases} \frac{W_{21}}{W_{11}} \cdot P_{211} , & \text{if $W_{11} > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & \\ & P_{212} = 1 - P_{211} \\ & P_{111} = 1 - P_{112} \\ & \\ & \text{if $P_{21} leq \nicefrac{1}{2}$:} \\ & \quad \text{$P_{122}$ is given}\\ & \quad P_{221} = \begin{dcases} \frac{W_{12}}{W_{22}} \cdot P_{122} , & \text{if $W_{22} > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & \\ & \text{if $P_{21} > \nicefrac{1}{2}$:} \\ & \quad \text{$P_{221}$ is given}\\ & \quad P_{122} = \begin{dcases} \frac{W_{22}}{W_{12}} \cdot P_{221} , & \text{if $W_{12} > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & P_{121} = 1 - P_{122} \\ & P_{222} = 1 - P_{221} \\ \end{align*} """ # MODEL METADATA: class Meta(_AbstractProbability.Meta): store_id = "R2G2Model" # PROPERTIES: _G = 2 twothirds = 2.0 / 3.0 inherit_W1 = BoolProperty(default=False, text="Inherit flag for W1", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) W1 = FloatProperty(default=0.75, text="W1 (> 0.5)", math_text=r"$W_1 (> 0.5)$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.5, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_W1", inherit_from="parent.based_on.probabilities.W1", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_P112_or_P211 = BoolProperty(default=False, text="Inherit flag for P112_or_P211", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) P112_or_P211 = FloatProperty( default=0.75, text="P112 (W1 < 2/3) or\nP211 (W1 > 2/3)", math_text=r"$P_{112} %s$ or $\newlineP_{211} %s$" % (mt_range( 1.0 / 2.0, "W_1", 2.0 / 3.0), mt_range(2.0 / 3.0, "W_1", 1.0)), persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_P112_or_P211", inherit_from="parent.based_on.probabilities.P112_or_P211", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_P21 = BoolProperty(default=False, text="Inherit flag for P21", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) P21 = FloatProperty(default=0.75, text="P21", math_text=r"$P_{21}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_P21", inherit_from="parent.based_on.probabilities.P21", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_P122_or_P221 = BoolProperty(default=False, text="Inherit flag for P122_or_P221", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) P122_or_P221 = FloatProperty( default=0.75, text="P112 (W1 < 1/2) or\nP221 (W1 > 1/2)", math_text=r"$P_{122} %s$ or $\newlineP_{221} %s$" % (mt_range(0.0, "W_1", 1.0 / 2.0), mt_range(1.0 / 2.0, "W_1", 1.0)), persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_P122_or_P221", inherit_from="parent.based_on.probabilities.P122_or_P221", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, W1=0.75, P112_or_P211=0.75, P21=0.75, P122_or_P221=0.75, inherit_W1=False, inherit_P112_or_P211=False, inherit_P21=False, inherit_P122_or_P221=False, *args, **kwargs): super(R2G2Model, self).__init__(R=2, *args, **kwargs) with self.data_changed.hold(): self.W1 = not_none(W1, 0.75) self.inherit_W1 = inherit_W1 self.P112_or_P211 = not_none(P112_or_P211, 0.75) self.inherit_P112_or_P211 = inherit_P112_or_P211 self.P21 = not_none(P21, 0.75) self.inherit_P21 = inherit_P21 self.P122_or_P221 = not_none(P122_or_P221, 0.75) self.inherit_P122_or_P221 = inherit_P122_or_P221 self.update() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def update(self): with self.monitor_changes(): self.mW[0] = self.W1 self.mW[1] = 1.0 - self.mW[0] self.mP[1, 0] = self.P21 self.mP[1, 1] = 1.0 - self.mP[1, 0] self.mW[1, 0] = self.mW[1] * self.mP[1, 0] self.mW[1, 1] = self.mW[1] * self.mP[1, 1] self.mW[0, 1] = self.mW[1, 0] self.mW[0, 0] = self.mW[0] - self.mW[1, 0] if self.mW[0] <= self.twothirds: self.mP[0, 0, 1] = self.P112_or_P211 if self.mW[1, 0] == 0.0: self.mP[1, 0, 0] = 0.0 else: self.mP[1, 0, 0] = self.mP[0, 0, 1] * self.mW[0, 0] / self.mW[1, 0] else: self.mP[1, 0, 0] = self.P112_or_P211 if self.mW[0, 0] == 0.0: self.mP[0, 0, 1] = 0.0 else: self.mP[0, 0, 1] = self.mP[1, 0, 0] * self.mW[1, 0] / self.mW[0, 0] self.mP[1, 0, 1] = 1.0 - self.mP[1, 0, 0] self.mP[0, 0, 0] = 1.0 - self.mP[0, 0, 1] if self.mP[1, 0] <= 0.5: self.mP[0, 1, 1] = self.P122_or_P221 self.mP[1, 1, 0] = self.mP[0, 1, 1] * self.mW[0, 1] / self.mW[1, 1] else: self.mP[1, 1, 0] = self.P122_or_P221 self.mP[0, 1, 1] = self.mP[1, 1, 0] * self.mW[1, 1] / self.mW[0, 1] self.mP[0, 1, 0] = 1.0 - self.mP[0, 1, 1] self.mP[1, 1, 1] = 1.0 - self.mP[1, 1, 0] self.solve() self.validate() pass # end of class
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
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
class AtomRelation(ComponentPropMixin, RefinementValue, DataModel, Storable, metaclass=PyXRDRefinableMeta): # MODEL INTEL: class Meta(DataModel.Meta): store_id = "AtomRelation" file_filters = [ ("Atom relation", get_case_insensitive_glob("*.atr")), ] allowed_relations = {} component = property(DataModel.parent.fget, DataModel.parent.fset) # PROPERTIES: #: The name of this AtomRelation name = StringProperty(default="", text="Name", visible=True, persistent=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin, )) #: The value of this AtomRelation value = FloatProperty(default=0.0, text="Value", visible=True, persistent=True, tabular=True, widget_type='float_entry', signal_name="data_changed", refinable=True, mix_with=(SignalMixin, RefinableMixin)) #: Flag indicating whether this AtomRelation is enabled or not enabled = BoolProperty(default=True, text="Enabled", visible=True, persistent=True, tabular=True, signal_name="data_changed", mix_with=(SignalMixin, )) #: Is True when this AtomRelation's value is driven by another AtomRelation. #: Should never be set directly or things might break! driven_by_other = BoolProperty(default=False, text="Driven by other", visible=False, persistent=False, tabular=True) @property def applicable(self): """ Is True when this AtomRelation was passed a component of which the atom ratios are not set to be inherited from another component. """ return (self.parent is not None and not self.parent.inherit_atom_relations) # 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): self.value = value @property def inside_linked_component(self): return (self.component.linked_with is not None) and self.component.inherit_atom_relations @property def is_refinable(self): return self.enabled and not self.driven_by_other and not self.inside_linked_component @property def refine_info(self): return self.value_ref_info # ------------------------------------------------------------ # Initialisation and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): """ Valid keyword arguments for an AtomRelation are: name: the name of this AtomRelation value: the value for this AtomRelation enabled: boolean indicating whether or not this AtomRelation is enabled """ my_kwargs = self.pop_kwargs( kwargs, "data_name", "data_ratio", "ratio", *[ prop.label for prop in AtomRelation.Meta.get_local_persistent_properties() ]) super(AtomRelation, self).__init__(*args, **kwargs) kwargs = my_kwargs self.name = self.get_kwarg(kwargs, "", "name", "data_name") self.value = self.get_kwarg(kwargs, 0.0, "value", "ratio", "data_ratio") self.enabled = bool(self.get_kwarg(kwargs, True, "enabled")) # ------------------------------------------------------------ # Input/Output stuff # ------------------------------------------------------------ def resolve_relations(self): raise NotImplementedError( "Subclasses should implement the resolve_relations method!") # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def create_prop_store(self, prop=None): if self.component is not None: store = Gtk.ListStore(object, str, str) 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 relation in self.component._atom_relations: tp = relation.Meta.store_id if tp in self.Meta.allowed_relations: for prop, name in self.Meta.allowed_relations[tp]: if callable(name): name = name(relation) store.append([relation, prop, name]) return store def iter_references(self): raise NotImplementedError( "'iter_references' should be implemented by subclasses!") def _safe_is_referring(self, value): if value is not None and hasattr(value, "is_referring"): return value.is_referring([ self, ]) else: return False def is_referring(self, references=None): """ Checks whether this AtomRelation is causing circular references. Can be used to check this before actually setting references by setting the 'references' keyword argument to a list containing the new reference value(s). """ if references == None: references = [] # 1. Bluntly check if we're not already somewhere referred to, # if not, add ourselves to the list of references if self in references: return True references.append(self) # 2. Loop over our own references, check if they cause a circular # reference, if not add them to the list of references. for reference in self.iter_references(): if reference is not None and hasattr(reference, "is_referring"): if reference.is_referring(references): return True else: references.append(reference) return False def _set_driven_flag_for_prop(self, prop=None): """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.""" self.driven_by_other = True def apply_relation(self): raise NotImplementedError( "Subclasses should implement the apply_relation method!") pass # end of class
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
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
class R1G2Model(_AbstractProbability): r""" Probability model for Reichweite 1 with 2 components. The 2(=g*(g-1)) independent variables are: .. math:: :nowrap: \begin{flalign*} & W_1 \\ & \text{$P_{11} (W_1 < 0.5)$ or $P_{22} (W_1 > 0.5)$} \end{flalign*} Calculation of the other variables happens as follows: .. math:: :nowrap: \begin{align*} & W_2 = 1 – W_1 \\ & \begin{aligned} & \text{$P_{11}$ is given:} \\ & \quad P_{12} = 1 - P_{11} \\ & \quad P_{21} = \frac{W_1 \cdot P_{12}}{W2} \\ & \quad P_{22} = 1 - P_{21} \\ \end{aligned} \quad \quad \begin{aligned} & \text{$P_{22}$ is given:} \\ & \quad P_{21} = 1 - P_{22} \\ & \quad P_{12} = \frac{W_2 \cdot P_{21}}{W1} \\ & \quad P_{11} = 1 - P_{12} \\ \end{aligned} \\ \end{align*} """ # MODEL METADATA: class Meta(_AbstractProbability.Meta): store_id = "R1G2Model" # PROPERTIES: _G = 2 inherit_W1 = BoolProperty(default=False, text="Inherit flag for W1", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) W1 = FloatProperty(default=0.0, text="W1", math_text=r"$W_1$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_W1", inherit_from="parent.based_on.probabilities.W1", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_P11_or_P22 = BoolProperty(default=False, text="Inherit flag for P11_or_P22", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) P11_or_P22 = FloatProperty( default=0.0, text="P11_or_P22", math_text=r"$P_{11} %s$ or $\newline P_{22} %s$" % (mt_range(0.0, "W_1", 0.5), mt_range(0.5, "W_1", 1.0)), persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_P11_or_P22", inherit_from="parent.based_on.probabilities.P11_or_P22", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, W1=0.75, P11_or_P22=0.5, inherit_W1=False, inherit_P11_or_P22=False, *args, **kwargs): super(R1G2Model, self).__init__(R=1, *args, **kwargs) with self.data_changed.hold(): self.W1 = not_none(W1, 0.75) self.inherit_W1 = inherit_W1 self.P11_or_P22 = not_none(P11_or_P22, 0.5) self.inherit_P11_or_P22 = inherit_P11_or_P22 self.update() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def update(self): with self.monitor_changes(): self.mW[0] = self.W1 self.mW[1] = 1.0 - self.mW[0] if self.mW[0] <= 0.5: self.mP[0, 0] = self.P11_or_P22 self.mP[0, 1] = 1.0 - self.mP[0, 0] self.mP[1, 0] = self.mW[0] * self.mP[0, 1] / self.mW[1] self.mP[1, 1] = 1.0 - self.mP[1, 0] else: self.mP[1, 1] = self.P11_or_P22 self.mP[1, 0] = 1.0 - self.mP[1, 1] self.mP[0, 1] = self.mW[1] * self.mP[1, 0] / self.mW[0] self.mP[0, 0] = 1.0 - self.mP[0, 1] self.solve() self.validate() pass # end of class
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
class R1G4Model(_AbstractProbability): r""" Probability model for Reichweite 1 with 4 components. The independent variables (# = g*(g-1) = 12) are: .. math:: :nowrap: \begin{align*} & W_1 & P_{11} (W_1 < 0,5)\text{ or }P_{xx} (W_1 > 0,5) with P_{xx} = \frac {W_{22} + W_{23} + W_{24} + W_{32} + W_{33} + W_{34} + W_{42} + W_{43} + W_{44}}{W_2 + W_3 + W_4} \\ & R_2 = \frac{ W_2 }{W_2 + W_3 + W_4} & R_3 = \frac{ W_3 }{W_3 + W_4} \\ & G_2 = \frac{W_{22} + W_{23} + W_{24}}{\sum_{i=2}^{4}\sum_{j=2}^4{W_{ij}}} & G_3 = \frac{W_{32} + W_{33} + W_{34}}{\sum_{i=3}^{4}\sum_{j=2}^4{W_{ij}}} \\ & G_{22} = \frac{W_{22}}{W_{22} + W_{23} + W_{24}} & G_{23} = \frac{W_{23}}{W_{23} + W_{24}} \\ & G_{32} = \frac{W_{32}}{W_{32} + W_{33} + W_{34}} & G_{33} = \frac{W_{33}}{W_{33} + W_{34}} \\ & G_{42} = \frac{W_{42}}{W_{42} + W_{43} + W_{44}} & G_{44} = \frac{W_{43}}{W_{43} + W_{44}} \end{align*} Calculation of the other variables happens as follows: .. math:: :nowrap: \begin{align*} & \text{Calculate the base weight fractions of each component:} \\ & W_2 = (1 - W_1) \cdot R_1 \\ & W_3 = (1 - W_1 - W_2) \cdot R_2 \\ & W_4 = (1 - W_1 - W_2 - W_3) \\ & \\ & \text{if $W_1 \leq 0.5$:} \\ & \quad \text{$P_{11}$ is given}\\ & \quad W_{xx} = W_{22} + W_{23} + W_{24} + W_{32} + W_{33} + W_{34} + W_{42} + W_{43} + W_{44} = W_1 \cdot (1 - P_{11}) + W_2 + W_3 + W_4 \\ & \text{if $W_1 > 0.5$:} \\ & \quad \text{$P_{xx}$ is given and $P_{11}$ is derived further down} \\ & \quad W_{xx} = W_{22} + W_{23} + W_{24} + W_{32} + W_{33} + W_{34} + W_{42} + W_{43} + W_{44} = P_{xx} \cdot (W_2 + W_3 + W_4) \\ & \\ & \text{Caclulate a partial sum of the $2^{nd}$ component's contributions: } \\ & W_{2x} = W_{xx} \cdot G_2 \\ & \text{Calculate a partial sum of the $3^{d}$ and $4^{th}$ component's contributions:} \\ & W_{yx} = W_{xx} - W_{2x} \\ & \text{Calculate a partial sum of the $3^{d}$ component's contributions:} \\ & W_{3x} = W_{yx} \cdot G_3 \\ & \text{Calculate a partial sum of the $4^{th}$ component's contributions:} \\ & W_{4x} = W_{yx} - W_{3x} \\ & \\ & W_{22} = G_{22} \cdot W_{2x} \\ & W_{23} = G_{23} \cdot (W_{2x} - W_{22}) \\ & W_{24} = W{2x} - W_{22} - W_{23} \\ & \\ & W_{32} = G_{32} \cdot W_{3x} \\ & W_{33} = G_{33} \cdot (W_{3x} - W_{32}) \\ & W_{34} = W{3x} - W_{32} - W_{33} \\ & \\ & W_{42} = G_{42} \cdot W_{4x} \\ & W_{43} = G_{43} \cdot (W_{4x} - W_{42}) \\ & W_{44} = W{4x} - W_{42} - W_{43} \\ & \\ & \text{ From the above weight fractions the junction probabilities for any combination of $2^{nd}$, $3^{d}$ and $4^{th}$ type components can be calculated. } \\ & \text{ The remaining probabilities are: } \\ & P_{12} = \begin{dcases} \frac{W_2 - W_{22} - W_{32} - W_{42}}{W_1}, & \text{if $W_1 > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & P_{13} = \begin{dcases} \frac{W_3 - W_{23} - W_{33} - W_{43}}{W_1}, & \text{if $W_1 > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & P_{14} = \begin{dcases} \frac{W_4 - W_{24} - W_{34} - W_{44}}{W_1}, & \text{if $W_1 > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & \\ & \text{if $W_1 \leq 0.5$}: \\ & \quad P_{11} = 1 - P_{12} - P_{13} - P_{14} \\ & \text{Remainder of weight fraction can now be calculated as follows:} \\ & \quad W_{ij} = {W_{ii}} \cdot {P_{ij}} \quad \forall {i,j} \in \left[ {1, 4} \right] \\ \end{align*} """ # MODEL METADATA: class Meta(_AbstractProbability.Meta): store_id = "R1G4Model" # PROPERTIES _G = 4 inherit_W1 = BoolProperty(default=False, text="Inherit flag for W1", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) W1 = FloatProperty(default=0.6, text="W1", math_text=r"$W_1$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_W1", inherit_from="parent.based_on.probabilities.W1", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_P11_or_P22 = BoolProperty(default=False, text="Inherit flag for P11_or_P22", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) P11_or_P22 = FloatProperty( default=0.25, text="P11_or_P22", math_text=r"$P_{11} %s$ or $\newline P_{22} %s$" % (mt_range(0.0, "W_1", 0.5), mt_range(0.5, "W_1", 1.0)), persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_P11_or_P22", inherit_from="parent.based_on.probabilities.P11_or_P22", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_R1 = BoolProperty(default=False, text="Inherit flag for R1", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) R1 = FloatProperty(default=0.5, text="W2/(W2+W3+W4)", math_text=r"$\large\frac{W_2}{W_2 + W_3 + W_4}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_R1", inherit_from="parent.based_on.probabilities.R1", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_R2 = BoolProperty(default=False, text="Inherit flag for R2", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) R2 = FloatProperty(default=0.5, text="W3/(W3+W4)", math_text=r"$\large\frac{W_3}{W_3 + W_4}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_R2", inherit_from="parent.based_on.probabilities.R2", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_G1 = BoolProperty(default=False, text="Inherit flag for G1", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) G1 = FloatProperty( default=0.5, text="(W22+W23+W24)/(W22+W23+W24+W32+W33+W34+W42+W43+W44)", math_text= r"$\large\frac{\sum_{j=2}^{4} W_{2j}}{\sum_{i=2}^{4} \sum_{j=2}^{4} W_{ij}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G1", inherit_from="parent.based_on.probabilities.G1", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_G2 = BoolProperty(default=False, text="Inherit flag for G2", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) G2 = FloatProperty( default=0.4, text="(W32+W33+W34)/(W32+W33+W34+W42+W43+W44)", math_text= r"$\large\frac{\sum_{j=2}^{4} W_{3j}}{\sum_{i=3}^{4} \sum_{j=2}^{4} W_{ij}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G2", inherit_from="parent.based_on.probabilities.G2", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_G11 = BoolProperty(default=False, text="Inherit flag for G11", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) G11 = FloatProperty( default=0.5, text="W22/(W22+W23+W24)", math_text=r"$\large\frac{W_{22}}{\sum_{j=2}^{4} W_{2j}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G11", inherit_from="parent.based_on.probabilities.G11", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_G12 = BoolProperty(default=False, text="Inherit flag for G12", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) G12 = FloatProperty( default=0.5, text="W23/(W23+W24)", math_text=r"$\large\frac{W_{23}}{\sum_{j=3}^{4} W_{2j}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G12", inherit_from="parent.based_on.probabilities.G12", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_G21 = BoolProperty(default=False, text="Inherit flag for G21", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) G21 = FloatProperty( default=0.8, text="W32/(W32+W33+W34)", math_text=r"$\large\frac{W_{32}}{\sum_{j=2}^{4} W_{3j}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G21", inherit_from="parent.based_on.probabilities.G21", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_G22 = BoolProperty(default=False, text="Inherit flag for G22", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) G22 = FloatProperty( default=0.8, text="W33/(W32+W34)", math_text=r"$\large\frac{W_{33}}{\sum_{j=3}^{4} W_{3j}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G22", inherit_from="parent.based_on.probabilities.G22", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_G31 = BoolProperty(default=False, text="Inherit flag for G31", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) G31 = FloatProperty( default=0.7, text="W42/(W42+W43+W44)", math_text=r"$\large\frac{W_{42}}{\sum_{j=2}^{4} W_{4j}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G31", inherit_from="parent.based_on.probabilities.G31", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_G32 = BoolProperty(default=False, text="Inherit flag for G32", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) G32 = FloatProperty( default=0.5, text="W43/(W43+W44)", math_text=r"$\large\frac{W_{43}}{\sum_{j=3}^{4} W_{4j}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G32", inherit_from="parent.based_on.probabilities.G32", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, W1=0.6, P11_or_P22=0.25, R1=0.5, R2=0.5, G1=0.5, G2=0.4, G11=0.5, G12=0.5, G21=0.8, G22=0.75, G31=0.7, G32=0.5, inherit_W1=False, inherit_P11_or_P22=False, inherit_R1=False, inherit_R2=False, inherit_G1=False, inherit_G2=False, inherit_G11=False, inherit_G12=False, inherit_G21=False, inherit_G22=False, inherit_G31=False, inherit_G32=False, *args, **kwargs): super(R1G4Model, self).__init__(R=1, *args, **kwargs) with self.data_changed.hold(): self.W1 = not_none(W1, 0.6) self.inherit_W1 = inherit_W1 self.P11_or_P22 = not_none(P11_or_P22, 0.25) self.inherit_P11_or_P22 = inherit_P11_or_P22 self.R1 = not_none(R1, 0.5) self.inherit_R1 = inherit_R1 self.R2 = not_none(R2, 0.5) self.inherit_R2 = inherit_R2 self.G1 = not_none(G1, 0.5) self.inherit_G1 = inherit_G1 self.G2 = not_none(G2, 0.4) self.inherit_G2 = inherit_G2 self.G11 = not_none(G11, 0.5) self.inherit_G11 = inherit_G11 self.G12 = not_none(G12, 0.5) self.inherit_G12 = inherit_G12 self.G21 = not_none(G21, 0.8) self.inherit_G21 = inherit_G21 self.G22 = not_none(G22, 0.75) self.inherit_G22 = inherit_G22 self.G31 = not_none(G31, 0.7) self.inherit_G31 = inherit_G31 self.G32 = not_none(G32, 0.5) self.inherit_G32 = inherit_G32 self.update() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def update(self): with self.monitor_changes(): self.mW[0] = self.W1 self.mW[1] = (1.0 - self.mW[0]) * self.R1 self.mW[2] = (1.0 - self.mW[0] - self.mW[1]) * self.R2 self.mW[3] = 1.0 - self.mW[0] - self.mW[1] - self.mW[2] W0inv = 1.0 / self.mW[0] if self.mW[0] > 0.0 else 0.0 if self.mW[0] < 0.5: # P11 is given self.mP[0, 0] = self.P11_or_P22 Wxx = self.mW[0] * (self.mP[0, 0] - 1) + self.mW[1] + self.mW[2] + self.mW[3] else: # P22 is given Wxx = self.P11_or_P22 * (self.mW[1] + self.mW[2] + self.mW[3]) W1x = Wxx * self.G1 # = W11 + W12 + W13 Wyx = (Wxx - W1x) # = W21 + W22 + W23 + W31 + W32 + W33 W2x = Wyx * self.G2 # = W21 + W22 + W23 W3x = Wyx - W2x # = W31 + W32 + W33 self.mW[1, 1] = self.G11 * W1x self.mW[1, 2] = self.G12 * (W1x - self.mW[1, 1]) self.mW[1, 3] = W1x - self.mW[1, 1] - self.mW[1, 2] self.mW[2, 1] = self.G21 * W2x self.mW[2, 2] = self.G22 * (W2x - self.mW[2, 1]) self.mW[2, 3] = W2x - self.mW[2, 1] - self.mW[2, 2] self.mW[3, 1] = self.G31 * W3x self.mW[3, 2] = self.G32 * (W3x - self.mW[3, 1]) self.mW[3, 3] = W3x - self.mW[3, 1] - self.mW[3, 2] for i in range(1, 4): self.mP[i, 0] = 1 for j in range(1, 4): self.mP[i, j] = self.mW[ i, j] / self.mW[i] if self.mW[i] > 0 else 0 self.mP[i, 0] -= self.mP[i, j] self.mW[i, 0] = self.mW[i] * self.mP[i, 0] self.mP[0, 1] = (self.mW[1] - self.mW[1, 1] - self.mW[2, 1] - self.mW[3, 1]) * W0inv self.mP[0, 2] = (self.mW[2] - self.mW[1, 2] - self.mW[2, 2] - self.mW[3, 2]) * W0inv self.mP[0, 3] = (self.mW[3] - self.mW[1, 3] - self.mW[2, 3] - self.mW[3, 3]) * W0inv if self.mW[0] >= 0.5: self.mP[0, 0] = 1 - self.mP[0, 1] - self.mP[0, 2] - self.mP[0, 3] for i in range(4): for j in range(4): self.mW[i, j] = self.mW[i] * self.mP[i, j] self.solve() self.validate() pass # end of class
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
class R2G3Model(_AbstractProbability): r""" (Restricted) probability model for Reichweite 2 with 3 components. The (due to restrictions only) 6 independent variables are: .. math:: :nowrap: \begin{align*} & W_{1} & P_{111} \text{(if $\nicefrac{1}{2} \leq W_1 < \nicefrac{2}{3}$) or} P_{x1x} \text{(if $\nicefrac{2}{3} \leq W_1 \leq 1)$ with $x \in \left\{ {2,3} \right\}$} \\ & G_1 = \frac{W_2}{W_2 + W_3} & G_2 = \frac{W_{212} + W_{213}}{W_{212} + W_{213} + W_{312} + W_{313}} \\ & G_3 = \frac{W_{212}}{W_{212} + W_{213}} & G_4 = \frac{W_{312}}{W_{312} + W_{313}} \\ \end{align*} This model can not describe mixed layers in which the last two components occur right after each other in a stack. In other words there is always an alternation between (one or more) layers of the first component and a single layer of the second or third component. Therefore, the weight fraction of the first component (:math:`W_1`) needs to be > than 1/2. The restriction also translates in the following: .. math:: :nowrap: \begin{align*} & P_{22} = P_{23} = P_{32} = P_{33} = 0 \\ & P_{21} = P_{31} = 1 \\ & \\ & P_{122} = P_{123} = P_{132} = P_{133} = 0 \\ & P_{121} = P_{131} = 1 \\ & \\ & P_{222} = P_{223} = P_{232} = P_{233} = 0 \\ & P_{221} = P_{231} = 1 \\ & \\ & P_{322} = P_{323} = P_{332} = P_{333} = 0 \\ & P_{321} = P_{331} = 1 \\ \end{align*} Using the above, we can calculate a lot of the weight fractions of stacks: .. math:: :nowrap: \begin{align*} & W_{22} = W_{23} = W_{32} = W_{33} 0 \\ & W_{21} = W_{2} \\ & W_{31} = W_{3} \\ & \\ & W_{122} = W_{123} = W_{132} = W_{133} = 0 \\ & W_{121} = W_{12} = W_{21} = W_2 \\ & W_{131} = W_{13} = W_{31} = W_3 \\ & W_{11} = W_1 - W_{12} - W_{13} \\ & \\ & W_{221} = W_{231} = W_{222} = W_{223} = W_{232} = W_{233} = 0 \\ & W_{331} = W_{331} = W_{322} = W_{323} = W_{332} = W_{333} = 0 \\ \end{align*} Then the remaining fractions and probablities can be calculated as follows: .. math:: :nowrap: \begin{align*} & W_2 = G_1 * (1 - W_1) \\ & W_3 = 1 - W_1 - W_2 \\ & \\ & W_x = W_2 + W_3 & & \text{if $W_1 < \nicefrac{2}{3}$:} \\ & \quad \text{$P_{111}$ is given}\\ & \quad P_{x1x} = \begin{dcases} 1 - \frac{W_1 - W_x}{W_x} \cdot (1 - P_{111}, & \text{if $W_x > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & \\ & \text{if $W_1 \geq \nicefrac{2}{3}$:} \\ & \quad \text{$P_{x1x}$ is given}\\ & \quad P_{111} = \begin{dcases} 1 - \frac{W_x}{W_1 - W_x} \cdot (1 - P_{x1x}, & \text{if $(W_1 - W_x) > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & \\ & W_{x1x} = W_x \cdot P_{x1x} \\ & W_{21x} = G_2 \cdot W_{x1x} \\ & W_{31x} = W_{x1x} - W_{21x} \\ & \\ & W_{212} = G_3 \cdot W_{21x} \\ & W_{213} = (1 - G_3) \cdot W_{21x} \\ & W_{211} = W_{21} - W_{212} - W_{213} \\ & \\ & W_{312} = G_4 \cdot W_{31x} \\ & W_{313} = (1 - G_4) \cdot W_{31x} \\ & W_{311} = W_{31} - W_{312} - W_{313} \\ & \\ & W_{111} = W_{11} \cdot P_{111} \\ & W_{112} = W_{12} - W_{212} - W_{312} \\ & W_{112} = W_{13} - W_{213} - W_{313} \\ & \\ & \text{Calculate the remaining P using:} \\ & P_{ijk} = \begin{dcases} \frac{W_{ijk}}{W_{ij}}, & \text{if $W_{ij} > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ \end{align*} """ # MODEL METADATA: class Meta(_AbstractProbability.Meta): store_id = "R2G3Model" # PROPERTIES: _G = 3 twothirds = 2.0 / 3.0 inherit_W1 = BoolProperty(default=False, text="Inherit flag for W1", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) W1 = FloatProperty(default=0.8, text="W1 (> 0.5)", math_text=r"$W_1 (> 0.5)$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.5, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_W1", inherit_from="parent.based_on.probabilities.W1", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_P111_or_P212 = BoolProperty(default=False, text="Inherit flag for P112_or_P211", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) P111_or_P212 = FloatProperty( default=0.9, text="P111 (W1 < 2/3) or\nPx1x (W1 > 2/3)", math_text=r"$P_{111} %s$ or $\newline P_{x1x} %s$" % (mt_range(0.5, "W_1", 2.0 / 3.0), mt_range(2.0 / 3.0, "W_1", 1.0)), persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_P111_or_P212", inherit_from="parent.based_on.probabilities.P111_or_P212", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_G1 = BoolProperty(default=False, text="Inherit flag for G1", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) G1 = FloatProperty(default=0.9, text="W2/(W2+W3)", math_text=r"$\large\frac{W_2}{W_3 + W_2}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G1", inherit_from="parent.based_on.probabilities.G1", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_G2 = BoolProperty(default=False, text="Inherit flag for G2", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) G2 = FloatProperty( default=0.9, text="(W212+W213)/(W212+W213+W312+W313)", math_text= r"$\large\frac{W_{212} + W_{213}}{W_{212} + W_{213} + W_{312} + W_{313}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G2", inherit_from="parent.based_on.probabilities.G2", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_G3 = BoolProperty(default=False, text="Inherit flag for G3", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) G3 = FloatProperty(default=0.9, text="W212/(W212+W213)", math_text=r"$\large\frac{W_{212}}{W_{212} + W_{213}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G3", inherit_from="parent.based_on.probabilities.G3", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) inherit_G4 = BoolProperty(default=False, text="Inherit flag for G4", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, )) G4 = FloatProperty(default=0.9, text="W312/(W312+W313)", math_text=r"$\large\frac{W_{312}}{W_{312} + W_{313}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G4", inherit_from="parent.based_on.probabilities.G4", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin)) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, W1=0.8, P111_or_P212=0.9, G1=0.9, G2=0.9, G3=0.9, G4=0.9, inherit_W1=False, inherit_P111_or_P212=False, inherit_G1=False, inherit_G2=False, inherit_G3=False, inherit_G4=False, *args, **kwargs): super(R2G3Model, self).__init__(R=2, *args, **kwargs) with self.data_changed.hold(): self.W1 = not_none(W1, 0.8) self.inherit_W1 = inherit_W1 self.P111_or_P212 = not_none(P111_or_P212, 0.9) self.inherit_P111_or_P212 = inherit_P111_or_P212 self.G1 = not_none(G1, 0.9) self.inherit_G1 = inherit_G1 self.G2 = not_none(G2, 0.9) self.inherit_G2 = inherit_G2 self.G3 = not_none(G3, 0.9) self.inherit_G3 = inherit_G3 self.G4 = not_none(G4, 0.9) self.inherit_G4 = inherit_G4 self.update() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def update(self): with self.monitor_changes(): # calculate Wx's: self.mW[0] = self.W1 self.mW[1] = (1.0 - self.mW[0]) * self.G1 self.mW[2] = 1.0 - self.mW[0] - self.mW[1] # consequences of restrictions: self.mW[1, 1] = 0 self.mW[1, 2] = 0 self.mW[2, 1] = 0 self.mW[2, 2] = 0 self.mW[0, 1, 0] = self.mW[0, 1] = self.mW[1, 0] = self.mW[1] self.mW[0, 2, 0] = self.mW[0, 2] = self.mW[2, 0] = self.mW[2] self.mW[0, 0] = self.mW[0] - self.mW[0, 1] - self.mW[0, 2] # continue calculations: Wx = self.mW[1] + self.mW[2] if self.mW[0] < self.twothirds: self.mP[0, 0, 0] = self.P111_or_P212 Px0x = 1 - (self.mW[0] - Wx) / Wx * ( 1 - self.mP[0, 0, 0]) if Wx != 0 else 0.0 else: Px0x = self.P111_or_P212 self.mP[0, 0, 0] = 1 - Wx / (self.mW[0] - Wx) * (1 - Px0x) if ( self.mW[0] - Wx) != 0 else 0.0 Wx0x = Wx * Px0x W10x = self.G2 * Wx0x W20x = Wx0x - W10x self.mW[1, 0, 1] = self.G3 * W10x self.mW[1, 0, 2] = (1 - self.G3) * W10x self.mW[1, 0, 0] = self.mW[1, 0] - self.mW[1, 0, 1] - self.mW[1, 0, 2] self.mW[2, 0, 1] = self.G4 * W20x self.mW[2, 0, 2] = (1 - self.G4) * W20x self.mW[2, 0, 0] = self.mW[2, 0] - self.mW[2, 0, 1] - self.mW[2, 0, 2] self.mW[0, 0, 0] = self.mW[0, 0] * self.mP[0, 0, 0] self.mW[0, 0, 1] = self.mW[0, 1] - self.mW[1, 0, 1] - self.mW[2, 0, 1] self.mW[0, 0, 2] = self.mW[0, 2] - self.mW[1, 0, 2] - self.mW[2, 0, 2] # Calculate remaining P: for i in range(3): for j in range(3): for k in range(3): self.mP[i, j, k] = self.mW[i, j, k] / self.mW[ i, j] if self.mW[i, j] > 0 else 0.0 self.solve() self.validate() pass # end of class
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
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
class Marker(DataModel, Storable, CSVMixin): # MODEL INTEL: class Meta(DataModel.Meta): store_id = "Marker" specimen = property(DataModel.parent.fget, DataModel.parent.fset) # PROPERTIES: #: This marker's label label = StringProperty(default="New Marker", text="Label", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin, )) #: Flag indicating whether the color of this marker is inherited inherit_color = BoolProperty(default=settings.MARKER_INHERIT_COLOR, text="Inherit color", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin, )) #: This maker's color: color = ColorProperty(default=settings.MARKER_COLOR, text="Color", persistent=True, visible=True, tabular=True, inheritable=True, signal_name="visuals_changed", inherit_flag="inherit_color", inherit_from="specimen.project.display_marker_color", mix_with=( InheritableMixin, SignalMixin, )) #: Flag indicating whether the angle of this marker is inherited inherit_angle = BoolProperty(default=settings.MARKER_INHERIT_ANGLE, text="Inherit angle", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin, )) #: This maker's angle: angle = FloatProperty(default=settings.MARKER_ANGLE, text="Angle", widget_type="spin", persistent=True, visible=True, tabular=True, inheritable=True, signal_name="visuals_changed", inherit_flag="inherit_angle", inherit_from="specimen.project.display_marker_angle", mix_with=( InheritableMixin, SignalMixin, )) #: Flag indicating whether the top offset of this marker is inherited inherit_top_offset = BoolProperty( default=settings.MARKER_INHERIT_TOP_OFFSET, text="Inherit top offset", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin, )) #: This maker's top offset: top_offset = FloatProperty( default=settings.MARKER_TOP_OFFSET, text="Top offset", widget_type="spin", persistent=True, visible=True, tabular=True, inheritable=True, signal_name="visuals_changed", inherit_flag="inherit_top_offset", inherit_from="specimen.project.display_marker_top_offset", mix_with=( InheritableMixin, SignalMixin, )) #: Whether this marker is visible visible = BoolProperty(default=settings.MARKER_VISIBLE, text="Visible", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin, )) #: The marker's position position = FloatProperty(default=settings.MARKER_POSITION, text="Position", widget_type="spin", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin, )) #: The marker's x offset x_offset = FloatProperty(default=settings.MARKER_X_OFFSET, text="X offset", widget_type="spin", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin, )) #: The marker's y offset y_offset = FloatProperty(default=settings.MARKER_Y_OFFSET, text="Y offset", widget_type="spin", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin, )) #: Flag indicating whether the alignment of this marker is inherited inherit_align = BoolProperty(default=settings.MARKER_INHERIT_ALIGN, text="Inherit align", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin, )) #: This marker's alignment align = StringChoiceProperty( default=settings.MARKER_ALIGN, text="Align", choices=settings.MARKER_ALIGNS, persistent=True, visible=True, tabular=True, inheritable=True, signal_name="visuals_changed", inherit_flag="inherit_align", inherit_from="specimen.project.display_marker_align", mix_with=( InheritableMixin, SignalMixin, )) #: Flag indicating whether the base of this marker is inherited inherit_base = BoolProperty(default=settings.MARKER_INHERIT_BASE, text="Inherit base", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin, )) #: This marker's base base = IntegerChoiceProperty( default=settings.MARKER_BASE, text="Base", choices=settings.MARKER_BASES, persistent=True, visible=True, tabular=True, inheritable=True, signal_name="visuals_changed", inherit_flag="inherit_base", inherit_from="specimen.project.display_marker_base", mix_with=( InheritableMixin, SignalMixin, )) #: Flag indicating whether the top of this marker is inherited inherit_top = BoolProperty(default=settings.MARKER_INHERIT_TOP, text="Inherit top", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin, )) #: This marker's top top = IntegerChoiceProperty( default=settings.MARKER_TOP, text="Top", choices=settings.MARKER_TOPS, persistent=True, visible=True, tabular=True, inheritable=True, signal_name="visuals_changed", inherit_flag="inherit_top", inherit_from="specimen.project.display_marker_top", mix_with=( InheritableMixin, SignalMixin, )) #: Flag indicating whether the line style of this marker is inherited inherit_style = BoolProperty(default=settings.MARKER_INHERIT_STYLE, text="Inherit line style", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin, )) #: This marker's line style style = StringChoiceProperty( default=settings.MARKER_STYLE, text="Line style", choices=settings.MARKER_STYLES, persistent=True, visible=True, tabular=True, inheritable=True, signal_name="visuals_changed", inherit_flag="inherit_style", inherit_from="specimen.project.display_marker_style", mix_with=( InheritableMixin, SignalMixin, )) # ------------------------------------------------------------ # Initialisation and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): my_kwargs = self.pop_kwargs( kwargs, "data_label", "data_visible", "data_position", "data_x_offset", "data_y_offset" "data_color", "data_base", "data_angle", "data_align", *[ prop.label for prop in type(self).Meta.get_local_persistent_properties() ]) super(Marker, self).__init__(*args, **kwargs) kwargs = my_kwargs self.label = self.get_kwarg(kwargs, "", "label", "data_label") self.visible = self.get_kwarg(kwargs, True, "visible", "data_visible") self.position = float( self.get_kwarg(kwargs, 0.0, "position", "data_position")) self.x_offset = float( self.get_kwarg(kwargs, 0.0, "x_offset", "data_x_offset")) self.y_offset = float( self.get_kwarg(kwargs, 0.05, "y_offset", "data_y_offset")) self.top_offset = float(self.get_kwarg(kwargs, 0.0, "top_offset")) self.color = self.get_kwarg(kwargs, settings.MARKER_COLOR, "color", "data_color") self.base = int( self.get_kwarg(kwargs, settings.MARKER_BASE, "base", "data_base")) self.angle = float(self.get_kwarg(kwargs, 0.0, "angle", "data_angle")) self.align = self.get_kwarg(kwargs, settings.MARKER_ALIGN, "align") self.style = self.get_kwarg(kwargs, settings.MARKER_STYLE, "style", "data_align") # if top is not set and style is not "none", # assume top to be "Top of plot", otherwise (style is not "none") # assume top to be relative to the base point (using top_offset) self.top = int( self.get_kwarg(kwargs, 0 if self.style == "none" else 1, "top")) self.inherit_align = self.get_kwarg(kwargs, True, "inherit_align") self.inherit_color = self.get_kwarg(kwargs, True, "inherit_color") self.inherit_base = self.get_kwarg(kwargs, True, "inherit_base") self.inherit_top = self.get_kwarg(kwargs, True, "inherit_top") self.inherit_top_offset = self.get_kwarg(kwargs, True, "inherit_top_offset") self.inherit_angle = self.get_kwarg(kwargs, True, "inherit_angle") self.inherit_style = self.get_kwarg(kwargs, True, "inherit_style") # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def get_nm_position(self): if self.parent is not None: return self.parent.goniometer.get_nm_from_2t(self.position) else: return 0.0 def set_nm_position(self, position): if self.parent is not None: self.position = self.parent.goniometer.get_2t_from_nm(position) else: self.position = 0.0 pass # end of class
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
def R0_model_generator(pasG): class _R0Meta(_AbstractProbability.Meta): store_id = "R0G%dModel" % pasG class _BaseR0Model(object): """ Probability model for Reichweite = 0 (g-1) independent variables: W0 = W0/sum(W0>Wg) W1/sum(W1>Wg) W2/sum(W2>Wg) etc. Pij = Wj ∑W = 1 ∑P = 1 indexes are NOT zero-based in external property names! """ # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): my_kwargs = self.pop_kwargs( kwargs, *[ prop.label for prop in self.Meta.get_local_persistent_properties() ]) super(_BaseR0Model, self).__init__(R=0, *args, **kwargs) with self.data_changed.hold(): if self.G > 1 and "W1" in my_kwargs: # old-style model for i in range(self.G - 1): name = "W%d" % (i + 1) self.mW[i] = not_none(my_kwargs.get(name, None), 0.8) name = "F%d" % (i + 1) setattr( self, name, self.mW[i] / (np.sum(np.diag(self._W)[i:]) or 1.0)) else: for i in range(self.G - 1): name = "inherit_F%d" % (i + 1) setattr(self, name, my_kwargs.get(name, False)) name = "F%d" % (i + 1) setattr(self, name, not_none(my_kwargs.get(name, None), 0.8)) self.update() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def update(self): with self.monitor_changes(): if self.G > 1: for i in range(self.G - 1): name = "F%d" % (i + 1) if i > 0: self.mW[i] = getattr(self, name) * ( 1.0 - np.sum(np.diag(self._W)[0:i])) else: self.mW[i] = getattr(self, name) self.mW[self.G - 1] = 1.0 - np.sum(np.diag(self._W)[:-1]) else: self.mW[0] = 1.0 self._P[:] = np.repeat( np.diag(self._W)[np.newaxis, :], self.G, 0) self.solve() self.validate() pass # end of class _dict = dict() _dict["Meta"] = _R0Meta def set_attribute(name, value): # @NoSelf """Sets an attribute on the class and the dict""" _dict[name] = value setattr(_BaseR0Model, name, value) set_attribute("G", pasG) # PROPERTIES: for g in range(pasG - 1): label = "F%d" % (g + 1) text = "W%(g)d/Sum(W%(g)d+...+W%(G)d)" % {'g': g + 1, 'G': pasG} math_text = r"$\large\frac{W_{%(g)d}}{\sum_{i=%(g)d}^{%(G)d} W_i}$" % { 'g': g + 1, 'G': pasG } inh_flag = "inherit_F%d" % (g + 1) inh_from = "parent.based_on.probabilities.F%d" % (g + 1) set_attribute( label, FloatProperty(default=0.8, text=text, math_text=math_text, refinable=True, persistent=True, visible=True, minimum=0.0, maximum=1.0, inheritable=True, inherit_flag=inh_flag, inherit_from=inh_from, is_independent=True, store_private=True, set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin))) label = "inherit_F%d" % (g + 1) text = "Inherit flag for F%d" % (g + 1) set_attribute( label, BoolProperty(default=False, text=text, refinable=False, persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin, ))) # CREATE TYPE AND REGISTER AS STORABLE: cls = type("R0G%dModel" % pasG, (_BaseR0Model, _AbstractProbability), _dict) storables.register_decorator(cls) return cls