예제 #1
0
 def __init__(self, project=None):
     """ Initializes the AppModel with the given Project. """
     super(AppModel, self).__init__()
     self.needs_plot_update = Signal()
     self.current_project = project
     if project: project.parent = self
     self._statistics_visible = False
예제 #2
0
    def __init__(self, parent=None, store=False, options={}):
        super(RefineContext, self).__init__(parent=parent)
        self.options = options

        logger.info("Creating RefineContext with the following refinables:")

        if parent is not None:
            self.ref_props = []
            self.values = []
            self.ranges = ()
            for node in parent.refinables.iter_children():
                ref_prop = node.object
                if ref_prop.refine and ref_prop.refinable:
                    logger.info(" - %s from %r" % (ref_prop.text_title, ref_prop.obj))
                    self.ref_props.append(ref_prop)
                    self.values.append(ref_prop.value)
                    if not (ref_prop.value_min < ref_prop.value_max):
                        logger.info("Error in refinement setup!")
                        raise RefineSetupError, "Refinable property `%s` needs a valid range!" % (ref_prop.get_descriptor(),)
                    self.ranges += ((ref_prop.value_min, ref_prop.value_max),)

        if len(self.ref_props) == 0:
            logger.error("  No refinables selected!")
            raise RefineSetupError, "RefineContext needs at least one refinable property!"

        if store: self.store_project_state()

        self.initial_residual = self.mixture.optimizer.get_current_residual()
        self.initial_solution = np.array(self.values, dtype=float)

        self.best_solution = self.initial_solution
        self.best_residual = self.initial_residual

        self.last_solution = self.initial_solution
        self.last_residual = self.initial_residual

        self.solution_added = Signal()

        self.status = "created"
예제 #3
0
    def __init__(self, *args, **kwargs):
        """
            Constructor takes any of its properties as a keyword argument.
            It also support two UUID list keyword arguments:
                - phase_uuids: a list of UUID's for the phases in the mixture
                - specimen_uuids: a list of UUID's for the specimens in the mixture
            These should be *instead* of the phases and specimens keywords.
            
            In addition to the above, the constructor still supports the 
            following deprecated keywords, mapping to a current keyword:
                - phase_indeces: a list of project indices for the phases in the mixture
                - specimen_indeces: a list of project indices for the specimens in the mixture
                
            Any other arguments or keywords are passed to the base class.
        """

        my_kwargs = self.pop_kwargs(kwargs,
            "data_name", "phase_uuids", "phase_indeces", "specimen_uuids",
            "specimen_indeces", "data_phases", "data_scales", "data_bgshifts",
            "data_fractions", "data_refine_method", "fractions",
            "bgshifts", "scales", "phases",
            *[names[0] for names in type(self).Meta.get_local_storable_properties()]
        )
        super(Mixture, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        with self.data_changed.hold():

            self._data_object = MixtureData()

            self.needs_reset = Signal()
            self.needs_update = HoldableSignal()
            self.name = self.get_kwarg(kwargs, "New Mixture", "name", "data_name")
            self.auto_run = self.get_kwarg(kwargs, False, "auto_run")
            self.auto_bg = self.get_kwarg(kwargs, True, "auto_bg")

            # 2D matrix, rows match specimens, columns match mixture 'phases'; contains the actual phase objects
            phase_uuids = self.get_kwarg(kwargs, None, "phase_uuids")
            phase_indeces = self.get_kwarg(kwargs, None, "phase_indeces")
            if phase_uuids is not None:
                self.phase_matrix = np.array([[type(type(self)).object_pool.get_object(uuid) if uuid else None for uuid in row] for row in phase_uuids], dtype=np.object_)
            elif phase_indeces and self.parent is not None:
                warn("The use of object indices is deprecated since version 0.4. Please switch to using object UUIDs.", DeprecationWarning)
                self.phase_matrix = np.array([[self.parent.phases.get_user_data_from_index(index) if index != -1 else None for index in row] for row in phase_indeces], dtype=np.object_)
            else:
                self.phase_matrix = np.empty(shape=(0, 0), dtype=np.object_)

            # list with actual specimens, indexes match with rows in phase_matrix
            specimen_uuids = self.get_kwarg(kwargs, None, "specimen_uuids")
            specimen_indeces = self.get_kwarg(kwargs, None, "specimen_indeces")
            if specimen_uuids:
                self.specimens = [type(type(self)).object_pool.get_object(uuid) if uuid else None for uuid in specimen_uuids]
            elif specimen_indeces and self.parent is not None:
                warn("The use of object indices is deprecated since version 0.4. Please switch to using object UUIDs.", DeprecationWarning)
                self.specimens = [self.parent.specimens.get_user_data_from_index(index) if index != -1 else None for index in specimen_indeces]
            else:
                self.specimens = list()

            # list with mixture phase names, indexes match with cols in phase_matrix
            self.phases = self.get_kwarg(kwargs, list(), "phases", "data_phases")

            # list with scale values, indexes match with rows in phase_matrix (= specimens)
            self.scales = np.asarray(self.get_kwarg(kwargs, [1.0] * len(self.specimens), "scales", "data_scales"))
            # list with specimen background shift values, indexes match with rows in phase_matrix (=specimens)
            self.bgshifts = np.asarray(self.get_kwarg(kwargs, [0.0] * len(self.specimens), "bgshifts", "data_bgshifts"))
            # list with phase fractions, indexes match with cols in phase_matrix (=phases)
            self.fractions = np.asarray(self.get_kwarg(kwargs, [0.0] * len(self.phases), "fractions", "data_fractions"))

            # sanity check:
            n, m = self.phase_matrix.shape if self.phase_matrix.ndim == 2 else (0, 0)
            if len(self.scales) != n or len(self.specimens) != n or len(self.bgshifts) != n:
                raise IndexError, "Shape mismatch: scales (%d), background shifts (%d) or specimens (%d) list lengths do not match with row count (%d) of phase matrix" % (len(self.scales), len(self.specimens), len(self.bgshifts), n)
            if len(self.phases) != m or len(self.fractions) != m:
                raise IndexError, "Shape mismatch: fractions or phases lists do not match with column count of phase matrix"

            self._observe_specimens()
            self._observe_phases()

            self.optimizer = Optimizer(parent=self)
            self.refiner = Refiner(
                refine_method=self.get_kwarg(kwargs, 0, "refine_method", "data_refine_method"),
                refine_options=self.get_kwarg(kwargs, dict(), "refine_options"),
                parent=self)

            self.update()

            self.observe_model(self)

            pass # end hold data_changed
예제 #4
0
 def after():
     Signal.emit(self)
     if callable(self.after): self.after()
예제 #5
0
 def __init__(self, before=None, after=None):
     Signal.__init__(self)
     self.before = before
     self.after = after
     return
예제 #6
0
class Mixture(DataModel, Storable):
    """
        The base model for optimization and refinement of calculated data
        and experimental data. This is the main model you want to interact with,
        lower-level classes' functionality (mainly 
        :class:`~pyxrd.mixture.models.optimizers.Optimizer` and 
        :class:`~pyxrd.mixture.models.refiner.Refinement`) are integrated into this
        class. 
        
        The Mixture is responsible for managing the phases and specimens lists
        and combination-matrix and for delegating any optimization, calculation
        or refinement calls to the appropriate helper class.
    """

    # MODEL INTEL:
    class Meta(DataModel.Meta):
        properties = [  # TODO add labels
            PropIntel(name="name",
                      label="Name",
                      data_type=unicode,
                      storable=True,
                      has_widget=True,
                      is_column=True),
            PropIntel(name="refinables",
                      label="",
                      data_type=object,
                      widget_type="object_tree_view",
                      class_type=RefinableWrapper),
            PropIntel(name="refine_options",
                      label="",
                      data_type=dict,
                      storable=True,
                      stor_name="all_refine_options"),
            PropIntel(name="refine_method_index",
                      label="",
                      data_type=int,
                      storable=True),
            PropIntel(name="auto_run",
                      label="",
                      data_type=bool,
                      is_column=True,
                      storable=True,
                      has_widget=True),
            PropIntel(name="auto_bg",
                      label="",
                      data_type=bool,
                      is_column=True,
                      storable=True,
                      has_widget=True),
            PropIntel(name="needs_update",
                      label="",
                      data_type=object,
                      storable=False
                      ),  # Signal used to indicate the mixture needs an update
            PropIntel(
                name="needs_reset",
                label="",
                data_type=object,
                storable=False,
            ),  # Signal used to indicate the mixture matrix needs to be re-built...
        ]
        store_id = "Mixture"

    _data_object = None

    @property
    def data_object(self):
        self._data_object.specimens = [None] * len(self.specimens)
        self._data_object.auto_bg = self.auto_bg
        for i, specimen in enumerate(self.specimens):
            if specimen is not None:
                data_object = specimen.data_object
                data_object.phases = [None] * len(self.phases)
                for j, phase in enumerate(self.phase_matrix[i, ...].flatten()):
                    data_object.phases[
                        j] = phase.data_object if phase is not None else None
                self._data_object.specimens[i] = data_object
            else:
                self._data_object.specimens[i] = None
        return self._data_object

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

    # SIGNALS:
    #: Signal, emitted when the # of phases or specimens changes
    needs_reset = None
    #: Signal, emitted when the patterns of the specimens need an update
    needs_update = None

    # INTERNALS:
    _name = ""

    @property
    def name(self):
        """ The name of this mixture """
        return self._name

    @name.setter
    def name(self, value):
        self._name = value
        self.visuals_changed.emit()

    #: Flag, True if the mixture will automatically adjust phase fractions and scales
    auto_run = False
    #: Flag, True if the mixture is allowed to also update the background level
    auto_bg = True

    #: The tree of refinable properties
    @property
    def refinables(self):
        return self.refinement.refinables

    @property
    def refine_method_index(self):
        """ An integer describing which method to use for the refinement (see 
        mixture.models.methods.get_all_refine_methods) """
        return self.refinement.refine_method_index

    #: A dict containing the current refinement options
    @property
    def refine_options(self):
        return self.refinement.refine_options

    #: A dict containing all refinement options
    @property
    def all_refine_options(self):
        return self.refinement.all_refine_options

    # Lists and matrices:
    #: A 2D numpy object array containing the combination matrix
    phase_matrix = None
    #: The list of specimen objects
    specimens = None
    #: The list of phase names
    phases = None

    @property
    def scales(self):
        """ A list of floats containing the absolute scales for the calculated patterns """
        return self._data_object.scales

    @scales.setter
    def scales(self, value):
        self._data_object.scales = value

    @property
    def bgshifts(self):
        """ A list of background shifts for the calculated patterns """
        return self._data_object.bgshifts

    @bgshifts.setter
    def bgshifts(self, value):
        self._data_object.bgshifts = value

    @property
    def fractions(self):
        """ A list of phase fractions for this mixture """
        return self._data_object.fractions

    @fractions.setter
    def fractions(self, value):
        self._data_object.fractions = value

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        """
            Constructor takes any of its properties as a keyword argument.
            It also support two UUID list keyword arguments:
                - phase_uuids: a list of UUID's for the phases in the mixture
                - specimen_uuids: a list of UUID's for the specimens in the mixture
            These should be *instead* of the phases and specimens keywords.
            
            In addition to the above, the constructor still supports the 
            following deprecated keywords, mapping to a current keyword:
                - phase_indeces: a list of project indices for the phases in the mixture
                - specimen_indeces: a list of project indices for the specimens in the mixture
                
            Any other arguments or keywords are passed to the base class.
        """

        my_kwargs = self.pop_kwargs(
            kwargs, "data_name", "phase_uuids", "phase_indeces",
            "specimen_uuids", "specimen_indeces", "data_phases", "data_scales",
            "data_bgshifts", "data_fractions", "refine_method",
            "data_refine_method", "fractions", "bgshifts", "scales", "phases",
            *[
                names[0]
                for names in type(self).Meta.get_local_storable_properties()
            ])
        super(Mixture, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        with self.data_changed.hold():

            self._data_object = MixtureData()

            self.needs_reset = Signal()
            self.needs_update = HoldableSignal()
            self.name = self.get_kwarg(kwargs, "New Mixture", "name",
                                       "data_name")
            self.auto_run = self.get_kwarg(kwargs, False, "auto_run")
            self.auto_bg = self.get_kwarg(kwargs, True, "auto_bg")

            # 2D matrix, rows match specimens, columns match mixture 'phases'; contains the actual phase objects
            phase_uuids = self.get_kwarg(kwargs, None, "phase_uuids")
            phase_indeces = self.get_kwarg(kwargs, None, "phase_indeces")
            if phase_uuids is not None:
                self.phase_matrix = np.array([[
                    type(type(self)).object_pool.get_object(uuid)
                    if uuid else None for uuid in row
                ] for row in phase_uuids],
                                             dtype=np.object_)
            elif phase_indeces and self.parent is not None:
                warn(
                    "The use of object indices is deprecated since version 0.4. Please switch to using object UUIDs.",
                    DeprecationWarning)
                self.phase_matrix = np.array([[
                    self.parent.phases.get_user_data_from_index(index)
                    if index != -1 else None for index in row
                ] for row in phase_indeces],
                                             dtype=np.object_)
            else:
                self.phase_matrix = np.empty(shape=(0, 0), dtype=np.object_)

            # list with actual specimens, indexes match with rows in phase_matrix
            specimen_uuids = self.get_kwarg(kwargs, None, "specimen_uuids")
            specimen_indeces = self.get_kwarg(kwargs, None, "specimen_indeces")
            if specimen_uuids:
                self.specimens = [
                    type(type(self)).object_pool.get_object(uuid)
                    if uuid else None for uuid in specimen_uuids
                ]
            elif specimen_indeces and self.parent is not None:
                warn(
                    "The use of object indices is deprecated since version 0.4. Please switch to using object UUIDs.",
                    DeprecationWarning)
                self.specimens = [
                    self.parent.specimens.get_user_data_from_index(index)
                    if index != -1 else None for index in specimen_indeces
                ]
            else:
                self.specimens = list()

            # list with mixture phase names, indexes match with cols in phase_matrix
            self.phases = self.get_kwarg(kwargs, list(), "phases",
                                         "data_phases")

            # list with scale values, indexes match with rows in phase_matrix (= specimens)
            self.scales = np.asarray(
                self.get_kwarg(kwargs, [1.0] * len(self.specimens), "scales",
                               "data_scales"))
            # list with specimen background shift values, indexes match with rows in phase_matrix (=specimens)
            self.bgshifts = np.asarray(
                self.get_kwarg(kwargs, [0.0] * len(self.specimens), "bgshifts",
                               "data_bgshifts"))
            # list with phase fractions, indexes match with cols in phase_matrix (=phases)
            self.fractions = np.asarray(
                self.get_kwarg(kwargs, [0.0] * len(self.phases), "fractions",
                               "data_fractions"))

            # sanity check:
            n, m = self.phase_matrix.shape if self.phase_matrix.ndim == 2 else (
                0, 0)
            if len(self.scales) != n or len(self.specimens) != n or len(
                    self.bgshifts) != n:
                raise IndexError, "Shape mismatch: scales (%d), background shifts (%d) or specimens (%d) list lengths do not match with row count (%d) of phase matrix" % (
                    len(self.scales), len(self.specimens), len(
                        self.bgshifts), n)
            if len(self.phases) != m or len(self.fractions) != m:
                raise IndexError, "Shape mismatch: fractions or phases lists do not match with column count of phase matrix"

            self._observe_specimens()
            self._observe_phases()

            self.optimizer = Optimizer(parent=self)
            self.refinement = Refinement(
                refine_method_index=self.get_kwarg(kwargs, 0,
                                                   "refine_method_index",
                                                   "refine_method",
                                                   "data_refine_method"),
                refine_options=self.get_kwarg(kwargs, dict(),
                                              "refine_options"),
                parent=self)

            self.update()

            self.observe_model(self)

            pass  # end hold data_changed

    # ------------------------------------------------------------
    #      Notifications of observable properties
    # ------------------------------------------------------------
    @DataModel.observe("removed", signal=True)
    def notify_removed(self, model, prop_name, info):
        if model == self:
            self._relieve_phases()
            self._relieve_specimens()

    @DataModel.observe("needs_update", signal=True)
    def notify_needs_update(self, model, prop_name, info):
        with self.data_changed.hold():
            self.update()

    @DataModel.observe("data_changed", signal=True)
    def notify_data_changed(self, model, prop_name, info):
        if not model == self and not (info.arg == "based_on"
                                      and model.based_on is not None
                                      and model.based_on in self.phase_matrix):
            self.needs_update.emit()

    @DataModel.observe("visuals_changed", signal=True)
    def notify_visuals_changed(self, model, prop_name, info):
        if isinstance(model, Phase) and \
           not (info.arg == "based_on" and model.based_on is not None and model.based_on in self.phase_matrix):
            for i, specimen in enumerate(self.specimens):
                if specimen is not None:
                    specimen.update_visuals(self.phase_matrix[i, :])

    # ------------------------------------------------------------
    #      Input/Output stuff
    # ------------------------------------------------------------
    def json_properties(self):
        self.refinement.update_refinement_treestore()
        retval = Storable.json_properties(self)

        retval["phase_uuids"] = [[item.uuid if item else "" for item in row]
                                 for row in map(list, self.phase_matrix)]
        retval["specimen_uuids"] = [
            specimen.uuid if specimen else "" for specimen in self.specimens
        ]
        retval["phases"] = self.phases
        retval["fractions"] = self.fractions.tolist()
        retval["bgshifts"] = self.bgshifts.tolist()
        retval["scales"] = self.scales.tolist()

        return retval

    @staticmethod
    def from_json(**kwargs):
        # Remove this deprecated kwarg:
        if "refinables" in kwargs:
            del kwargs["refinables"]
        return Mixture(**kwargs)

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def unset_phase(self, phase):
        """ Clears a phase slot in the phase matrix """
        with self.needs_update.hold_and_emit():
            with self.data_changed.hold():
                shape = self.phase_matrix.shape
                with self._relieve_and_observe_phases():
                    for i in range(shape[0]):
                        for j in range(shape[1]):
                            if self.phase_matrix[i, j] == phase:
                                self.phase_matrix[i, j] = None
                self.refinement.update_refinement_treestore()

    def unset_specimen(self, specimen):
        """ Clears a specimen slot in the specimen list """
        with self.needs_update.hold_and_emit():
            with self.data_changed.hold():
                with self._relieve_and_observe_specimens():
                    for i, spec in enumerate(self.specimens):
                        if spec == specimen:
                            self.specimens[i] = None

    def get_phase(self, specimen_slot, phase_slot):
        """Returns the phase at the given slot positions or None if not set"""
        return self.phase_matrix[specimen_slot, phase_slot]

    def set_phase(self, specimen_slot, phase_slot, phase):
        """Sets the phase at the given slot positions"""
        if self.parent is not None:  #no parent = no valid phases
            with self.needs_update.hold_and_emit():
                with self.data_changed.hold():
                    with self._relieve_and_observe_phases():
                        if phase is not None and not phase in self.parent.phases:
                            raise RuntimeError, "Cannot add a phase to a Mixture which is not inside the project!"
                        self.phase_matrix[specimen_slot, phase_slot] = phase
                    self.refinement.update_refinement_treestore()

    def get_specimen(self, specimen_slot):
        """Returns the specimen at the given slot position or None if not set"""
        return self.specimens[specimen_slot]

    def set_specimen(self, specimen_slot, specimen):
        """Sets the specimen at the given slot position"""
        if self.parent is not None:  #no parent = no valid specimens
            with self.needs_update.hold_and_emit():
                with self.data_changed.hold():
                    with self._relieve_and_observe_specimens():
                        if specimen is not None and not specimen in self.parent.specimens:
                            raise RuntimeError, "Cannot add a specimen to a Mixture which is not inside the project!"
                        self.specimens[specimen_slot] = specimen

    @contextmanager
    def _relieve_and_observe_specimens(self):
        self._relieve_specimens()
        yield
        self._observe_specimens()

    def _observe_specimens(self):
        """ Starts observing specimens in the specimens list"""
        for specimen in self.specimens:
            if specimen is not None:
                self.observe_model(specimen)

    def _relieve_specimens(self):
        """ Relieves specimens observer calls """
        for specimen in self.specimens:
            if specimen is not None:
                self.relieve_model(specimen)

    @contextmanager
    def _relieve_and_observe_phases(self):
        self._relieve_phases()
        yield
        self._observe_phases()

    def _observe_phases(self):
        """ Starts observing phases in the phase matrix"""
        for phase in self.phase_matrix.flat:
            if phase is not None:
                self.observe_model(phase)

    def _relieve_phases(self):
        """ Relieves phase observer calls """
        for phase in self.phase_matrix.flat:
            if phase is not None:
                self.relieve_model(phase)

    def add_phase_slot(self, phase_name, fraction):
        """ Adds a new phase column to the phase matrix """
        with self.needs_update.hold_and_emit():
            with self.data_changed.hold():
                self.phases.append(phase_name)
                self.fractions = np.append(self.fractions, fraction)
                n, m = self.phase_matrix.shape if self.phase_matrix.ndim == 2 else (
                    0, 0)
                if self.phase_matrix.size == 0:
                    self.phase_matrix = np.resize(self.phase_matrix.copy(),
                                                  (n, m + 1))
                    self.phase_matrix[:] = None
                else:
                    self.phase_matrix = np.concatenate(
                        [self.phase_matrix.copy(), [[None]] * n], axis=1)
                    self.phase_matrix[:, m] = None
                self.refinement.update_refinement_treestore()
        return m

    def del_phase_slot(self, phase_slot):
        """ Deletes a phase column using its index """
        with self.needs_update.hold_and_emit():
            with self.data_changed.hold():
                with self._relieve_and_observe_phases():
                    # Remove the corresponding phase name, fraction & references:
                    del self.phases[phase_slot]
                    self.fractions = np.delete(self.fractions, phase_slot)
                    self.phase_matrix = np.delete(self.phase_matrix,
                                                  phase_slot,
                                                  axis=1)
                # Update our refinement tree store to reflect current state
                self.refinement.update_refinement_treestore()
        # Inform any interested party they need to update their representation
        self.needs_reset.emit()

    def del_phase_slot_by_name(self, phase_name):
        """ Deletes a phase slot using its name """
        self.del_phase_slot(self.phases.index(phase_name))

    def add_specimen_slot(self, specimen, scale, bgs):
        """ Adds a new specimen to the phase matrix (a row) and specimen list """
        with self.needs_update.hold_and_emit():
            with self.data_changed.hold():
                self.specimens.append(None)
                self.scales = np.append(self.scales, scale)
                self.bgshifts = np.append(self.bgshifts, bgs)
                n, m = self.phase_matrix.shape if self.phase_matrix.ndim == 2 else (
                    0, 0)
                if self.phase_matrix.size == 0:
                    self.phase_matrix = np.resize(self.phase_matrix.copy(),
                                                  (n + 1, m))
                    self.phase_matrix[:] = None
                else:
                    self.phase_matrix = np.concatenate(
                        [self.phase_matrix.copy(), [[None] * m]], axis=0)
                    self.phase_matrix[n, :] = None
                if specimen is not None:
                    self.set_specimen(n, specimen)
        return n

    def del_specimen_slot(self, specimen_slot):
        """ Deletes a specimen slot using its slot index """
        with self.needs_update.hold_and_emit():
            with self.data_changed.hold():
                # Remove the corresponding specimen name, scale, bg-shift & phases:
                with self._relieve_and_observe_specimens():
                    del self.specimens[specimen_slot]
                    self.scales = np.delete(self.scales, specimen_slot)
                    self.bgshifts = np.delete(self.bgshifts, specimen_slot)
                    self.phase_matrix = np.delete(self.phase_matrix,
                                                  specimen_slot,
                                                  axis=0)
                # Update our refinement tree store to reflect current state
                self.refinement.update_refinement_treestore()
        # Inform any interested party they need to update their representation
        self.needs_reset.emit()

    def del_specimen_slot_by_object(self, specimen):
        """ Deletes a specimen slot using the actual object """
        try:
            self.del_specimen_slot(self.specimens.index(specimen))
        except ValueError:
            logger.exception(
                "Caught a ValueError when deleting a specimen from  mixture '%s'"
                % self.name)

    # ------------------------------------------------------------
    #      Refinement stuff:
    # ------------------------------------------------------------
    def set_data_object(self, mixture, calculate=False):
        """
            Sets the fractions, scales and bgshifts of this mixture.
        """
        if mixture is not None:
            with self.needs_update.ignore():
                with self.data_changed.hold_and_emit():
                    self.fractions[:] = list(mixture.fractions)
                    self.scales[:] = list(mixture.scales)
                    self.bgshifts[:] = list(mixture.bgshifts)

                    if calculate:  # (re-)calculate if requested:
                        mixture = self.optimizer.calculate(mixture)

                    for i, (specimen_data, specimen) in enumerate(
                            izip(mixture.specimens, self.specimens)):
                        if specimen is not None:
                            with specimen.data_changed.ignore():
                                specimen.update_pattern(
                                    specimen_data.total_intensity,
                                    specimen_data.phase_intensities *
                                    self.fractions[:, np.newaxis] *
                                    self.scales[i], self.phase_matrix[i, :])

    def optimize(self):
        """
            Optimize the current solution (fractions, scales, bg shifts & calculate
            phase intensities)
        """
        with self.needs_update.ignore():
            with self.data_changed.hold():
                # no need to re-calculate, is already done by the optimization
                self.set_data_object(self.optimizer.optimize())

    def apply_current_data_object(self):
        """
            Recalculates the intensities using the current fractions, scales
            and bg shifts without optimization
        """
        with self.needs_update.ignore():
            with self.data_changed.hold():
                self.set_data_object(self.data_object, calculate=True)

    # @print_timing
    def update(self):
        """
            Optimizes or re-applies the current mixture 'solution'.
            Effectively re-calculates the entire patterns.
        """
        with self.needs_update.ignore():
            with self.data_changed.hold():
                if self.auto_run:
                    self.optimize()
                else:
                    self.apply_current_data_object()

    # ------------------------------------------------------------
    #      Various other things:
    # ------------------------------------------------------------
    def get_composition_matrix(self):
        """
            Returns a matrix containing the oxide composition for each specimen 
            in this mixture. It uses the COMPOSITION_CONV file for this purpose
            to convert element weights into their oxide weight equivalent.
        """

        # create an atom nr -> (atom name, conversion) mapping
        # this is used to transform most of the elements into their oxides
        atom_conv = OrderedDict()
        with open(settings.DATA_REG.get_file_path("COMPOSITION_CONV"),
                  'r') as f:
            reader = csv.reader(f)
            reader.next()  # skip header
            for row in reader:
                nr, name, fact = row
                atom_conv[int(nr)] = (name, float(fact))

        comps = list()
        for i, row in enumerate(self.phase_matrix):
            comp = dict()
            for j, phase in enumerate(row):
                phase_fract = self.fractions[j]
                for k, component in enumerate(phase.components):
                    comp_fract = phase.probabilities.mW[k] * phase_fract
                    for atom in chain(component.layer_atoms,
                                      component.interlayer_atoms):
                        nr = atom.atom_type.atom_nr
                        if nr in atom_conv:
                            wt = atom.pn * atom.atom_type.weight * comp_fract * atom_conv[
                                nr][1]
                            comp[nr] = comp.get(nr, 0.0) + wt
            comps.append(comp)

        final_comps = np.zeros(shape=(len(atom_conv) + 1, len(comps) + 1),
                               dtype='a15')
        final_comps[0, 0] = " " * 8
        for j, comp in enumerate(comps):
            fact = 100.0 / sum(comp.values())
            for i, (nr, (oxide_name,
                         conv)) in enumerate(atom_conv.iteritems()):
                wt = comp.get(nr, 0.0) * fact
                # set relevant cells:
                if i == 0:
                    final_comps[i,
                                j + 1] = self.specimens[j].name.ljust(15)[:15]
                if j == 0:
                    final_comps[i + 1, j] = ("%s  " % oxide_name).rjust(8)[:8]
                final_comps[i + 1, j + 1] = ("%.1f" % wt).ljust(15)[:15]

        return final_comps

    pass  # end of class
예제 #7
0
    def __init__(self, *args, **kwargs):
        """
            Constructor takes any of its properties as a keyword argument.
            It also support two UUID list keyword arguments:
                - phase_uuids: a list of UUID's for the phases in the mixture
                - specimen_uuids: a list of UUID's for the specimens in the mixture
            These should be *instead* of the phases and specimens keywords.
            
            In addition to the above, the constructor still supports the 
            following deprecated keywords, mapping to a current keyword:
                - phase_indeces: a list of project indices for the phases in the mixture
                - specimen_indeces: a list of project indices for the specimens in the mixture
                
            Any other arguments or keywords are passed to the base class.
        """

        my_kwargs = self.pop_kwargs(
            kwargs, "data_name", "phase_uuids", "phase_indeces",
            "specimen_uuids", "specimen_indeces", "data_phases", "data_scales",
            "data_bgshifts", "data_fractions", "refine_method",
            "data_refine_method", "fractions", "bgshifts", "scales", "phases",
            *[
                names[0]
                for names in type(self).Meta.get_local_storable_properties()
            ])
        super(Mixture, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        with self.data_changed.hold():

            self._data_object = MixtureData()

            self.needs_reset = Signal()
            self.needs_update = HoldableSignal()
            self.name = self.get_kwarg(kwargs, "New Mixture", "name",
                                       "data_name")
            self.auto_run = self.get_kwarg(kwargs, False, "auto_run")
            self.auto_bg = self.get_kwarg(kwargs, True, "auto_bg")

            # 2D matrix, rows match specimens, columns match mixture 'phases'; contains the actual phase objects
            phase_uuids = self.get_kwarg(kwargs, None, "phase_uuids")
            phase_indeces = self.get_kwarg(kwargs, None, "phase_indeces")
            if phase_uuids is not None:
                self.phase_matrix = np.array([[
                    type(type(self)).object_pool.get_object(uuid)
                    if uuid else None for uuid in row
                ] for row in phase_uuids],
                                             dtype=np.object_)
            elif phase_indeces and self.parent is not None:
                warn(
                    "The use of object indices is deprecated since version 0.4. Please switch to using object UUIDs.",
                    DeprecationWarning)
                self.phase_matrix = np.array([[
                    self.parent.phases.get_user_data_from_index(index)
                    if index != -1 else None for index in row
                ] for row in phase_indeces],
                                             dtype=np.object_)
            else:
                self.phase_matrix = np.empty(shape=(0, 0), dtype=np.object_)

            # list with actual specimens, indexes match with rows in phase_matrix
            specimen_uuids = self.get_kwarg(kwargs, None, "specimen_uuids")
            specimen_indeces = self.get_kwarg(kwargs, None, "specimen_indeces")
            if specimen_uuids:
                self.specimens = [
                    type(type(self)).object_pool.get_object(uuid)
                    if uuid else None for uuid in specimen_uuids
                ]
            elif specimen_indeces and self.parent is not None:
                warn(
                    "The use of object indices is deprecated since version 0.4. Please switch to using object UUIDs.",
                    DeprecationWarning)
                self.specimens = [
                    self.parent.specimens.get_user_data_from_index(index)
                    if index != -1 else None for index in specimen_indeces
                ]
            else:
                self.specimens = list()

            # list with mixture phase names, indexes match with cols in phase_matrix
            self.phases = self.get_kwarg(kwargs, list(), "phases",
                                         "data_phases")

            # list with scale values, indexes match with rows in phase_matrix (= specimens)
            self.scales = np.asarray(
                self.get_kwarg(kwargs, [1.0] * len(self.specimens), "scales",
                               "data_scales"))
            # list with specimen background shift values, indexes match with rows in phase_matrix (=specimens)
            self.bgshifts = np.asarray(
                self.get_kwarg(kwargs, [0.0] * len(self.specimens), "bgshifts",
                               "data_bgshifts"))
            # list with phase fractions, indexes match with cols in phase_matrix (=phases)
            self.fractions = np.asarray(
                self.get_kwarg(kwargs, [0.0] * len(self.phases), "fractions",
                               "data_fractions"))

            # sanity check:
            n, m = self.phase_matrix.shape if self.phase_matrix.ndim == 2 else (
                0, 0)
            if len(self.scales) != n or len(self.specimens) != n or len(
                    self.bgshifts) != n:
                raise IndexError, "Shape mismatch: scales (%d), background shifts (%d) or specimens (%d) list lengths do not match with row count (%d) of phase matrix" % (
                    len(self.scales), len(self.specimens), len(
                        self.bgshifts), n)
            if len(self.phases) != m or len(self.fractions) != m:
                raise IndexError, "Shape mismatch: fractions or phases lists do not match with column count of phase matrix"

            self._observe_specimens()
            self._observe_phases()

            self.optimizer = Optimizer(parent=self)
            self.refinement = Refinement(
                refine_method_index=self.get_kwarg(kwargs, 0,
                                                   "refine_method_index",
                                                   "refine_method",
                                                   "data_refine_method"),
                refine_options=self.get_kwarg(kwargs, dict(),
                                              "refine_options"),
                parent=self)

            self.update()

            self.observe_model(self)

            pass  # end hold data_changed
예제 #8
0
class AppModel(PyXRDModel):
    """
        Simple model that stores the state of the application window.
        Should never be made persistent.
        
        Attributes:
            needs_plot_update: a mvc.Signal to indicate the plot needs an
                update. This models listens for the 'needs_update' signal on the
                loaded project and propagates this accordingly.
            current_project: the currently loaded project
            statistics_visble: a boolean indicating whether or not statistic
                should be visible
            current_specimen: the currently selected specimen, is None if more
                than one specimen is selected.
            current_specimens: a list of currently selected specimens, is never
                None, even if only one specimen is selected.
            single_specimen_selected: a boolean indicating whether or not a
                single specimen is selected
            multiple_specimen_selected: a boolean indicating whether or not
                multiple specimen are selected
    """
    # MODEL INTEL:
    class Meta(PyXRDModel.Meta):
        properties = [
            PropIntel(name="current_project", observable=True),
            PropIntel(name="current_specimen", observable=True),
            PropIntel(name="current_specimens", observable=True),
            PropIntel(name="statistics_visible", observable=True),
            PropIntel(name="needs_plot_update", observable=True)
        ]

    # SIGNALS:
    needs_plot_update = None

    # PROPERTIES:
    _current_project = None
    def get_current_project(self):
        return self._current_project
    def set_current_project(self, value):
        if self._current_project is not None: self.relieve_model(self._current_project)
        self._current_project = value
        type(type(self)).object_pool.clear()
        if self._current_project is not None: self.observe_model(self._current_project)
        self.clear_selected()
        self.needs_plot_update.emit()
    current_filename = None

    _statistics_visible = None
    def set_statistics_visible(self, value): self._statistics_visible = value
    def get_statistics_visible(self):
        return self._statistics_visible and self.current_specimen is not None and self.current_project.layout_mode != 1

    _current_specimen = None
    def get_current_specimen(self): return self._current_specimen
    def set_current_specimen(self, value):
        self._current_specimens = [value]
        self._current_specimen = value

    _current_specimens = []
    def get_current_specimens(self): return self._current_specimens
    def set_current_specimens(self, value):
        if value == None:
            value = []
        self._current_specimens = value
        if len(self._current_specimens) == 1:
            self._current_specimen = self._current_specimens[0]
        else:
            self._current_specimen = None

    @property
    def project_loaded(self):
        return self.current_project is not None

    @property
    def specimen_selected(self):
        return self.current_specimen is not None

    @property
    def single_specimen_selected(self):
        return self.specimen_selected and len(self.current_specimens) == 1

    @property
    def multiple_specimens_selected(self):
        return len(self.current_specimens) > 1

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self, project=None):
        """ Initializes the AppModel with the given Project. """
        super(AppModel, self).__init__()
        self.needs_plot_update = Signal()
        self.current_project = project
        if project: project.parent = self
        self._statistics_visible = False

    # ------------------------------------------------------------
    #      Notifications of observable properties
    # ------------------------------------------------------------
    @PyXRDModel.observe("data_changed", signal=True)
    @PyXRDModel.observe("visuals_changed", signal=True)
    def notify_needs_update(self, model, prop_name, info):
        self.needs_plot_update.emit()

    def clear_selected(self):
        self.current_specimens = None

    pass # end of class
예제 #9
0
파일: models.py 프로젝트: tomcat333/PyXRD
class AppModel(PyXRDModel):
    """
        Simple model that stores the state of the application window.
        Should never be made persistent.
        
        Attributes:
            needs_plot_update: a mvc.Signal to indicate the plot needs an
                update. This models listens for the 'needs_update' signal on the
                loaded project and propagates this accordingly.
            current_project: the currently loaded project
            current_specimen: the currently selected specimen, is None if more
                than one specimen is selected.
            current_specimens: a list of currently selected specimens, is never
                None, even if only one specimen is selected.
            single_specimen_selected: a boolean indicating whether or not a
                single specimen is selected
            multiple_specimen_selected: a boolean indicating whether or not
                multiple specimen are selected
    """
    # MODEL INTEL:
    class Meta(PyXRDModel.Meta):
        properties = [
            PropIntel(name="current_project", observable=True),
            PropIntel(name="current_specimen", observable=True),
            PropIntel(name="current_specimens", observable=True),
            PropIntel(name="needs_plot_update", observable=True)
        ]

    # SIGNALS:
    needs_plot_update = None

    # PROPERTIES:
    _current_project = None
    def get_current_project(self):
        return self._current_project
    def set_current_project(self, value):
        if self._current_project is not None: self.relieve_model(self._current_project)
        self._current_project = value
        type(type(self)).object_pool.clear()
        if self._current_project is not None: self.observe_model(self._current_project)
        self.clear_selected()
        self.needs_plot_update.emit()

    @property
    def current_filename(self):
        return self.current_project.filename if self.current_project else None

    _current_specimen = None
    def get_current_specimen(self): return self._current_specimen
    def set_current_specimen(self, value):
        self._current_specimens = [value]
        self._current_specimen = value

    _current_specimens = []
    def get_current_specimens(self): return self._current_specimens
    def set_current_specimens(self, value):
        if value == None:
            value = []
        self._current_specimens = value
        if len(self._current_specimens) == 1:
            self._current_specimen = self._current_specimens[0]
        else:
            self._current_specimen = None

    @property
    def project_loaded(self):
        return self.current_project is not None

    @property
    def specimen_selected(self):
        return self.current_specimen is not None

    @property
    def single_specimen_selected(self):
        return self.specimen_selected and len(self.current_specimens) == 1

    @property
    def multiple_specimens_selected(self):
        return len(self.current_specimens) > 1

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self, project=None):
        """ Initializes the AppModel with the given Project. """
        super(AppModel, self).__init__()
        self.needs_plot_update = Signal()
        self.current_project = project
        if project: project.parent = self

    # ------------------------------------------------------------
    #      Notifications of observable properties
    # ------------------------------------------------------------
    @PyXRDModel.observe("data_changed", signal=True)
    @PyXRDModel.observe("visuals_changed", signal=True)
    def notify_needs_update(self, model, prop_name, info):
        self.needs_plot_update.emit()

    def clear_selected(self):
        self.current_specimens = None

    pass # end of class
예제 #10
0
    def __init__(self, marker_peaks=[], *args, **kwargs):
        super(MineralScorer, self).__init__(*args, **kwargs)
        self._matches = []
        self.matches_changed = Signal()

        self.marker_peaks = marker_peaks  # position, intensity
예제 #11
0
class MineralScorer(DataModel):
    # MODEL INTEL:
    class Meta(DataModel.Meta):
        properties = [
            PropIntel(name="matches", data_type=list, has_widget=True),
            PropIntel(name="minerals", data_type=list, has_widget=True),
            PropIntel(name="matches_changed",
                      data_type=object,
                      has_widget=False,
                      storable=False)
        ]

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

    matches_changed = None

    _matches = None

    def get_matches(self):
        return self._matches

    _minerals = None

    def get_minerals(self):
        # Load them when accessed for the first time:
        if self._minerals == None:
            self._minerals = list()
            with unicode_open(
                    settings.DATA_REG.get_file_path("MINERALS")) as f:
                mineral = ""
                abbreviation = ""
                position_flag = True
                peaks = []
                for line in f:
                    line = line.replace('\n', '')
                    try:
                        number = float(line)
                        if position_flag:
                            position = number
                        else:
                            intensity = number
                            peaks.append((position, intensity))
                        position_flag = not position_flag
                    except ValueError:
                        if mineral != "":
                            self._minerals.append(
                                (mineral, abbreviation, peaks))
                        position_flag = True
                        if len(line) > 25:
                            mineral = line[:24].strip()
                        if len(line) > 49:
                            abbreviation = line[49:].strip()
                        peaks = []
        sorted(self._minerals, key=lambda mineral: mineral[0])
        return self._minerals

    # ------------------------------------------------------------
    #      Initialisation and other internals
    # ------------------------------------------------------------
    def __init__(self, marker_peaks=[], *args, **kwargs):
        super(MineralScorer, self).__init__(*args, **kwargs)
        self._matches = []
        self.matches_changed = Signal()

        self.marker_peaks = marker_peaks  # position, intensity

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def auto_match(self):
        self._matches = score_minerals(self.marker_peaks, self.minerals)
        self.matches_changed.emit()

    def del_match(self, index):
        if self.matches:
            del self.matches[index]
            self.matches_changed.emit()

    def add_match(self, name, abbreviation, peaks):
        matches = score_minerals(self.marker_peaks,
                                 [(name, abbreviation, peaks)])
        if len(matches):
            name, abbreviation, peaks, matches, score = matches[0]
        else:
            matches, score = [], 0.
        self.matches.append([name, abbreviation, peaks, matches, score])
        sorted(self._matches, key=lambda match: match[-1], reverse=True)
        self.matches_changed.emit()

    pass  # end of class
예제 #12
0
    def __init__(self, marker_peaks=[], *args, **kwargs):
        super(MineralScorer, self).__init__(*args, **kwargs)
        self._matches = []
        self.matches_changed = Signal()

        self.marker_peaks = marker_peaks  # position, intensity
예제 #13
0
class MineralScorer(DataModel):
    # MODEL INTEL:
    class Meta(DataModel.Meta):
        properties = [
            PropIntel(name="matches", data_type=list, has_widget=True),
            PropIntel(name="minerals", data_type=list, has_widget=True),
            PropIntel(name="matches_changed", data_type=object, has_widget=False, storable=False),
        ]

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

    matches_changed = None

    _matches = None

    def get_matches(self):
        return self._matches

    _minerals = None

    def get_minerals(self):
        # Load them when accessed for the first time:
        if self._minerals == None:
            self._minerals = list()
            with unicode_open(settings.DATA_REG.get_file_path("MINERALS")) as f:
                mineral = ""
                abbreviation = ""
                position_flag = True
                peaks = []
                for line in f:
                    line = line.replace("\n", "")
                    try:
                        number = float(line)
                        if position_flag:
                            position = number
                        else:
                            intensity = number
                            peaks.append((position, intensity))
                        position_flag = not position_flag
                    except ValueError:
                        if mineral != "":
                            self._minerals.append((mineral, abbreviation, peaks))
                        position_flag = True
                        if len(line) > 25:
                            mineral = line[:24].strip()
                        if len(line) > 49:
                            abbreviation = line[49:].strip()
                        peaks = []
        sorted(self._minerals, key=lambda mineral: mineral[0])
        return self._minerals

    # ------------------------------------------------------------
    #      Initialisation and other internals
    # ------------------------------------------------------------
    def __init__(self, marker_peaks=[], *args, **kwargs):
        super(MineralScorer, self).__init__(*args, **kwargs)
        self._matches = []
        self.matches_changed = Signal()

        self.marker_peaks = marker_peaks  # position, intensity

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def auto_match(self):
        self._matches = score_minerals(self.marker_peaks, self.minerals)
        self.matches_changed.emit()

    def del_match(self, index):
        if self.matches:
            del self.matches[index]
            self.matches_changed.emit()

    def add_match(self, name, abbreviation, peaks):
        matches = score_minerals(self.marker_peaks, [(name, abbreviation, peaks)])
        if len(matches):
            name, abbreviation, peaks, matches, score = matches[0]
        else:
            matches, score = [], 0.0
        self.matches.append([name, abbreviation, peaks, matches, score])
        sorted(self._matches, key=lambda match: match[-1], reverse=True)
        self.matches_changed.emit()

    pass  # end of class
예제 #14
0
 def after():
     Signal.emit(self)
     if callable(self.after): self.after()
예제 #15
0
 def __init__(self, before=None, after=None):
     Signal.__init__(self)
     self.before = before
     self.after = after
     return
예제 #16
0
class Mixture(DataModel, Storable):
    """
        The base model for optimization and refinement of calculated data
        and experimental data. This is the main model you want to interact with,
        lower-level classes' functionality (mainly 
        :class:`~pyxrd.mixture.models.optimizers.Optimizer` and 
        :class:`~pyxrd.mixture.models.refiner.Refiner`) are integrated into this
        class. 
        
        The Mixture is responsible for managing the phases and specimens lists
        and combination-matrix and for delegating any optimization, calculation
        or refinement calls to the appropriate helper class.
    """
    # MODEL INTEL:
    class Meta(DataModel.Meta):
        properties = [ # TODO add labels
            PropIntel(name="name", label="Name", data_type=unicode, storable=True, has_widget=True, is_column=True),
            PropIntel(name="refinables", label="", data_type=object, widget_type="object_tree_view", class_type=RefinableWrapper),
            PropIntel(name="refine_options", label="", data_type=dict, storable=True, stor_name="all_refine_options"),
            PropIntel(name="refine_method", label="", data_type=int, storable=True),
            PropIntel(name="auto_run", label="", data_type=bool, is_column=True, storable=True, has_widget=True),
            PropIntel(name="auto_bg", label="", data_type=bool, is_column=True, storable=True, has_widget=True),
            PropIntel(name="needs_update", label="", data_type=object, storable=False), # Signal used to indicate the mixture needs an update
            PropIntel(name="needs_reset", label="", data_type=object, storable=False,), # Signal used to indicate the mixture matrix needs to be re-built...
        ]
        store_id = "Mixture"

    _data_object = None
    @property
    def data_object(self):
        self._data_object.specimens = [None] * len(self.specimens)
        self._data_object.auto_bg = self.auto_bg
        for i, specimen in enumerate(self.specimens):
            if specimen is not None:
                data_object = specimen.data_object
                data_object.phases = [None] * len(self.phases)
                for j, phase in enumerate(self.phase_matrix[i, ...].flatten()):
                    data_object.phases[j] = phase.data_object if phase is not None else None
                self._data_object.specimens[i] = data_object
            else:
                self._data_object.specimens[i] = None
        return self._data_object

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

    # SIGNALS:
    #: Signal, emitted when the # of phases or specimens changes
    needs_reset = None
    #: Signal, emitted when the patterns of the specimens need an update
    needs_update = None

    # INTERNALS:
    _name = ""
    @property
    def name(self):
        """ The name of this mixture """
        return self._name
    @name.setter
    def name(self, value):
        self._name = value
        self.visuals_changed.emit()

    #: Flag, True if the mixture will automatically adjust phase fractions and scales
    auto_run = False
    #: Flag, True if the mixture is allowed to also update the background level
    auto_bg = True

    #: The tree of refinable properties
    @property
    def refinables(self):
        return self.refiner.refinables

    @property
    def refine_method(self):
        """ An integer describing which method to use for the refinement (see 
        mixture.models.methods.get_all_refine_methods) """
        return self.refiner.refine_method

    #: A dict containing the current refinement options
    @property
    def refine_options(self):
        return  self.refiner.refine_options

    #: A dict containing all refinement options
    @property
    def all_refine_options(self):
        return self.refiner.all_refine_options

    # Lists and matrices:
    #: A 2D numpy object array containing the combination matrix
    phase_matrix = None
    #: The list of specimen objects
    specimens = None
    #: The list of phase names
    phases = None

    @property
    def scales(self):
        """ A list of floats containing the absolute scales for the calculated patterns """
        return self._data_object.scales
    @scales.setter
    def scales(self, value):
        self._data_object.scales = value

    @property
    def bgshifts(self):
        """ A list of background shifts for the calculated patterns """
        return self._data_object.bgshifts
    @bgshifts.setter
    def bgshifts(self, value):
        self._data_object.bgshifts = value

    @property
    def fractions(self):
        """ A list of phase fractions for this mixture """
        return self._data_object.fractions
    @fractions.setter
    def fractions(self, value):
        self._data_object.fractions = value

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        """
            Constructor takes any of its properties as a keyword argument.
            It also support two UUID list keyword arguments:
                - phase_uuids: a list of UUID's for the phases in the mixture
                - specimen_uuids: a list of UUID's for the specimens in the mixture
            These should be *instead* of the phases and specimens keywords.
            
            In addition to the above, the constructor still supports the 
            following deprecated keywords, mapping to a current keyword:
                - phase_indeces: a list of project indices for the phases in the mixture
                - specimen_indeces: a list of project indices for the specimens in the mixture
                
            Any other arguments or keywords are passed to the base class.
        """

        my_kwargs = self.pop_kwargs(kwargs,
            "data_name", "phase_uuids", "phase_indeces", "specimen_uuids",
            "specimen_indeces", "data_phases", "data_scales", "data_bgshifts",
            "data_fractions", "data_refine_method", "fractions",
            "bgshifts", "scales", "phases",
            *[names[0] for names in type(self).Meta.get_local_storable_properties()]
        )
        super(Mixture, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        with self.data_changed.hold():

            self._data_object = MixtureData()

            self.needs_reset = Signal()
            self.needs_update = HoldableSignal()
            self.name = self.get_kwarg(kwargs, "New Mixture", "name", "data_name")
            self.auto_run = self.get_kwarg(kwargs, False, "auto_run")
            self.auto_bg = self.get_kwarg(kwargs, True, "auto_bg")

            # 2D matrix, rows match specimens, columns match mixture 'phases'; contains the actual phase objects
            phase_uuids = self.get_kwarg(kwargs, None, "phase_uuids")
            phase_indeces = self.get_kwarg(kwargs, None, "phase_indeces")
            if phase_uuids is not None:
                self.phase_matrix = np.array([[type(type(self)).object_pool.get_object(uuid) if uuid else None for uuid in row] for row in phase_uuids], dtype=np.object_)
            elif phase_indeces and self.parent is not None:
                warn("The use of object indices is deprecated since version 0.4. Please switch to using object UUIDs.", DeprecationWarning)
                self.phase_matrix = np.array([[self.parent.phases.get_user_data_from_index(index) if index != -1 else None for index in row] for row in phase_indeces], dtype=np.object_)
            else:
                self.phase_matrix = np.empty(shape=(0, 0), dtype=np.object_)

            # list with actual specimens, indexes match with rows in phase_matrix
            specimen_uuids = self.get_kwarg(kwargs, None, "specimen_uuids")
            specimen_indeces = self.get_kwarg(kwargs, None, "specimen_indeces")
            if specimen_uuids:
                self.specimens = [type(type(self)).object_pool.get_object(uuid) if uuid else None for uuid in specimen_uuids]
            elif specimen_indeces and self.parent is not None:
                warn("The use of object indices is deprecated since version 0.4. Please switch to using object UUIDs.", DeprecationWarning)
                self.specimens = [self.parent.specimens.get_user_data_from_index(index) if index != -1 else None for index in specimen_indeces]
            else:
                self.specimens = list()

            # list with mixture phase names, indexes match with cols in phase_matrix
            self.phases = self.get_kwarg(kwargs, list(), "phases", "data_phases")

            # list with scale values, indexes match with rows in phase_matrix (= specimens)
            self.scales = np.asarray(self.get_kwarg(kwargs, [1.0] * len(self.specimens), "scales", "data_scales"))
            # list with specimen background shift values, indexes match with rows in phase_matrix (=specimens)
            self.bgshifts = np.asarray(self.get_kwarg(kwargs, [0.0] * len(self.specimens), "bgshifts", "data_bgshifts"))
            # list with phase fractions, indexes match with cols in phase_matrix (=phases)
            self.fractions = np.asarray(self.get_kwarg(kwargs, [0.0] * len(self.phases), "fractions", "data_fractions"))

            # sanity check:
            n, m = self.phase_matrix.shape if self.phase_matrix.ndim == 2 else (0, 0)
            if len(self.scales) != n or len(self.specimens) != n or len(self.bgshifts) != n:
                raise IndexError, "Shape mismatch: scales (%d), background shifts (%d) or specimens (%d) list lengths do not match with row count (%d) of phase matrix" % (len(self.scales), len(self.specimens), len(self.bgshifts), n)
            if len(self.phases) != m or len(self.fractions) != m:
                raise IndexError, "Shape mismatch: fractions or phases lists do not match with column count of phase matrix"

            self._observe_specimens()
            self._observe_phases()

            self.optimizer = Optimizer(parent=self)
            self.refiner = Refiner(
                refine_method=self.get_kwarg(kwargs, 0, "refine_method", "data_refine_method"),
                refine_options=self.get_kwarg(kwargs, dict(), "refine_options"),
                parent=self)

            self.update()

            self.observe_model(self)

            pass # end hold data_changed

    # ------------------------------------------------------------
    #      Notifications of observable properties
    # ------------------------------------------------------------
    @DataModel.observe("removed", signal=True)
    def notify_removed(self, model, prop_name, info):
        if model == self:
            self._relieve_phases()
            self._relieve_specimens()

    @DataModel.observe("needs_update", signal=True)
    def notify_needs_update(self, model, prop_name, info):
        with self.data_changed.hold():
            self.update()

    @DataModel.observe("data_changed", signal=True)
    def notify_data_changed(self, model, prop_name, info):
        if not model == self and not (
            info.arg == "based_on" and model.based_on is not None and
            model.based_on in self.phase_matrix):
                self.needs_update.emit()

    @DataModel.observe("visuals_changed", signal=True)
    def notify_visuals_changed(self, model, prop_name, info):
        if isinstance(model, Phase) and \
           not (info.arg == "based_on" and model.based_on is not None and model.based_on in self.phase_matrix):
            for i, specimen in enumerate(self.specimens):
                if specimen is not None:
                    specimen.update_visuals(self.phase_matrix[i, :])

    # ------------------------------------------------------------
    #      Input/Output stuff
    # ------------------------------------------------------------
    def json_properties(self):
        self.refiner.update_refinement_treestore()
        retval = Storable.json_properties(self)

        retval["phase_uuids"] = [[item.uuid if item else "" for item in row] for row in map(list, self.phase_matrix)]
        retval["specimen_uuids"] = [specimen.uuid if specimen else "" for specimen in self.specimens]
        retval["phases"] = self.phases
        retval["fractions"] = self.fractions.tolist()
        retval["bgshifts"] = self.bgshifts.tolist()
        retval["scales"] = self.scales.tolist()

        return retval

    @staticmethod
    def from_json(**kwargs):
        # Remove this deprecated kwarg:
        if "refinables" in kwargs:
            del kwargs["refinables"]
        return Mixture(**kwargs)

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def unset_phase(self, phase):
        """ Clears a phase slot in the phase matrix """
        with self.needs_update.hold_and_emit():
            with self.data_changed.hold():
                shape = self.phase_matrix.shape
                with self._relieve_and_observe_phases():
                    for i in range(shape[0]):
                        for j in range(shape[1]):
                            if self.phase_matrix[i, j] == phase:
                                self.phase_matrix[i, j] = None
                self.refiner.update_refinement_treestore()

    def unset_specimen(self, specimen):
        """ Clears a specimen slot in the specimen list """
        with self.needs_update.hold_and_emit():
            with self.data_changed.hold():
                with self._relieve_and_observe_specimens():
                    for i, spec in enumerate(self.specimens):
                        if spec == specimen:
                            self.specimens[i] = None

    def get_phase(self, specimen_slot, phase_slot):
        """Returns the phase at the given slot positions or None if not set"""
        return self.phase_matrix[specimen_slot, phase_slot]

    def set_phase(self, specimen_slot, phase_slot, phase):
        """Sets the phase at the given slot positions"""
        if self.parent is not None: #no parent = no valid phases
            with self.needs_update.hold_and_emit():
                with self.data_changed.hold():
                    with self._relieve_and_observe_phases():
                        if phase is not None and not phase in self.parent.phases:
                            raise RuntimeError, "Cannot add a phase to a Mixture which is not inside the project!"
                        self.phase_matrix[specimen_slot, phase_slot] = phase
                    self.refiner.update_refinement_treestore()

    def get_specimen(self, specimen_slot):
        """Returns the specimen at the given slot position or None if not set"""
        return self.specimens[specimen_slot]

    def set_specimen(self, specimen_slot, specimen):
        """Sets the specimen at the given slot position"""
        if self.parent is not None: #no parent = no valid specimens
            with self.needs_update.hold_and_emit():
                with self.data_changed.hold():
                    with self._relieve_and_observe_specimens():
                        if specimen is not None and not specimen in self.parent.specimens:
                            raise RuntimeError, "Cannot add a specimen to a Mixture which is not inside the project!"
                        self.specimens[specimen_slot] = specimen

    @contextmanager
    def _relieve_and_observe_specimens(self):
        self._relieve_specimens()
        yield
        self._observe_specimens()

    def _observe_specimens(self):
        """ Starts observing specimens in the specimens list"""
        for specimen in self.specimens:
            if specimen is not None:
                self.observe_model(specimen)

    def _relieve_specimens(self):
        """ Relieves specimens observer calls """
        for specimen in self.specimens:
            if specimen is not None:
                self.relieve_model(specimen)

    @contextmanager
    def _relieve_and_observe_phases(self):
        self._relieve_phases()
        yield
        self._observe_phases()

    def _observe_phases(self):
        """ Starts observing phases in the phase matrix"""
        for phase in self.phase_matrix.flat:
            if phase is not None:
                self.observe_model(phase)

    def _relieve_phases(self):
        """ Relieves phase observer calls """
        for phase in self.phase_matrix.flat:
            if phase is not None:
                self.relieve_model(phase)

    def add_phase_slot(self, phase_name, fraction):
        """ Adds a new phase column to the phase matrix """
        with self.needs_update.hold_and_emit():
            with self.data_changed.hold():
                self.phases.append(phase_name)
                self.fractions = np.append(self.fractions, fraction)
                n, m = self.phase_matrix.shape if self.phase_matrix.ndim == 2 else (0, 0)
                if self.phase_matrix.size == 0:
                    self.phase_matrix = np.resize(self.phase_matrix.copy(), (n, m + 1))
                    self.phase_matrix[:] = None
                else:
                    self.phase_matrix = np.concatenate([self.phase_matrix.copy(), [[None]] * n ], axis=1)
                    self.phase_matrix[:, m] = None
                self.refiner.update_refinement_treestore()
        return m

    def del_phase_slot(self, phase_slot):
        """ Deletes a phase column using its index """
        with self.needs_update.hold_and_emit():
            with self.data_changed.hold():
                with self._relieve_and_observe_phases():
                    # Remove the corresponding phase name, fraction & references:
                    del self.phases[phase_slot]
                    self.fractions = np.delete(self.fractions, phase_slot)
                    self.phase_matrix = np.delete(self.phase_matrix, phase_slot, axis=1)
                # Update our refinement tree store to reflect current state
                self.refiner.update_refinement_treestore()
        # Inform any interested party they need to update their representation
        self.needs_reset.emit()

    def del_phase_slot_by_name(self, phase_name):
        """ Deletes a phase slot using its name """
        self.del_phase_slot(self.phases.index(phase_name))

    def add_specimen_slot(self, specimen, scale, bgs):
        """ Adds a new specimen to the phase matrix (a row) and specimen list """
        with self.needs_update.hold_and_emit():
            with self.data_changed.hold():
                self.specimens.append(None)
                self.scales = np.append(self.scales, scale)
                self.bgshifts = np.append(self.bgshifts, bgs)
                n, m = self.phase_matrix.shape if self.phase_matrix.ndim == 2 else (0, 0)
                if self.phase_matrix.size == 0:
                    self.phase_matrix = np.resize(self.phase_matrix.copy(), (n + 1, m))
                    self.phase_matrix[:] = None
                else:
                    self.phase_matrix = np.concatenate([self.phase_matrix.copy(), [[None] * m] ], axis=0)
                    self.phase_matrix[n, :] = None
                if specimen is not None:
                    self.set_specimen(n, specimen)
        return n

    def del_specimen_slot(self, specimen_slot):
        """ Deletes a specimen slot using its slot index """
        with self.needs_update.hold_and_emit():
            with self.data_changed.hold():
                # Remove the corresponding specimen name, scale, bg-shift & phases:
                with self._relieve_and_observe_specimens():
                    del self.specimens[specimen_slot]
                    self.scales = np.delete(self.scales, specimen_slot)
                    self.bgshifts = np.delete(self.bgshifts, specimen_slot)
                    self.phase_matrix = np.delete(self.phase_matrix, specimen_slot, axis=0)
                # Update our refinement tree store to reflect current state
                self.refiner.update_refinement_treestore()
        # Inform any interested party they need to update their representation
        self.needs_reset.emit()

    def del_specimen_slot_by_object(self, specimen):
        """ Deletes a specimen slot using the actual object """
        try:
            self.del_specimen_slot(self.specimens.index(specimen))
        except ValueError:
            logger.exception("Caught a ValueError when deleting a specimen from  mixture '%s'" % self.name)

    # ------------------------------------------------------------
    #      Refinement stuff:
    # ------------------------------------------------------------
    def set_data_object(self, mixture, calculate=False):
        """
            Sets the fractions, scales and bgshifts of this mixture.
        """
        if mixture is not None:
            with self.needs_update.ignore():
                with self.data_changed.hold_and_emit():
                    self.fractions[:] = list(mixture.fractions)
                    self.scales[:] = list(mixture.scales)
                    self.bgshifts[:] = list(mixture.bgshifts)

                    if calculate: # (re-)calculate if requested:
                        mixture = self.optimizer.calculate(mixture)

                    for i, (specimen_data, specimen) in enumerate(izip(mixture.specimens, self.specimens)):
                        if specimen is not None:
                            with specimen.data_changed.ignore():
                                specimen.update_pattern(
                                    specimen_data.total_intensity,
                                    specimen_data.phase_intensities * self.fractions[:, np.newaxis] * self.scales[i],
                                    self.phase_matrix[i, :]
                                )

    def optimize(self):
        """
            Optimize the current solution (fractions, scales, bg shifts & calculate
            phase intensities)
        """
        with self.needs_update.ignore():
            with self.data_changed.hold():
                # no need to re-calculate, is already done by the optimization
                self.set_data_object(self.optimizer.optimize())

    def apply_current_data_object(self):
        """
            Recalculates the intensities using the current fractions, scales
            and bg shifts without optimization
        """
        with self.needs_update.ignore():
            with self.data_changed.hold():
                self.set_data_object(self.data_object, calculate=True)

    # @print_timing
    def update(self):
        """
            Optimizes or re-applies the current mixture 'solution'.
            Effectively re-calculates the entire patterns.
        """
        with self.needs_update.ignore():
            with self.data_changed.hold():
                if self.auto_run:
                    self.optimize()
                else:
                    self.apply_current_data_object()

    # ------------------------------------------------------------
    #      Various other things:
    # ------------------------------------------------------------
    def get_composition_matrix(self):
        """
            Returns a matrix containing the oxide composition for each specimen 
            in this mixture. It uses the COMPOSITION_CONV file for this purpose
            to convert element weights into their oxide weight equivalent.
        """

        # create an atom nr -> (atom name, conversion) mapping
        # this is used to transform most of the elements into their oxides
        atom_conv = OrderedDict()
        with open(settings.DATA_REG.get_file_path("COMPOSITION_CONV"), 'r') as f:
            reader = csv.reader(f)
            reader.next() # skip header
            for row in reader:
                nr, name, fact = row
                atom_conv[int(nr)] = (name, float(fact))

        comps = list()
        for i, row in enumerate(self.phase_matrix):
            comp = dict()
            for j, phase in enumerate(row):
                phase_fract = self.fractions[j]
                for k, component in enumerate(phase.components):
                    comp_fract = phase.probabilities.mW[k] * phase_fract
                    for atom in chain(component.layer_atoms,
                            component.interlayer_atoms):
                        nr = atom.atom_type.atom_nr
                        if nr in atom_conv:
                            wt = atom.pn * atom.atom_type.weight * comp_fract * atom_conv[nr][1]
                            comp[nr] = comp.get(nr, 0.0) + wt
            comps.append(comp)

        final_comps = np.zeros(shape=(len(atom_conv) + 1, len(comps) + 1), dtype='a15')
        final_comps[0, 0] = " "*8
        for j, comp in enumerate(comps):
            fact = 100.0 / sum(comp.values())
            for i, (nr, (oxide_name, conv)) in enumerate(atom_conv.iteritems()):
                wt = comp.get(nr, 0.0) * fact
                # set relevant cells:
                if i == 0:
                    final_comps[i, j + 1] = self.specimens[j].name.ljust(15)[:15]
                if j == 0:
                    final_comps[i + 1, j] = ("%s  " % oxide_name).rjust(8)[:8]
                final_comps[i + 1, j + 1] = ("%.1f" % wt).ljust(15)[:15]

        return final_comps

    pass # end of class
예제 #17
0
파일: base.py 프로젝트: tomcat333/PyXRD
 def __init__(self, parent=None, *args, **kwargs):
     super(ChildModel, self).__init__(*args, **kwargs)
     self.removed = Signal()
     self.added = Signal()
     self.parent = parent
예제 #18
0
class RefineContext(ChildModel):
    """
        A context model for the refinement procedure.
        Enables to keep track of the initial state, the current state and
        the best state found so far (a state being a tuple of a solution and
        its residual).
        Also loads the properties to be refined once, together with their
        ranges and initial values and the refinement options.
    """

    class Meta(ChildModel.Meta):
        properties = [ # TODO add labels
            PropIntel(name="solution_added"),
            PropIntel(name="status", column=True, data_type=str, has_widget=False),
            PropIntel(name="status_message", column=True, data_type=str, has_widget=False),
            PropIntel(name="initial_residual", column=True, data_type=float, has_widget=True, widget_type='label'),
            PropIntel(name="last_residual", column=True, data_type=float, has_widget=True, widget_type='label'),
            PropIntel(name="best_residual", column=True, data_type=float, has_widget=True, widget_type='label'),
        ]

    mixture = property(ChildModel.parent.fget, ChildModel.parent.fset)

    # SIGNALS:
    solution_added = None

    # OTHER:
    options = None

    initial_solution = None
    initial_residual = None

    last_solution = None
    last_residual = None

    best_solution = None
    best_residual = None

    ref_props = None
    values = None
    ranges = None

    status = "not initialized"
    status_message = ""

    record_header = None
    records = None

    def __init__(self, parent=None, store=False, options={}):
        super(RefineContext, self).__init__(parent=parent)
        self.options = options

        logger.info("Creating RefineContext with the following refinables:")

        if parent is not None:
            self.ref_props = []
            self.values = []
            self.ranges = ()
            for node in parent.refinables.iter_children():
                ref_prop = node.object
                if ref_prop.refine and ref_prop.refinable:
                    logger.info(" - %s from %r" % (ref_prop.text_title, ref_prop.obj))
                    self.ref_props.append(ref_prop)
                    self.values.append(ref_prop.value)
                    if not (ref_prop.value_min < ref_prop.value_max):
                        logger.info("Error in refinement setup!")
                        raise RefineSetupError, "Refinable property `%s` needs a valid range!" % (ref_prop.get_descriptor(),)
                    self.ranges += ((ref_prop.value_min, ref_prop.value_max),)

        if len(self.ref_props) == 0:
            logger.error("  No refinables selected!")
            raise RefineSetupError, "RefineContext needs at least one refinable property!"

        if store: self.store_project_state()

        self.initial_residual = self.mixture.optimizer.get_current_residual()
        self.initial_solution = np.array(self.values, dtype=float)

        self.best_solution = self.initial_solution
        self.best_residual = self.initial_residual

        self.last_solution = self.initial_solution
        self.last_residual = self.initial_residual

        self.solution_added = Signal()

        self.status = "created"

    def store_project_state(self):
        # Create this once, this is rather costly
        # Any multithreaded/processed function that needs the project state
        # can then reload the project and get the correct mixture, setup
        # this context again... etc.
        # Only do this for the main process though, otherwise memory usage will
        # go through the roof.
        self.project = self.mixture.project
        self.project_dump = self.project.dump_object(zipped=True).getvalue()
        self.mixture_index = self.project.mixtures.index(self.mixture)

    def get_uniform_solutions(self, num):
        """
            Returns `num` solutions (uniformly distributed within their ranges) 
            for the selected parameters.
        """
        start_solutions = np.random.random_sample((num, len(self.ref_props)))
        ranges = np.asarray(self.ranges, dtype=float)
        return ranges[:, 0] + start_solutions * (ranges[:, 1] - ranges[:, 0])

    def set_initial_solution(self, solution):
        """
            Re-sets the initial solutions to a given solution array 
        """
        self.initial_solution = np.array(solution, dtype=float)
        self.apply_solution(self.initial_solution)
        self.initial_residual = self.mixture.optimizer.get_current_residual()

        self.best_solution = self.initial_solution
        self.best_residual = self.initial_residual

        self.last_solution = self.initial_solution
        self.last_residual = self.initial_residual

    def apply_solution(self, solution):
        """
            Applies the given solution
        """
        solution = np.asanyarray(solution)
        with self.mixture.needs_update.hold():
            with self.mixture.data_changed.hold():
                for i, ref_prop in enumerate(self.ref_props):
                    if not (solution.shape == ()):
                        ref_prop.value = float(solution[i])
                    else:
                        ref_prop.value = float(solution[()])

    def get_data_object_for_solution(self, solution):
        """
            Gets the mixture data object after setting the given solution
        """
        with self.mixture.needs_update.ignore():
            with self.mixture.data_changed.ignore():
                self.apply_solution(solution)
                return pickle.dumps(self.mixture.data_object)

    def get_pickled_data_object_for_solution(self, solution):
        """
            Gets the mixture data object after setting the given solution
        """
        with self.mixture.needs_update.ignore():
            with self.mixture.data_changed.ignore():
                self.apply_solution(solution)
                return pickle.dumps(
                    self.mixture.data_object,
                    protocol=pickle.HIGHEST_PROTOCOL
                )

    def get_residual_for_solution(self, solution):
        """
            Gets the residual for the given solution after setting it
        """
        self.apply_solution(solution)
        return self.mixture.optimizer.get_optimized_residual()

    def update(self, solution, residual=None):
        """
            Update's the refinement contect with the given solution:
                - applies the solution & gets the residual if not given
                - stores it as the `last_solution`
                - checks if this solution is better then the current best solution,
                  and if so, stores it as such
                - emits the `solution_added` signal for any part interested
                
            This function and it's signal can be used to record a refinement
            history. This function only stores the initial, the best and the
            latest solution. The record_state_data can be used to store a record
            of the refinement.
        """
        residual = residual if residual is not None else self.get_residual_for_solution(solution)
        self.last_solution = solution
        self.last_residual = residual
        if self.best_residual == None or self.best_residual > self.last_residual:
            self.best_residual = self.last_residual
            self.best_solution = self.last_solution
        self.solution_added.emit(arg=(self.last_solution, self.last_residual))

    def apply_best_solution(self):
        self.apply_solution(self.best_solution)

    def apply_last_solution(self):
        self.apply_solution(self.last_solution)

    def apply_initial_solution(self):
        self.apply_solution(self.initial_solution)

    def best_solution_to_string(self):
        retval = ""
        for i, ref_prop in enumerate(self.ref_props):
            retval += "%25s: %f\n" % (ref_prop.get_descriptor(), self.best_solution[i])
        return retval

    def record_state_data(self, state_data):
        keys, record = zip(*state_data)
        if self.record_header == None:
            self.record_header = keys
            self.records = []
        self.records.append(record)

    pass # end of class
예제 #19
0
파일: models.py 프로젝트: tomcat333/PyXRD
 def __init__(self, project=None):
     """ Initializes the AppModel with the given Project. """
     super(AppModel, self).__init__()
     self.needs_plot_update = Signal()
     self.current_project = project
     if project: project.parent = self
예제 #20
0
파일: signals.py 프로젝트: Python3pkg/PyXRD
 def after():
     Signal.emit(self)
     if isinstance(self.after, collections.Callable): self.after()