Exemplo n.º 1
0
class RefinementInfo(PyXRDModel, Storable):
    """
        A model that is used to store the refinement information for each
        refinable value (in other models): minimum and maximum value and
        a flag to indicate whether this value is selected for refinement.
    """

    # MODEL INTEL:
    class Meta(PyXRDModel.Meta, Storable.Meta):
        store_id = "RefinementInfo"

    minimum = FloatProperty(default=0.0, text="Minimum", persistent=True)
    maximum = FloatProperty(default=1.0, text="Maximum", persistent=True)
    refine = BoolProperty(default=False, text="Refine", persistent=True)

    def __init__(self, minimum, maximum, refine, *args, **kwargs):
        """
            Valid *positional* arguments for a RefinementInfo are:
                refine: whether or not the linked parameter is selected for refinement
                minimum: the minimum allowable value for the linked parameter
                maximum: the maximum allowable value for the linked parameter   
        """
        super(RefinementInfo, self).__init__()
        self.refine = refine
        self.minimum = not_none(minimum, 0.0)
        self.maximum = not_none(maximum, 1.0)

    def to_json(self):
        return self.json_properties()

    def json_properties(self):
        return [self.minimum, self.maximum, self.refine]

    pass # end of class
Exemplo n.º 2
0
class R1G4Model(_AbstractProbability):
    r"""
    Probability model for Reichweite 1 with 4 components.
    
    The independent variables (# = g*(g-1) = 12) are:
    
    .. math::
        :nowrap:
        
            \begin{align*}
                & W_1
                & P_{11} (W_1 < 0,5)\text{ or }P_{xx} (W_1 > 0,5) 
                with P_{xx} = \frac {W_{22} + W_{23} + W_{24} + W_{32} + W_{33} + W_{34} + W_{42} + W_{43} + W_{44}}{W_2 + W_3 + W_4} \\
                & R_2 = \frac{ W_2 }{W_2 + W_3 + W_4}
                & R_3 = \frac{ W_3 }{W_3 + W_4} \\
                & G_2 = \frac{W_{22} + W_{23} + W_{24}}{\sum_{i=2}^{4}\sum_{j=2}^4{W_{ij}}}
                & G_3 = \frac{W_{32} + W_{33} + W_{34}}{\sum_{i=3}^{4}\sum_{j=2}^4{W_{ij}}} \\
                & G_{22} = \frac{W_{22}}{W_{22} + W_{23} + W_{24}}
                & G_{23} = \frac{W_{23}}{W_{23} + W_{24}} \\
                & G_{32} = \frac{W_{32}}{W_{32} + W_{33} + W_{34}}
                & G_{33} = \frac{W_{33}}{W_{33} + W_{34}} \\
                & G_{42} = \frac{W_{42}}{W_{42} + W_{43} + W_{44}}
                & G_{44} = \frac{W_{43}}{W_{43} + W_{44}}
            \end{align*} 
    
    Calculation of the other variables happens as follows:
    
    .. math::
        :nowrap:
        
        \begin{align*}
            & \text{Calculate the base weight fractions of each component:} \\
            & W_2 = (1 - W_1) \cdot R_1 \\
            & W_3 = (1 - W_1 - W_2) \cdot R_2 \\
            & W_4 = (1 - W_1 - W_2 - W_3) \\
            & \\
            & \text{if $W_1 \leq 0.5$:} \\
            & \quad \text{$P_{11}$ is given}\\
            & \quad W_{xx} = W_{22} + W_{23} + W_{24} + W_{32} + W_{33} + W_{34} + W_{42} + W_{43} + W_{44} = W_1 \cdot (1 - P_{11}) + W_2 + W_3 + W_4 \\
            & \text{if $W_1 > 0.5$:} \\
            & \quad \text{$P_{xx}$ is given and $P_{11}$ is derived further down} \\
            & \quad W_{xx} = W_{22} + W_{23} + W_{24} + W_{32} + W_{33} + W_{34} + W_{42} + W_{43} + W_{44} = P_{xx} \cdot (W_2 + W_3 + W_4) \\  
            & \\
            & \text{Caclulate a partial sum of the $2^{nd}$ component's contributions: } \\
            & W_{2x} = W_{xx} \cdot G_2 \\
            & \text{Calculate a partial sum of the $3^{d}$ and $4^{th}$ component's contributions:} \\
            & W_{yx} = W_{xx} - W_{2x} \\
            & \text{Calculate a partial sum of the $3^{d}$ component's contributions:} \\
            & W_{3x} = W_{yx} \cdot G_3 \\
            & \text{Calculate a partial sum of the $4^{th}$ component's contributions:} \\
            & W_{4x} = W_{yx} - W_{3x} \\
            & \\
            & W_{22} = G_{22} \cdot W_{2x} \\
            & W_{23} = G_{23} \cdot (W_{2x} - W_{22}) \\
            & W_{24} = W{2x} - W_{22} - W_{23} \\
            & \\
            & W_{32} = G_{32} \cdot W_{3x} \\
            & W_{33} = G_{33} \cdot (W_{3x} - W_{32}) \\
            & W_{34} = W{3x} - W_{32} - W_{33} \\
            & \\
            & W_{42} = G_{42} \cdot W_{4x} \\
            & W_{43} = G_{43} \cdot (W_{4x} - W_{42}) \\
            & W_{44} = W{4x} - W_{42} - W_{43} \\
            & \\
            & \text{ From the above weight fractions the junction probabilities 
                for any combination of $2^{nd}$, $3^{d}$ and $4^{th}$ type
                components can be calculated. } \\
            & \text{ The remaining probabilities are: } \\
            & P_{12} = \begin{dcases}
                \frac{W_2 - W_{22} - W_{32} - W_{42}}{W_1}, & \text{if $W_1 > 0$} \\
                0, & \text{otherwise}
            \end{dcases} \\
            & P_{13} = \begin{dcases}
                \frac{W_3 - W_{23} - W_{33} - W_{43}}{W_1}, & \text{if $W_1 > 0$} \\
                0, & \text{otherwise}
            \end{dcases} \\
            & P_{14} = \begin{dcases}
                \frac{W_4 - W_{24} - W_{34} - W_{44}}{W_1}, & \text{if $W_1 > 0$} \\
                0, & \text{otherwise}
            \end{dcases} \\
            & \\
            & \text{if $W_1 \leq 0.5$}: \\
            & \quad P_{11} = 1 - P_{12} - P_{13} - P_{14} \\
            & \text{Remainder of weight fraction can now be calculated as follows:} \\
            & \quad W_{ij} = {W_{ii}} \cdot {P_{ij}} \quad \forall {i,j} \in \left[ {1, 4} \right] \\
        \end{align*}
    """

    # MODEL METADATA:
    class Meta(_AbstractProbability.Meta):
        store_id = "R1G4Model"

    # PROPERTIES
    _G = 4

    inherit_W1 = BoolProperty(default=False,
                              text="Inherit flag for W1",
                              persistent=True,
                              visible=True,
                              set_action_name="update",
                              mix_with=(SetActionMixin, ))
    W1 = FloatProperty(default=0.6,
                       text="W1",
                       math_text=r"$W_1$",
                       persistent=True,
                       visible=True,
                       refinable=True,
                       store_private=True,
                       minimum=0.0,
                       maximum=1.0,
                       is_independent=True,
                       inheritable=True,
                       inherit_flag="inherit_W1",
                       inherit_from="parent.based_on.probabilities.W1",
                       set_action_name="update",
                       mix_with=(SetActionMixin, RefinableMixin,
                                 InheritableMixin))

    inherit_P11_or_P22 = BoolProperty(default=False,
                                      text="Inherit flag for P11_or_P22",
                                      persistent=True,
                                      visible=True,
                                      set_action_name="update",
                                      mix_with=(SetActionMixin, ))
    P11_or_P22 = FloatProperty(
        default=0.25,
        text="P11_or_P22",
        math_text=r"$P_{11} %s$ or $\newline P_{22} %s$" %
        (mt_range(0.0, "W_1", 0.5), mt_range(0.5, "W_1", 1.0)),
        persistent=True,
        visible=True,
        refinable=True,
        store_private=True,
        minimum=0.0,
        maximum=1.0,
        is_independent=True,
        inheritable=True,
        inherit_flag="inherit_P11_or_P22",
        inherit_from="parent.based_on.probabilities.P11_or_P22",
        set_action_name="update",
        mix_with=(SetActionMixin, RefinableMixin, InheritableMixin))

    inherit_R1 = BoolProperty(default=False,
                              text="Inherit flag for R1",
                              persistent=True,
                              visible=True,
                              set_action_name="update",
                              mix_with=(SetActionMixin, ))
    R1 = FloatProperty(default=0.5,
                       text="W2/(W2+W3+W4)",
                       math_text=r"$\large\frac{W_2}{W_2 + W_3 + W_4}$",
                       persistent=True,
                       visible=True,
                       refinable=True,
                       store_private=True,
                       minimum=0.0,
                       maximum=1.0,
                       is_independent=True,
                       inheritable=True,
                       inherit_flag="inherit_R1",
                       inherit_from="parent.based_on.probabilities.R1",
                       set_action_name="update",
                       mix_with=(SetActionMixin, RefinableMixin,
                                 InheritableMixin))

    inherit_R2 = BoolProperty(default=False,
                              text="Inherit flag for R2",
                              persistent=True,
                              visible=True,
                              set_action_name="update",
                              mix_with=(SetActionMixin, ))
    R2 = FloatProperty(default=0.5,
                       text="W3/(W3+W4)",
                       math_text=r"$\large\frac{W_3}{W_3 + W_4}$",
                       persistent=True,
                       visible=True,
                       refinable=True,
                       store_private=True,
                       minimum=0.0,
                       maximum=1.0,
                       is_independent=True,
                       inheritable=True,
                       inherit_flag="inherit_R2",
                       inherit_from="parent.based_on.probabilities.R2",
                       set_action_name="update",
                       mix_with=(SetActionMixin, RefinableMixin,
                                 InheritableMixin))

    inherit_G1 = BoolProperty(default=False,
                              text="Inherit flag for G1",
                              persistent=True,
                              visible=True,
                              set_action_name="update",
                              mix_with=(SetActionMixin, ))
    G1 = FloatProperty(
        default=0.5,
        text="(W22+W23+W24)/(W22+W23+W24+W32+W33+W34+W42+W43+W44)",
        math_text=
        r"$\large\frac{\sum_{j=2}^{4} W_{2j}}{\sum_{i=2}^{4} \sum_{j=2}^{4} W_{ij}}$",
        persistent=True,
        visible=True,
        refinable=True,
        store_private=True,
        minimum=0.0,
        maximum=1.0,
        is_independent=True,
        inheritable=True,
        inherit_flag="inherit_G1",
        inherit_from="parent.based_on.probabilities.G1",
        set_action_name="update",
        mix_with=(SetActionMixin, RefinableMixin, InheritableMixin))

    inherit_G2 = BoolProperty(default=False,
                              text="Inherit flag for G2",
                              persistent=True,
                              visible=True,
                              set_action_name="update",
                              mix_with=(SetActionMixin, ))
    G2 = FloatProperty(
        default=0.4,
        text="(W32+W33+W34)/(W32+W33+W34+W42+W43+W44)",
        math_text=
        r"$\large\frac{\sum_{j=2}^{4} W_{3j}}{\sum_{i=3}^{4} \sum_{j=2}^{4} W_{ij}}$",
        persistent=True,
        visible=True,
        refinable=True,
        store_private=True,
        minimum=0.0,
        maximum=1.0,
        is_independent=True,
        inheritable=True,
        inherit_flag="inherit_G2",
        inherit_from="parent.based_on.probabilities.G2",
        set_action_name="update",
        mix_with=(SetActionMixin, RefinableMixin, InheritableMixin))

    inherit_G11 = BoolProperty(default=False,
                               text="Inherit flag for G11",
                               persistent=True,
                               visible=True,
                               set_action_name="update",
                               mix_with=(SetActionMixin, ))
    G11 = FloatProperty(
        default=0.5,
        text="W22/(W22+W23+W24)",
        math_text=r"$\large\frac{W_{22}}{\sum_{j=2}^{4} W_{2j}}$",
        persistent=True,
        visible=True,
        refinable=True,
        store_private=True,
        minimum=0.0,
        maximum=1.0,
        is_independent=True,
        inheritable=True,
        inherit_flag="inherit_G11",
        inherit_from="parent.based_on.probabilities.G11",
        set_action_name="update",
        mix_with=(SetActionMixin, RefinableMixin, InheritableMixin))

    inherit_G12 = BoolProperty(default=False,
                               text="Inherit flag for G12",
                               persistent=True,
                               visible=True,
                               set_action_name="update",
                               mix_with=(SetActionMixin, ))
    G12 = FloatProperty(
        default=0.5,
        text="W23/(W23+W24)",
        math_text=r"$\large\frac{W_{23}}{\sum_{j=3}^{4} W_{2j}}$",
        persistent=True,
        visible=True,
        refinable=True,
        store_private=True,
        minimum=0.0,
        maximum=1.0,
        is_independent=True,
        inheritable=True,
        inherit_flag="inherit_G12",
        inherit_from="parent.based_on.probabilities.G12",
        set_action_name="update",
        mix_with=(SetActionMixin, RefinableMixin, InheritableMixin))

    inherit_G21 = BoolProperty(default=False,
                               text="Inherit flag for G21",
                               persistent=True,
                               visible=True,
                               set_action_name="update",
                               mix_with=(SetActionMixin, ))
    G21 = FloatProperty(
        default=0.8,
        text="W32/(W32+W33+W34)",
        math_text=r"$\large\frac{W_{32}}{\sum_{j=2}^{4} W_{3j}}$",
        persistent=True,
        visible=True,
        refinable=True,
        store_private=True,
        minimum=0.0,
        maximum=1.0,
        is_independent=True,
        inheritable=True,
        inherit_flag="inherit_G21",
        inherit_from="parent.based_on.probabilities.G21",
        set_action_name="update",
        mix_with=(SetActionMixin, RefinableMixin, InheritableMixin))

    inherit_G22 = BoolProperty(default=False,
                               text="Inherit flag for G22",
                               persistent=True,
                               visible=True,
                               set_action_name="update",
                               mix_with=(SetActionMixin, ))
    G22 = FloatProperty(
        default=0.8,
        text="W33/(W32+W34)",
        math_text=r"$\large\frac{W_{33}}{\sum_{j=3}^{4} W_{3j}}$",
        persistent=True,
        visible=True,
        refinable=True,
        store_private=True,
        minimum=0.0,
        maximum=1.0,
        is_independent=True,
        inheritable=True,
        inherit_flag="inherit_G22",
        inherit_from="parent.based_on.probabilities.G22",
        set_action_name="update",
        mix_with=(SetActionMixin, RefinableMixin, InheritableMixin))

    inherit_G31 = BoolProperty(default=False,
                               text="Inherit flag for G31",
                               persistent=True,
                               visible=True,
                               set_action_name="update",
                               mix_with=(SetActionMixin, ))
    G31 = FloatProperty(
        default=0.7,
        text="W42/(W42+W43+W44)",
        math_text=r"$\large\frac{W_{42}}{\sum_{j=2}^{4} W_{4j}}$",
        persistent=True,
        visible=True,
        refinable=True,
        store_private=True,
        minimum=0.0,
        maximum=1.0,
        is_independent=True,
        inheritable=True,
        inherit_flag="inherit_G31",
        inherit_from="parent.based_on.probabilities.G31",
        set_action_name="update",
        mix_with=(SetActionMixin, RefinableMixin, InheritableMixin))

    inherit_G32 = BoolProperty(default=False,
                               text="Inherit flag for G32",
                               persistent=True,
                               visible=True,
                               set_action_name="update",
                               mix_with=(SetActionMixin, ))
    G32 = FloatProperty(
        default=0.5,
        text="W43/(W43+W44)",
        math_text=r"$\large\frac{W_{43}}{\sum_{j=3}^{4} W_{4j}}$",
        persistent=True,
        visible=True,
        refinable=True,
        store_private=True,
        minimum=0.0,
        maximum=1.0,
        is_independent=True,
        inheritable=True,
        inherit_flag="inherit_G32",
        inherit_from="parent.based_on.probabilities.G32",
        set_action_name="update",
        mix_with=(SetActionMixin, RefinableMixin, InheritableMixin))

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------

    def __init__(self,
                 W1=0.6,
                 P11_or_P22=0.25,
                 R1=0.5,
                 R2=0.5,
                 G1=0.5,
                 G2=0.4,
                 G11=0.5,
                 G12=0.5,
                 G21=0.8,
                 G22=0.75,
                 G31=0.7,
                 G32=0.5,
                 inherit_W1=False,
                 inherit_P11_or_P22=False,
                 inherit_R1=False,
                 inherit_R2=False,
                 inherit_G1=False,
                 inherit_G2=False,
                 inherit_G11=False,
                 inherit_G12=False,
                 inherit_G21=False,
                 inherit_G22=False,
                 inherit_G31=False,
                 inherit_G32=False,
                 *args,
                 **kwargs):
        super(R1G4Model, self).__init__(R=1, *args, **kwargs)

        with self.data_changed.hold():
            self.W1 = not_none(W1, 0.6)
            self.inherit_W1 = inherit_W1
            self.P11_or_P22 = not_none(P11_or_P22, 0.25)
            self.inherit_P11_or_P22 = inherit_P11_or_P22
            self.R1 = not_none(R1, 0.5)
            self.inherit_R1 = inherit_R1
            self.R2 = not_none(R2, 0.5)
            self.inherit_R2 = inherit_R2
            self.G1 = not_none(G1, 0.5)
            self.inherit_G1 = inherit_G1
            self.G2 = not_none(G2, 0.4)
            self.inherit_G2 = inherit_G2
            self.G11 = not_none(G11, 0.5)
            self.inherit_G11 = inherit_G11
            self.G12 = not_none(G12, 0.5)
            self.inherit_G12 = inherit_G12
            self.G21 = not_none(G21, 0.8)
            self.inherit_G21 = inherit_G21
            self.G22 = not_none(G22, 0.75)
            self.inherit_G22 = inherit_G22
            self.G31 = not_none(G31, 0.7)
            self.inherit_G31 = inherit_G31
            self.G32 = not_none(G32, 0.5)
            self.inherit_G32 = inherit_G32

            self.update()

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def update(self):
        with self.monitor_changes():
            self.mW[0] = self.W1
            self.mW[1] = (1.0 - self.mW[0]) * self.R1
            self.mW[2] = (1.0 - self.mW[0] - self.mW[1]) * self.R2
            self.mW[3] = 1.0 - self.mW[0] - self.mW[1] - self.mW[2]

            W0inv = 1.0 / self.mW[0] if self.mW[0] > 0.0 else 0.0

            if self.mW[0] < 0.5:  # P11 is given
                self.mP[0, 0] = self.P11_or_P22
                Wxx = self.mW[0] * (self.mP[0, 0] -
                                    1) + self.mW[1] + self.mW[2] + self.mW[3]
            else:  # P22 is given
                Wxx = self.P11_or_P22 * (self.mW[1] + self.mW[2] + self.mW[3])

            W1x = Wxx * self.G1  # = W11 + W12 + W13
            Wyx = (Wxx - W1x)  # = W21 + W22 + W23 + W31 + W32 + W33
            W2x = Wyx * self.G2  # = W21 + W22 + W23
            W3x = Wyx - W2x  # = W31 + W32 + W33

            self.mW[1, 1] = self.G11 * W1x
            self.mW[1, 2] = self.G12 * (W1x - self.mW[1, 1])
            self.mW[1, 3] = W1x - self.mW[1, 1] - self.mW[1, 2]

            self.mW[2, 1] = self.G21 * W2x
            self.mW[2, 2] = self.G22 * (W2x - self.mW[2, 1])
            self.mW[2, 3] = W2x - self.mW[2, 1] - self.mW[2, 2]

            self.mW[3, 1] = self.G31 * W3x
            self.mW[3, 2] = self.G32 * (W3x - self.mW[3, 1])
            self.mW[3, 3] = W3x - self.mW[3, 1] - self.mW[3, 2]

            for i in range(1, 4):
                self.mP[i, 0] = 1
                for j in range(1, 4):
                    self.mP[i, j] = self.mW[
                        i, j] / self.mW[i] if self.mW[i] > 0 else 0
                    self.mP[i, 0] -= self.mP[i, j]
                self.mW[i, 0] = self.mW[i] * self.mP[i, 0]

            self.mP[0, 1] = (self.mW[1] - self.mW[1, 1] - self.mW[2, 1] -
                             self.mW[3, 1]) * W0inv
            self.mP[0, 2] = (self.mW[2] - self.mW[1, 2] - self.mW[2, 2] -
                             self.mW[3, 2]) * W0inv
            self.mP[0, 3] = (self.mW[3] - self.mW[1, 3] - self.mW[2, 3] -
                             self.mW[3, 3]) * W0inv

            if self.mW[0] >= 0.5:
                self.mP[0,
                        0] = 1 - self.mP[0, 1] - self.mP[0, 2] - self.mP[0, 3]

            for i in range(4):
                for j in range(4):
                    self.mW[i, j] = self.mW[i] * self.mP[i, j]

            self.solve()
            self.validate()

    pass  # end of class
Exemplo n.º 3
0
class R1G2Model(_AbstractProbability):
    r"""
    Probability model for Reichweite 1 with 2 components.
    
    The 2(=g*(g-1)) independent variables are:
    
    .. math::
        :nowrap:
        
        \begin{flalign*}
            & W_1 \\
            & \text{$P_{11} (W_1 < 0.5)$ or $P_{22} (W_1 > 0.5)$}
        \end{flalign*}
    
    Calculation of the other variables happens as follows:
    
    .. math::
        :nowrap:
        
        \begin{align*}
            & W_2 = 1 – W_1 \\
            & \begin{aligned}
                & \text{$P_{11}$ is given:}  \\
                & \quad P_{12} = 1 - P_{11} \\
                & \quad P_{21} = \frac{W_1 \cdot P_{12}}{W2} \\
                & \quad P_{22} = 1 - P_{21} \\
            \end{aligned}
            \quad \quad
            \begin{aligned}
                & \text{$P_{22}$ is given:} \\
                & \quad P_{21} = 1 - P_{22} \\
                & \quad P_{12} = \frac{W_2 \cdot P_{21}}{W1} \\
                & \quad P_{11} = 1 - P_{12} \\            
            \end{aligned} \\
        \end{align*}
        
    """

    # MODEL METADATA:
    class Meta(_AbstractProbability.Meta):
        store_id = "R1G2Model"

    # PROPERTIES:
    _G = 2

    inherit_W1 = BoolProperty(default=False,
                              text="Inherit flag for W1",
                              persistent=True,
                              visible=True,
                              set_action_name="update",
                              mix_with=(SetActionMixin, ))

    W1 = FloatProperty(default=0.0,
                       text="W1",
                       math_text=r"$W_1$",
                       persistent=True,
                       visible=True,
                       refinable=True,
                       store_private=True,
                       minimum=0.0,
                       maximum=1.0,
                       is_independent=True,
                       inheritable=True,
                       inherit_flag="inherit_W1",
                       inherit_from="parent.based_on.probabilities.W1",
                       set_action_name="update",
                       mix_with=(SetActionMixin, RefinableMixin,
                                 InheritableMixin))

    inherit_P11_or_P22 = BoolProperty(default=False,
                                      text="Inherit flag for P11_or_P22",
                                      persistent=True,
                                      visible=True,
                                      set_action_name="update",
                                      mix_with=(SetActionMixin, ))

    P11_or_P22 = FloatProperty(
        default=0.0,
        text="P11_or_P22",
        math_text=r"$P_{11} %s$ or $\newline P_{22} %s$" %
        (mt_range(0.0, "W_1", 0.5), mt_range(0.5, "W_1", 1.0)),
        persistent=True,
        visible=True,
        refinable=True,
        store_private=True,
        minimum=0.0,
        maximum=1.0,
        is_independent=True,
        inheritable=True,
        inherit_flag="inherit_P11_or_P22",
        inherit_from="parent.based_on.probabilities.P11_or_P22",
        set_action_name="update",
        mix_with=(SetActionMixin, RefinableMixin, InheritableMixin))

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self,
                 W1=0.75,
                 P11_or_P22=0.5,
                 inherit_W1=False,
                 inherit_P11_or_P22=False,
                 *args,
                 **kwargs):
        super(R1G2Model, self).__init__(R=1, *args, **kwargs)

        with self.data_changed.hold():
            self.W1 = not_none(W1, 0.75)
            self.inherit_W1 = inherit_W1
            self.P11_or_P22 = not_none(P11_or_P22, 0.5)
            self.inherit_P11_or_P22 = inherit_P11_or_P22

            self.update()

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def update(self):
        with self.monitor_changes():
            self.mW[0] = self.W1
            self.mW[1] = 1.0 - self.mW[0]
            if self.mW[0] <= 0.5:
                self.mP[0, 0] = self.P11_or_P22
                self.mP[0, 1] = 1.0 - self.mP[0, 0]
                self.mP[1, 0] = self.mW[0] * self.mP[0, 1] / self.mW[1]
                self.mP[1, 1] = 1.0 - self.mP[1, 0]
            else:
                self.mP[1, 1] = self.P11_or_P22
                self.mP[1, 0] = 1.0 - self.mP[1, 1]
                self.mP[0, 1] = self.mW[1] * self.mP[1, 0] / self.mW[0]
                self.mP[0, 0] = 1.0 - self.mP[0, 1]

            self.solve()
            self.validate()

    pass  # end of class
Exemplo n.º 4
0
class R1G3Model(_AbstractProbability):
    r"""
    Probability model for Reichweite 1 with 3 components.
    
    The 6 (=g*(g-1)) independent variables are:
    
    .. math::
        :nowrap:
        
        \begin{align*}
                & W_1
                & \text{$P_{11} (W_1 < 0.5)$ or $P_{xx} (W_1 > 0.5)$}
                with P_{xx} = \frac {W_{22} + W_{23} + W_{32} + W_{33} + W_{42} + W_{43}}{W_2 + W_3} \\
                & G_1 = \frac{W_2}{W_2 + W_3}
                & G_2 = \frac{W_{22} + W_{23}}{W_{22} + W_{23} + W_{32} + W_{33}} \\
                & G_3 = \frac{W_{22}}{W_{22} + W_{23}}
                & G_4 = \frac{W_{32}}{W_{32} + W_{33}} \\
        \end{align*}
            
    Calculation of the other variables happens as follows:
    
    
    
    .. math::
        :nowrap:
        
        \begin{align*}
            & \text{Calculate the 'inverted' ratios of $G_2$, $G_3$ and $G_4$ as follows:} \\
            & \quad G_i^{\text{-1}} =
            \begin{cases}
                G_i^{-1} - 1.0, & \text{if } G_i > 0 \\
                0,              & \text{otherwise}
            \end{cases} \quad \forall i \in \left\{ {2, 3, 4}\right\} \\
            & \\
            & \text{Calculate the base weight fractions of each component:} \\
            & \quad W_2 = (1 - W_1) \cdot G_1\\
            & \quad W_3 = 1.0 - W_1 - W_2 \\
            & \\
            & \text{if $W_1 \leq 0.5$:} \\
            & \quad \text{$P_{11}$ is given and W_xx is derived as} \\
            & \quad W_{xx} = W_{22} + W_{23} + W_{32} + W_{23} = W_1 \cdot (1 - P_{11}) + W_2 + W_3 \\ 
            & \\
            & \text{if $W_1 > 0.5$:} \\
            & \quad \text{$P_{xx}$ is given and $P_{11}$ is derived further down} \\
            & \quad W_{xx} = W_{22} + W_{23} + W_{32} + W_{23} = P_{xx} \cdot (W_2 + W_3) \\
            & \\
            & W_{22} = W_{xx} \cdot G_2 \cdot G_3 \\
            & W_{23} = W_{22} \cdot G_3^{-1} \\
            & W_{32} = W_{xx} \cdot (1 - G_2) \\
            & W_{33} = G_4^{-1} \cdot W_{32} \\
            & \\
            & P_{23} = 
            \begin{dcases}
                \dfrac{W_{23}}{W_2}, & \text{if $W_2 > 0$} \\
                0, & \text{otherwise}
            \end{dcases} \\
            & P_{12} = 1 - P_{22} - P_{23} \\
            & \\
            & P_{32} =
            \begin{dcases}
                \frac{W_{32}}{W_3}, & \text{if $W_3 > 0$} \\
                0, & \text{otherwise}
            \end{dcases} \\
            & P_{33} =
            \begin{dcases}
                \frac{W_{33}}{W_3}, & \text{if $W_3 > 0$} \\
                0, & \text{otherwise}
            \end{dcases} \\
            & P_{31} = 1 - P_{32} - P_{33} \\
            & \\
            & P_{12} =
            \begin{dcases}
                \frac{W_2 - W_{22} - W_{32}}{W_1}, & \text{if $W_1 > 0$} \\
                0, & \text{otherwise}
            \end{dcases} \\
            & P_{13} =
            \begin{dcases}
                \frac{W_3 - W_{23} - W_{33}}{W_1}, & \text{if $W_1 > 0$} \\
                0, & \text{otherwise}
            \end{dcases} \\
            & \\
            & \text{if $W_1 > 0.5$}: \\
            & \quad P_{11} = 1 - P_{12} - P_{13} \\
            & \\
            & \text{Remainder of weight fraction can be calculated as follows:} \\
            & \quad W_{ij} = {W_{ii}} \cdot {P_{ij}} \quad \forall {i,j} \in \left[ {1, 3} \right] \\
        \end{align*}
        
    """

    # MODEL METADATA:
    class Meta(_AbstractProbability.Meta):
        store_id = "R1G3Model"

    # PROPERTIES
    _G = 3

    inherit_W1 = BoolProperty(default=False,
                              text="Inherit flag for W1",
                              persistent=True,
                              visible=True,
                              set_action_name="update",
                              mix_with=(SetActionMixin, ))
    W1 = FloatProperty(default=0.8,
                       text="W1",
                       math_text=r"$W_1$",
                       persistent=True,
                       visible=True,
                       refinable=True,
                       store_private=True,
                       minimum=0.0,
                       maximum=1.0,
                       is_independent=True,
                       inheritable=True,
                       inherit_flag="inherit_W1",
                       inherit_from="parent.based_on.probabilities.W1",
                       set_action_name="update",
                       mix_with=(SetActionMixin, RefinableMixin,
                                 InheritableMixin))

    inherit_P11_or_P22 = BoolProperty(default=False,
                                      text="Inherit flag for P11_or_P22",
                                      persistent=True,
                                      visible=True,
                                      set_action_name="update",
                                      mix_with=(SetActionMixin, ))
    P11_or_P22 = FloatProperty(
        default=0.7,
        text="P11_or_P22",
        math_text=r"$P_{11} %s$ or $\newline P_{22} %s$" %
        (mt_range(0.0, "W_1", 0.5), mt_range(0.5, "W_1", 1.0)),
        persistent=True,
        visible=True,
        refinable=True,
        store_private=True,
        minimum=0.0,
        maximum=1.0,
        is_independent=True,
        inheritable=True,
        inherit_flag="inherit_P11_or_P22",
        inherit_from="parent.based_on.probabilities.P11_or_P22",
        set_action_name="update",
        mix_with=(SetActionMixin, RefinableMixin, InheritableMixin))

    inherit_G1 = BoolProperty(default=False,
                              text="Inherit flag for G1",
                              persistent=True,
                              visible=True,
                              set_action_name="update",
                              mix_with=(SetActionMixin, ))
    G1 = FloatProperty(default=0.7,
                       text="W2/(W2+W3)",
                       math_text=r"$\large\frac{W_2}{W_3 + W_2}$",
                       persistent=True,
                       visible=True,
                       refinable=True,
                       store_private=True,
                       minimum=0.0,
                       maximum=1.0,
                       is_independent=True,
                       inheritable=True,
                       inherit_flag="inherit_G1",
                       inherit_from="parent.based_on.probabilities.G1",
                       set_action_name="update",
                       mix_with=(SetActionMixin, RefinableMixin,
                                 InheritableMixin))

    inherit_G2 = BoolProperty(default=False,
                              text="Inherit flag for G2",
                              persistent=True,
                              visible=True,
                              set_action_name="update",
                              mix_with=(SetActionMixin, ))
    G2 = FloatProperty(
        default=0.7,
        text="(W22+W23)/(W22+W23+W32+W33)",
        math_text=
        r"$\large\frac{W_{22} + W_{23}}{W_{22} + W_{23} + W_{32} + W_{33}}$",
        persistent=True,
        visible=True,
        refinable=True,
        store_private=True,
        minimum=0.0,
        maximum=1.0,
        is_independent=True,
        inheritable=True,
        inherit_flag="inherit_G2",
        inherit_from="parent.based_on.probabilities.G2",
        set_action_name="update",
        mix_with=(SetActionMixin, RefinableMixin, InheritableMixin))

    inherit_G3 = BoolProperty(default=False,
                              text="Inherit flag for G3",
                              persistent=True,
                              visible=True,
                              set_action_name="update",
                              mix_with=(SetActionMixin, ))
    G3 = FloatProperty(default=0.7,
                       text="W22/(W22+W23)",
                       math_text=r"$\large\frac{W_{22}}{W_{22} + W_{23}}$",
                       persistent=True,
                       visible=True,
                       refinable=True,
                       store_private=True,
                       minimum=0.0,
                       maximum=1.0,
                       is_independent=True,
                       inheritable=True,
                       inherit_flag="inherit_G3",
                       inherit_from="parent.based_on.probabilities.G3",
                       set_action_name="update",
                       mix_with=(SetActionMixin, RefinableMixin,
                                 InheritableMixin))

    inherit_G4 = BoolProperty(default=False,
                              text="Inherit flag for G4",
                              persistent=True,
                              visible=True,
                              set_action_name="update",
                              mix_with=(SetActionMixin, ))
    G4 = FloatProperty(default=0.7,
                       text="W23/(W32+W33)",
                       math_text=r"$\large\frac{W_{22}}{W_{22} + W_{23}}$",
                       persistent=True,
                       visible=True,
                       refinable=True,
                       store_private=True,
                       minimum=0.0,
                       maximum=1.0,
                       is_independent=True,
                       inheritable=True,
                       inherit_flag="inherit_G4",
                       inherit_from="parent.based_on.probabilities.G4",
                       set_action_name="update",
                       mix_with=(SetActionMixin, RefinableMixin,
                                 InheritableMixin))

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self,
                 W1=0.8,
                 P11_or_P22=0.7,
                 G1=0.7,
                 G2=0.7,
                 G3=0.7,
                 G4=0.7,
                 inherit_W1=False,
                 inherit_P11_or_P22=False,
                 inherit_G1=False,
                 inherit_G2=False,
                 inherit_G3=False,
                 inherit_G4=False,
                 *args,
                 **kwargs):
        super(R1G3Model, self).__init__(R=1, *args, **kwargs)

        with self.data_changed.hold():
            self.W1 = not_none(W1, 0.8)
            self.inherit_W1 = bool(inherit_W1)
            self.P11_or_P22 = not_none(P11_or_P22, 0.7)
            self.inherit_P11_or_P22 = bool(inherit_P11_or_P22)
            self.G1 = not_none(G1, 0.7)
            self.inherit_G1 = bool(inherit_G1)
            self.G2 = not_none(G2, 0.7)
            self.inherit_G2 = bool(inherit_G2)
            self.G3 = not_none(G3, 0.7)
            self.inherit_G3 = bool(inherit_G3)
            self.G4 = not_none(G4, 0.7)
            self.inherit_G4 = bool(inherit_G4)

            self.update()

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def update(self):
        with self.monitor_changes():
            self.mW[0] = self.W1
            self.mW[1] = (1 - self.mW[0]) * self.G1
            self.mW[2] = 1.0 - self.mW[0] - self.mW[1]

            W0inv = 1.0 / self.mW[0] if self.mW[0] > 0.0 else 0.0

            Wxx = 0
            if self.mW[0] <= 0.5:  # P00 given
                self.mP[0, 0] = self.P11_or_P22
                # Wxx = W11 + W12 + W21 + W22
                Wxx = self.mW[0] * (self.mP[0, 0] -
                                    1) + self.mW[1] + self.mW[2]
            else:  # Pxx given
                # Wxx = W11 + W12 + W21 + W22
                Wxx = (1.0 - self.mW[0]) * self.P11_or_P22

            # W11 + W12 = Wxx * G2:
            self.mW[1, 1] = Wxx * self.G2 * self.G3
            self.mW[1, 2] = Wxx * self.G2 * (1 - self.G3)
            self.mP[1,
                    1] = self.mW[1,
                                 1] / self.mW[1] if self.mW[1] > 0.0 else 0.0

            self.mW[2, 1] = Wxx * (1 - self.G2) * self.G4
            self.mW[2, 2] = Wxx * (1 - self.G2) * (1 - self.G4)

            self.mP[1, 2] = (self.mW[1, 2] /
                             self.mW[1]) if self.mW[1] > 0.0 else 0.0
            self.mP[1, 0] = 1 - self.mP[1, 1] - self.mP[1, 2]

            self.mP[2, 1] = (self.mW[2, 1] /
                             self.mW[2]) if self.mW[2] > 0.0 else 0.0
            self.mP[2, 2] = (self.mW[2, 2] /
                             self.mW[2]) if self.mW[2] > 0.0 else 0.0
            self.mP[2, 0] = 1 - self.mP[2, 1] - self.mP[2, 2]

            self.mP[0,
                    1] = (self.mW[1] - self.mW[1, 1] - self.mW[2, 1]) * W0inv
            self.mP[0,
                    2] = (self.mW[2] - self.mW[1, 2] - self.mW[2, 2]) * W0inv

            if self.mW[0] > 0.5:
                self.mP[0, 0] = 1 - self.mP[0, 1] - self.mP[0, 2]

            for i in range(3):
                for j in range(3):
                    self.mW[i, j] = self.mW[i, i] * self.mP[i, j]

            self.solve()
            self.validate()

    pass  # end of class
Exemplo n.º 5
0
class Specimen(DataModel, Storable):
    # MODEL INTEL:
    class Meta(DataModel.Meta):
        store_id = "Specimen"

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

    _data_object = None

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

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

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

    # PROPERTIES:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        self._data_object = SpecimenData()

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

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

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

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

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

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

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

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

                self.statistics = Statistics(parent=self)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    pass  # end of class
Exemplo n.º 6
0
class Marker(DataModel, Storable, CSVMixin):

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

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

    # PROPERTIES:

    #: This marker's label
    label = StringProperty(default="New Marker",
                           text="Label",
                           persistent=True,
                           visible=True,
                           tabular=True,
                           signal_name="visuals_changed",
                           mix_with=(SignalMixin, ))

    #: Flag indicating whether the color of this marker is inherited
    inherit_color = BoolProperty(default=settings.MARKER_INHERIT_COLOR,
                                 text="Inherit color",
                                 persistent=True,
                                 visible=True,
                                 tabular=True,
                                 signal_name="visuals_changed",
                                 mix_with=(SignalMixin, ))
    #: This maker's color:
    color = ColorProperty(default=settings.MARKER_COLOR,
                          text="Color",
                          persistent=True,
                          visible=True,
                          tabular=True,
                          inheritable=True,
                          signal_name="visuals_changed",
                          inherit_flag="inherit_color",
                          inherit_from="specimen.project.display_marker_color",
                          mix_with=(
                              InheritableMixin,
                              SignalMixin,
                          ))

    #: Flag indicating whether the angle of this marker is inherited
    inherit_angle = BoolProperty(default=settings.MARKER_INHERIT_ANGLE,
                                 text="Inherit angle",
                                 persistent=True,
                                 visible=True,
                                 tabular=True,
                                 signal_name="visuals_changed",
                                 mix_with=(SignalMixin, ))
    #: This maker's angle:
    angle = FloatProperty(default=settings.MARKER_ANGLE,
                          text="Angle",
                          widget_type="spin",
                          persistent=True,
                          visible=True,
                          tabular=True,
                          inheritable=True,
                          signal_name="visuals_changed",
                          inherit_flag="inherit_angle",
                          inherit_from="specimen.project.display_marker_angle",
                          mix_with=(
                              InheritableMixin,
                              SignalMixin,
                          ))

    #: Flag indicating whether the top offset of this marker is inherited
    inherit_top_offset = BoolProperty(
        default=settings.MARKER_INHERIT_TOP_OFFSET,
        text="Inherit top offset",
        persistent=True,
        visible=True,
        tabular=True,
        signal_name="visuals_changed",
        mix_with=(SignalMixin, ))
    #: This maker's top offset:
    top_offset = FloatProperty(
        default=settings.MARKER_TOP_OFFSET,
        text="Top offset",
        widget_type="spin",
        persistent=True,
        visible=True,
        tabular=True,
        inheritable=True,
        signal_name="visuals_changed",
        inherit_flag="inherit_top_offset",
        inherit_from="specimen.project.display_marker_top_offset",
        mix_with=(
            InheritableMixin,
            SignalMixin,
        ))

    #: Whether this marker is visible
    visible = BoolProperty(default=settings.MARKER_VISIBLE,
                           text="Visible",
                           persistent=True,
                           visible=True,
                           tabular=True,
                           signal_name="visuals_changed",
                           mix_with=(SignalMixin, ))

    #: The marker's position
    position = FloatProperty(default=settings.MARKER_POSITION,
                             text="Position",
                             widget_type="spin",
                             persistent=True,
                             visible=True,
                             tabular=True,
                             signal_name="visuals_changed",
                             mix_with=(SignalMixin, ))

    #: The marker's x offset
    x_offset = FloatProperty(default=settings.MARKER_X_OFFSET,
                             text="X offset",
                             widget_type="spin",
                             persistent=True,
                             visible=True,
                             tabular=True,
                             signal_name="visuals_changed",
                             mix_with=(SignalMixin, ))

    #: The marker's y offset
    y_offset = FloatProperty(default=settings.MARKER_Y_OFFSET,
                             text="Y offset",
                             widget_type="spin",
                             persistent=True,
                             visible=True,
                             tabular=True,
                             signal_name="visuals_changed",
                             mix_with=(SignalMixin, ))

    #: Flag indicating whether the alignment of this marker is inherited
    inherit_align = BoolProperty(default=settings.MARKER_INHERIT_ALIGN,
                                 text="Inherit align",
                                 persistent=True,
                                 visible=True,
                                 tabular=True,
                                 signal_name="visuals_changed",
                                 mix_with=(SignalMixin, ))
    #: This marker's alignment
    align = StringChoiceProperty(
        default=settings.MARKER_ALIGN,
        text="Align",
        choices=settings.MARKER_ALIGNS,
        persistent=True,
        visible=True,
        tabular=True,
        inheritable=True,
        signal_name="visuals_changed",
        inherit_flag="inherit_align",
        inherit_from="specimen.project.display_marker_align",
        mix_with=(
            InheritableMixin,
            SignalMixin,
        ))

    #: Flag indicating whether the base of this marker is inherited
    inherit_base = BoolProperty(default=settings.MARKER_INHERIT_BASE,
                                text="Inherit base",
                                persistent=True,
                                visible=True,
                                tabular=True,
                                signal_name="visuals_changed",
                                mix_with=(SignalMixin, ))
    #: This marker's base
    base = IntegerChoiceProperty(
        default=settings.MARKER_BASE,
        text="Base",
        choices=settings.MARKER_BASES,
        persistent=True,
        visible=True,
        tabular=True,
        inheritable=True,
        signal_name="visuals_changed",
        inherit_flag="inherit_base",
        inherit_from="specimen.project.display_marker_base",
        mix_with=(
            InheritableMixin,
            SignalMixin,
        ))

    #: Flag indicating whether the top of this marker is inherited
    inherit_top = BoolProperty(default=settings.MARKER_INHERIT_TOP,
                               text="Inherit top",
                               persistent=True,
                               visible=True,
                               tabular=True,
                               signal_name="visuals_changed",
                               mix_with=(SignalMixin, ))
    #: This marker's top
    top = IntegerChoiceProperty(
        default=settings.MARKER_TOP,
        text="Top",
        choices=settings.MARKER_TOPS,
        persistent=True,
        visible=True,
        tabular=True,
        inheritable=True,
        signal_name="visuals_changed",
        inherit_flag="inherit_top",
        inherit_from="specimen.project.display_marker_top",
        mix_with=(
            InheritableMixin,
            SignalMixin,
        ))

    #: Flag indicating whether the line style of this marker is inherited
    inherit_style = BoolProperty(default=settings.MARKER_INHERIT_STYLE,
                                 text="Inherit line style",
                                 persistent=True,
                                 visible=True,
                                 tabular=True,
                                 signal_name="visuals_changed",
                                 mix_with=(SignalMixin, ))
    #: This marker's line style
    style = StringChoiceProperty(
        default=settings.MARKER_STYLE,
        text="Line style",
        choices=settings.MARKER_STYLES,
        persistent=True,
        visible=True,
        tabular=True,
        inheritable=True,
        signal_name="visuals_changed",
        inherit_flag="inherit_style",
        inherit_from="specimen.project.display_marker_style",
        mix_with=(
            InheritableMixin,
            SignalMixin,
        ))

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

        my_kwargs = self.pop_kwargs(
            kwargs, "data_label", "data_visible", "data_position",
            "data_x_offset", "data_y_offset"
            "data_color", "data_base", "data_angle", "data_align", *[
                prop.label
                for prop in type(self).Meta.get_local_persistent_properties()
            ])
        super(Marker, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        self.label = self.get_kwarg(kwargs, "", "label", "data_label")
        self.visible = self.get_kwarg(kwargs, True, "visible", "data_visible")
        self.position = float(
            self.get_kwarg(kwargs, 0.0, "position", "data_position"))
        self.x_offset = float(
            self.get_kwarg(kwargs, 0.0, "x_offset", "data_x_offset"))
        self.y_offset = float(
            self.get_kwarg(kwargs, 0.05, "y_offset", "data_y_offset"))
        self.top_offset = float(self.get_kwarg(kwargs, 0.0, "top_offset"))
        self.color = self.get_kwarg(kwargs, settings.MARKER_COLOR, "color",
                                    "data_color")
        self.base = int(
            self.get_kwarg(kwargs, settings.MARKER_BASE, "base", "data_base"))
        self.angle = float(self.get_kwarg(kwargs, 0.0, "angle", "data_angle"))
        self.align = self.get_kwarg(kwargs, settings.MARKER_ALIGN, "align")
        self.style = self.get_kwarg(kwargs, settings.MARKER_STYLE, "style",
                                    "data_align")

        # if top is not set and style is not "none",
        # assume top to be "Top of plot", otherwise (style is not "none")
        # assume top to be relative to the base point (using top_offset)
        self.top = int(
            self.get_kwarg(kwargs, 0 if self.style == "none" else 1, "top"))

        self.inherit_align = self.get_kwarg(kwargs, True, "inherit_align")
        self.inherit_color = self.get_kwarg(kwargs, True, "inherit_color")
        self.inherit_base = self.get_kwarg(kwargs, True, "inherit_base")
        self.inherit_top = self.get_kwarg(kwargs, True, "inherit_top")
        self.inherit_top_offset = self.get_kwarg(kwargs, True,
                                                 "inherit_top_offset")
        self.inherit_angle = self.get_kwarg(kwargs, True, "inherit_angle")
        self.inherit_style = self.get_kwarg(kwargs, True, "inherit_style")

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def get_nm_position(self):
        if self.parent is not None:
            return self.parent.goniometer.get_nm_from_2t(self.position)
        else:
            return 0.0

    def set_nm_position(self, position):
        if self.parent is not None:
            self.position = self.parent.goniometer.get_2t_from_nm(position)
        else:
            self.position = 0.0

    pass  # end of class
Exemplo n.º 7
0
class Project(DataModel, Storable):
    """
    This is the top-level object that servers the purpose of combining the
    different objects (most notably :class:`~.atoms.models.AtomType`'s,
    :class:`~.phases.models.Phase`'s, :class:`~.specimen.models.Specimen`'s and
    :class:`~.mixture.models.Mixture`'s).
    
    It also provides a large number of display-related 'default' properties 
    (e.g. for patterns and their markers, axes etc.). For more details: see the
    property descriptions.
    
    Example usage:
    
    .. code-block:: python
    
        >>> from pyxrd.project.models import Project
        >>> from pyxrd.generic.io.xrd_parsers import XRDParser
        >>> from pyxrd.specimen.models import Specimen
        >>> project = Project(name="New Project", author="Mr. X", layout_mode="FULL", axes_dspacing=True)
        >>> for specimen in Specimen.from_experimental_data("/path/to/xrd_data_file.rd", parent=project):
        ...   project.specimens.append(specimen)
        ...
        
    """

    # MODEL INTEL:
    class Meta(DataModel.Meta):
        store_id = "Project"
        file_filters = [
            ("PyXRD Project files",
             get_case_insensitive_glob("*.pyxrd", "*.zpd")),
        ]
        import_filters = [
            ("Sybilla XML files", get_case_insensitive_glob("*.xml")),
        ]

    # PROPERTIES:

    filename = None

    #: The project name
    name = StringProperty(default="",
                          text="Name",
                          visible=True,
                          persistent=True)

    #: The project data (string)
    date = StringProperty(default="",
                          text="Date",
                          visible=True,
                          persistent=True)

    #: The project description
    description = StringProperty(
        default=None,
        text="Description",
        visible=True,
        persistent=True,
        widget_type="text_view",
    )

    #: The project author
    author = StringProperty(default="",
                            text="Author",
                            visible=True,
                            persistent=True)

    #: Flag indicating whether this project has been changed since it was last saved.
    needs_saving = BoolProperty(default=True, visible=False, persistent=False)

    #: The layout mode this project should be displayed in
    layout_mode = StringChoiceProperty(default=settings.DEFAULT_LAYOUT,
                                       text="Layout mode",
                                       visible=True,
                                       persistent=True,
                                       choices=settings.DEFAULT_LAYOUTS,
                                       mix_with=(SignalMixin, ),
                                       signal_name="visuals_changed")

    #: The manual lower limit for the X-axis
    axes_xmin = FloatProperty(default=settings.AXES_MANUAL_XMIN,
                              text="min. [°2T]",
                              visible=True,
                              persistent=True,
                              minimum=0.0,
                              widget_type="spin",
                              mix_with=(SignalMixin, ),
                              signal_name="visuals_changed")

    #: The manual upper limit for the X-axis
    axes_xmax = FloatProperty(default=settings.AXES_MANUAL_XMAX,
                              text="max. [°2T]",
                              visible=True,
                              persistent=True,
                              minimum=0.0,
                              widget_type="spin",
                              mix_with=(SignalMixin, ),
                              signal_name="visuals_changed")

    #: Whether or not to stretch the X-axis over the entire available display
    axes_xstretch = BoolProperty(default=settings.AXES_XSTRETCH,
                                 text="Stetch x-axis to fit window",
                                 visible=True,
                                 persistent=True,
                                 mix_with=(SignalMixin, ),
                                 signal_name="visuals_changed")

    #: Flag toggling between d-spacing (when True) or 2-Theta axes (when False)
    axes_dspacing = BoolProperty(default=settings.AXES_DSPACING,
                                 text="Show d-spacing in x-axis",
                                 visible=True,
                                 persistent=True,
                                 mix_with=(SignalMixin, ),
                                 signal_name="visuals_changed")

    #: Whether or not the y-axis should be shown
    axes_yvisible = BoolProperty(default=settings.AXES_YVISIBLE,
                                 text="Y-axis visible",
                                 visible=True,
                                 persistent=True,
                                 mix_with=(SignalMixin, ),
                                 signal_name="visuals_changed")

    #: The manual lower limit for the Y-axis (in counts)
    axes_ymin = FloatProperty(default=settings.AXES_MANUAL_YMIN,
                              text="min. [counts]",
                              visible=True,
                              persistent=True,
                              minimum=0.0,
                              widget_type="spin",
                              mix_with=(SignalMixin, ),
                              signal_name="visuals_changed")

    #: The manual upper limit for the Y-axis (in counts)
    axes_ymax = FloatProperty(default=settings.AXES_MANUAL_YMAX,
                              text="max. [counts]",
                              visible=True,
                              persistent=True,
                              minimum=0.0,
                              widget_type="spin",
                              mix_with=(SignalMixin, ),
                              signal_name="visuals_changed")

    #: What type of y-axis to use: raw counts, single or multi-normalized units
    axes_ynormalize = IntegerChoiceProperty(default=settings.AXES_YNORMALIZE,
                                            text="Y scaling",
                                            visible=True,
                                            persistent=True,
                                            choices=settings.AXES_YNORMALIZERS,
                                            mix_with=(SignalMixin, ),
                                            signal_name="visuals_changed")

    #: Whether to use automatic or manual Y limits
    axes_ylimit = IntegerChoiceProperty(default=settings.AXES_YLIMIT,
                                        text="Y limit",
                                        visible=True,
                                        persistent=True,
                                        choices=settings.AXES_YLIMITS,
                                        mix_with=(SignalMixin, ),
                                        signal_name="visuals_changed")

    #: The offset between patterns as a fraction of the maximum intensity
    display_plot_offset = FloatProperty(default=settings.PLOT_OFFSET,
                                        text="Pattern offset",
                                        visible=True,
                                        persistent=True,
                                        minimum=0.0,
                                        widget_type="float_entry",
                                        mix_with=(SignalMixin, ),
                                        signal_name="visuals_changed")

    #: The number of patterns to group ( = having no offset)
    display_group_by = IntegerProperty(default=settings.PATTERN_GROUP_BY,
                                       text="Group patterns by",
                                       visible=True,
                                       persistent=True,
                                       minimum=1,
                                       widget_type="spin",
                                       mix_with=(SignalMixin, ),
                                       signal_name="visuals_changed")

    #: The relative position (from the pattern offset) for pattern labels
    #: as a fraction of the patterns intensity
    display_label_pos = FloatProperty(default=settings.LABEL_POSITION,
                                      text="Default label position",
                                      visible=True,
                                      persistent=True,
                                      widget_type="float_entry",
                                      mix_with=(SignalMixin, ),
                                      signal_name="visuals_changed")

    #: What type of scale to use for X-axis, automatic or manual
    axes_xlimit = IntegerChoiceProperty(default=settings.AXES_XLIMIT,
                                        text="X limit",
                                        visible=True,
                                        persistent=True,
                                        choices=settings.AXES_XLIMITS,
                                        mix_with=(SignalMixin, ),
                                        signal_name="visuals_changed")

    #: The default angle at which marker labels are displayed
    display_marker_angle = FloatProperty(default=settings.MARKER_ANGLE,
                                         text="Angle",
                                         visible=True,
                                         persistent=True,
                                         widget_type="float_entry",
                                         mix_with=(SignalMixin, ),
                                         signal_name="visuals_changed")

    #: The default offset for marker labels
    display_marker_top_offset = FloatProperty(
        default=settings.MARKER_TOP_OFFSET,
        text="Offset from base",
        visible=True,
        persistent=True,
        widget_type="float_entry",
        mix_with=(SignalMixin, ),
        signal_name="visuals_changed")

    #: The default marker label alignment (one of settings.MARKER_ALIGNS)
    display_marker_align = StringChoiceProperty(default=settings.MARKER_ALIGN,
                                                text="Label alignment",
                                                visible=True,
                                                persistent=True,
                                                choices=settings.MARKER_ALIGNS,
                                                mix_with=(SignalMixin, ),
                                                signal_name="visuals_changed")

    #: The default marker label base (one of settings.MARKER_BASES)
    display_marker_base = IntegerChoiceProperty(default=settings.MARKER_BASE,
                                                text="Base connection",
                                                visible=True,
                                                persistent=True,
                                                choices=settings.MARKER_BASES,
                                                mix_with=(SignalMixin, ),
                                                signal_name="visuals_changed")

    #: The default marker label top (one of settings.MARKER_TOPS)
    display_marker_top = IntegerChoiceProperty(default=settings.MARKER_TOP,
                                               text="Top connection",
                                               visible=True,
                                               persistent=True,
                                               choices=settings.MARKER_TOPS,
                                               mix_with=(SignalMixin, ),
                                               signal_name="visuals_changed")

    #: The default marker style (one of settings.MARKER_STYLES)
    display_marker_style = StringChoiceProperty(default=settings.MARKER_STYLE,
                                                text="Line style",
                                                visible=True,
                                                persistent=True,
                                                choices=settings.MARKER_STYLES,
                                                mix_with=(SignalMixin, ),
                                                signal_name="visuals_changed")

    #: The default marker color
    display_marker_color = StringProperty(default=settings.MARKER_COLOR,
                                          text="Color",
                                          visible=True,
                                          persistent=True,
                                          widget_type="color",
                                          mix_with=(SignalMixin, ),
                                          signal_name="visuals_changed")

    #: The default calculated profile color
    display_calc_color = StringProperty(default=settings.CALCULATED_COLOR,
                                        text="Calculated color",
                                        visible=True,
                                        persistent=True,
                                        widget_type="color",
                                        mix_with=(SignalMixin, ),
                                        signal_name="visuals_changed")

    #: The default experimental profile color
    display_exp_color = StringProperty(default=settings.EXPERIMENTAL_COLOR,
                                       text="Experimental color",
                                       visible=True,
                                       persistent=True,
                                       widget_type="color",
                                       mix_with=(SignalMixin, ),
                                       signal_name="visuals_changed")

    #: The default calculated profile line width
    display_calc_lw = IntegerProperty(default=settings.CALCULATED_LINEWIDTH,
                                      text="Calculated line width",
                                      visible=True,
                                      persistent=True,
                                      widget_type="spin",
                                      mix_with=(SignalMixin, ),
                                      signal_name="visuals_changed")

    #: The default experimental profile line width
    display_exp_lw = IntegerProperty(default=settings.EXPERIMENTAL_LINEWIDTH,
                                     text="Experimental line width",
                                     visible=True,
                                     persistent=True,
                                     widget_type="spin",
                                     mix_with=(SignalMixin, ),
                                     signal_name="visuals_changed")

    #: The default calculated profile line style
    display_calc_ls = StringChoiceProperty(
        default=settings.CALCULATED_LINESTYLE,
        text="Calculated line style",
        visible=True,
        persistent=True,
        choices=settings.PATTERN_LINE_STYLES,
        mix_with=(SignalMixin, ),
        signal_name="visuals_changed")

    #: The default experimental profile line style
    display_exp_ls = StringChoiceProperty(
        default=settings.EXPERIMENTAL_LINESTYLE,
        text="Experimental line style",
        visible=True,
        persistent=True,
        choices=settings.PATTERN_LINE_STYLES,
        mix_with=(SignalMixin, ),
        signal_name="visuals_changed")

    #: The default calculated profile line style
    display_calc_marker = StringChoiceProperty(
        default=settings.CALCULATED_MARKER,
        text="Calculated line marker",
        visible=True,
        persistent=True,
        choices=settings.PATTERN_MARKERS,
        mix_with=(SignalMixin, ),
        signal_name="visuals_changed")

    #: The default calculated profile line style
    display_exp_marker = StringChoiceProperty(
        default=settings.EXPERIMENTAL_MARKER,
        text="Experimental line marker",
        visible=True,
        persistent=True,
        choices=settings.PATTERN_MARKERS,
        mix_with=(SignalMixin, ),
        signal_name="visuals_changed")

    #: The list of specimens
    specimens = ListProperty(
        default=[],
        text="Specimens",
        data_type=Specimen,
        visible=True,
        persistent=True,
    )

    #: The list of phases
    phases = ListProperty(default=[],
                          text="Phases",
                          data_type=Phase,
                          visible=False,
                          persistent=True)

    #: The list of atom types
    atom_types = ListProperty(default=[],
                              text="Atom types",
                              data_type=AtomType,
                              visible=False,
                              persistent=True)

    #: The list of Behaviours
    #behaviours = ListProperty(
    #    default=[], text="Behaviours", data_type=InSituBehaviour,
    #    visible=False, persistent=True
    #)

    #: The list of mixtures
    mixtures = ListProperty(default=[],
                            text="Mixture",
                            data_type=Mixture,
                            visible=False,
                            persistent=True)

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        """
            Constructor takes any of its properties as a keyword argument 
            except for:
                - needs_saving
            
            In addition to the above, the constructor still supports the 
            following deprecated keywords, mapping to a current keyword:
                - goniometer: the project-level goniometer, is passed on to the
                  specimens
                - axes_xscale: deprecated alias for axes_xlimit
                - axes_yscale: deprecated alias for axes_ynormalize
                
            Any other arguments or keywords are passed to the base class.
        """
        my_kwargs = self.pop_kwargs(
            kwargs, "goniometer", "data_goniometer", "data_atom_types",
            "data_phases", "axes_yscale", "axes_xscale", "filename",
            "behaviours", *[
                prop.label
                for prop in Project.Meta.get_local_persistent_properties()
            ])
        super(Project, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        with self.data_changed.hold():
            with self.visuals_changed.hold():

                self.filename = self.get_kwarg(kwargs, self.filename,
                                               "filename")
                self.layout_mode = self.get_kwarg(kwargs, self.layout_mode,
                                                  "layout_mode")

                self.display_marker_align = self.get_kwarg(
                    kwargs, self.display_marker_align, "display_marker_align")
                self.display_marker_color = self.get_kwarg(
                    kwargs, self.display_marker_color, "display_marker_color")
                self.display_marker_base = self.get_kwarg(
                    kwargs, self.display_marker_base, "display_marker_base")
                self.display_marker_top = self.get_kwarg(
                    kwargs, self.display_marker_top, "display_marker_top")
                self.display_marker_top_offset = self.get_kwarg(
                    kwargs, self.display_marker_top_offset,
                    "display_marker_top_offset")
                self.display_marker_angle = self.get_kwarg(
                    kwargs, self.display_marker_angle, "display_marker_angle")
                self.display_marker_style = self.get_kwarg(
                    kwargs, self.display_marker_style, "display_marker_style")

                self.display_calc_color = self.get_kwarg(
                    kwargs, self.display_calc_color, "display_calc_color")
                self.display_exp_color = self.get_kwarg(
                    kwargs, self.display_exp_color, "display_exp_color")
                self.display_calc_lw = self.get_kwarg(kwargs,
                                                      self.display_calc_lw,
                                                      "display_calc_lw")
                self.display_exp_lw = self.get_kwarg(kwargs,
                                                     self.display_exp_lw,
                                                     "display_exp_lw")
                self.display_calc_ls = self.get_kwarg(kwargs,
                                                      self.display_calc_ls,
                                                      "display_calc_ls")
                self.display_exp_ls = self.get_kwarg(kwargs,
                                                     self.display_exp_ls,
                                                     "display_exp_ls")
                self.display_calc_marker = self.get_kwarg(
                    kwargs, self.display_calc_marker, "display_calc_marker")
                self.display_exp_marker = self.get_kwarg(
                    kwargs, self.display_exp_marker, "display_exp_marker")
                self.display_plot_offset = self.get_kwarg(
                    kwargs, self.display_plot_offset, "display_plot_offset")
                self.display_group_by = self.get_kwarg(kwargs,
                                                       self.display_group_by,
                                                       "display_group_by")
                self.display_label_pos = self.get_kwarg(
                    kwargs, self.display_label_pos, "display_label_pos")

                self.axes_xlimit = self.get_kwarg(kwargs, self.axes_xlimit,
                                                  "axes_xlimit", "axes_xscale")
                self.axes_xmin = self.get_kwarg(kwargs, self.axes_xmin,
                                                "axes_xmin")
                self.axes_xmax = self.get_kwarg(kwargs, self.axes_xmax,
                                                "axes_xmax")
                self.axes_xstretch = self.get_kwarg(kwargs, self.axes_xstretch,
                                                    "axes_xstretch")
                self.axes_ylimit = self.get_kwarg(kwargs, self.axes_ylimit,
                                                  "axes_ylimit")
                self.axes_ynormalize = self.get_kwarg(kwargs,
                                                      self.axes_ynormalize,
                                                      "axes_ynormalize",
                                                      "axes_yscale")
                self.axes_yvisible = self.get_kwarg(kwargs, self.axes_yvisible,
                                                    "axes_yvisible")
                self.axes_ymin = self.get_kwarg(kwargs, self.axes_ymin,
                                                "axes_ymin")
                self.axes_ymax = self.get_kwarg(kwargs, self.axes_ymax,
                                                "axes_ymax")

                goniometer = None
                goniometer_kwargs = self.get_kwarg(kwargs, None, "goniometer",
                                                   "data_goniometer")
                if goniometer_kwargs:
                    goniometer = self.parse_init_arg(goniometer_kwargs,
                                                     None,
                                                     child=True)

                # Set up and observe atom types:
                self.atom_types = self.get_list(kwargs, [],
                                                "atom_types",
                                                "data_atom_types",
                                                parent=self)
                self._atom_types_observer = ListObserver(
                    self.on_atom_type_inserted,
                    self.on_atom_type_removed,
                    prop_name="atom_types",
                    model=self)

                # Resolve json references & observe phases
                self.phases = self.get_list(kwargs, [],
                                            "phases",
                                            "data_phases",
                                            parent=self)
                for phase in self.phases:
                    phase.resolve_json_references()
                    self.observe_model(phase)
                self._phases_observer = ListObserver(self.on_phase_inserted,
                                                     self.on_phase_removed,
                                                     prop_name="phases",
                                                     model=self)

                # Set goniometer if required & observe specimens
                self.specimens = self.get_list(kwargs, [],
                                               "specimens",
                                               "data_specimens",
                                               parent=self)
                for specimen in self.specimens:
                    if goniometer: specimen.goniometer = goniometer
                    self.observe_model(specimen)
                self._specimens_observer = ListObserver(
                    self.on_specimen_inserted,
                    self.on_specimen_removed,
                    prop_name="specimens",
                    model=self)

                # Observe behaviours:
                #self.behaviours = self.get_list(kwargs, [], "behaviours", parent=self)
                #for behaviour in self.behaviours:
                #    self.observe_model(behaviour)
                #self._behaviours_observer = ListObserver(
                #    self.on_behaviour_inserted,
                #    self.on_behaviour_removed,
                #    prop_name="behaviours",
                #    model=self
                #)

                # Observe mixtures:
                self.mixtures = self.get_list(kwargs, [],
                                              "mixtures",
                                              "data_mixtures",
                                              parent=self)
                for mixture in self.mixtures:
                    self.observe_model(mixture)
                self._mixtures_observer = ListObserver(
                    self.on_mixture_inserted,
                    self.on_mixture_removed,
                    prop_name="mixtures",
                    model=self)

                self.name = str(
                    self.get_kwarg(kwargs, "Project name", "name",
                                   "data_name"))
                self.date = str(
                    self.get_kwarg(kwargs, time.strftime("%d/%m/%Y"), "date",
                                   "data_date"))
                self.description = str(
                    self.get_kwarg(kwargs, "Project description",
                                   "description", "data_description"))
                self.author = str(
                    self.get_kwarg(kwargs, "Project author", "author",
                                   "data_author"))

                load_default_data = self.get_kwarg(kwargs, True,
                                                   "load_default_data")
                if load_default_data and self.layout_mode != 1 and \
                    len(self.atom_types) == 0:
                    self.load_default_data()

                self.needs_saving = True
            pass  # end with visuals_changed
        pass  # end with data_changed

    def load_default_data(self):
        for atom_type in AtomType.get_from_csv(
                settings.DATA_REG.get_file_path("ATOM_SCAT_FACTORS")):
            self.atom_types.append(atom_type)

    # ------------------------------------------------------------
    #      Notifications of observable properties
    # ------------------------------------------------------------
    def on_phase_inserted(self, item):
        # Set parent on the new phase:
        if item.parent != self: item.parent = self
        item.resolve_json_references()

    def on_phase_removed(self, item):
        with self.data_changed.hold_and_emit():
            # Clear parent:
            item.parent = None
            # Clear links with other phases:
            if getattr(item, "based_on", None) is not None:
                item.based_on = None
            for phase in self.phases:
                if getattr(phase, "based_on", None) == item:
                    phase.based_on = None
            # Remove phase from mixtures:
            for mixture in self.mixtures:
                mixture.unset_phase(item)

    def on_atom_type_inserted(self, item, *data):
        if item.parent != self: item.parent = self
        # We do not observe AtomType's directly, if they change,
        # Atoms containing them will be notified, and that event should bubble
        # up to the project level.

    def on_atom_type_removed(self, item, *data):
        item.parent = None
        # We do not emit a signal for AtomType's, if it was part of
        # an Atom, the Atom will be notified, and the event should bubble
        # up to the project level

    def on_specimen_inserted(self, item):
        # Set parent and observe the new specimen (visuals changed signals):
        if item.parent != self: item.parent = self
        self.observe_model(item)

    def on_specimen_removed(self, item):
        with self.data_changed.hold_and_emit():
            # Clear parent & stop observing:
            item.parent = None
            self.relieve_model(item)
            # Remove specimen from mixtures:
            for mixture in self.mixtures:
                mixture.unset_specimen(item)

    def on_mixture_inserted(self, item):
        # Set parent and observe the new mixture:
        if item.parent != self: item.parent = self
        self.observe_model(item)

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

    def on_behaviour_inserted(self, item):
        # Set parent and observe the new mixture:
        if item.parent != self: item.parent = self
        self.observe_model(item)

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

    @DataModel.observe("data_changed", signal=True)
    def notify_data_changed(self, model, prop_name, info):
        self.needs_saving = True
        if isinstance(model, Mixture):
            self.data_changed.emit()

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

    # ------------------------------------------------------------
    #      Input/Output stuff
    # ------------------------------------------------------------
    @classmethod
    def from_json(type, **kwargs):  # @ReservedAssignment
        project = type(**kwargs)
        project.needs_saving = False  # don't mark this when just loaded
        return project

    def to_json_multi_part(self):
        to_json = self.to_json()
        properties = to_json["properties"]

        for name in ("phases", "specimens", "atom_types",
                     "mixtures"):  #"behaviours"
            yield (name, properties.pop(name))
            properties[name] = "file://%s" % name

        yield ("content", to_json)
        yield ("version", __version__)

    @staticmethod
    def create_from_sybilla_xml(filename, **kwargs):
        from pyxrd.project.importing import create_project_from_sybilla_xml
        return create_project_from_sybilla_xml(filename, **kwargs)

    # ------------------------------------------------------------
    #      Draggable mix-in hook:
    # ------------------------------------------------------------
    def on_label_dragged(self, delta_y, button=1):
        if button == 1:
            self.display_label_pos += delta_y
        pass

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def get_scale_factor(self, specimen=None):
        """
        Get the factor with which to scale raw data and the scaled offset
                
        :rtype: tuple containing the scale factor and the (scaled) offset
        """
        if self.axes_ynormalize == 0 or (self.axes_ynormalize == 1
                                         and specimen is None):
            return (1.0 / (self.get_max_display_y() or 1.0), 1.0)
        elif self.axes_ynormalize == 1:
            return (1.0 / (specimen.get_max_display_y or 1.0), 1.0)
        elif self.axes_ynormalize == 2:
            return (1.0, self.get_max_display_y())
        else:
            raise ValueError(
                "Wrong value for 'axes_ysnormalize' in %s: is `%d`; should be 0, 1 or 2"
                % (self, self.axes_ynormalize))

    def get_max_display_y(self):
        max_display_y = 0
        if self.parent is not None:
            for specimen in self.parent.current_specimens:
                max_display_y = max(specimen.max_display_y, max_display_y)
        return max_display_y

    @contextmanager
    def hold_child_signals(self):
        logger.info("Holding back all project child object signals")
        with self.hold_mixtures_needs_update():
            with self.hold_mixtures_data_changed():
                with self.hold_phases_data_changed():
                    with self.hold_specimens_data_changed():
                        with self.hold_atom_types_data_changed():
                            yield

    @contextmanager
    def hold_mixtures_needs_update(self):
        logger.info("Holding back all 'needs_update' signals from Mixtures")
        with EventContextManager(
                *[mixture.needs_update.hold() for mixture in self.mixtures]):
            yield

    @contextmanager
    def hold_mixtures_data_changed(self):
        logger.info("Holding back all 'data_changed' signals from Mixtures")
        with EventContextManager(
                *[mixture.data_changed.hold() for mixture in self.mixtures]):
            yield

    @contextmanager
    def hold_phases_data_changed(self):
        logger.info("Holding back all 'data_changed' signals from Phases")
        with EventContextManager(
                *[phase.data_changed.hold() for phase in self.phases]):
            yield

    @contextmanager
    def hold_atom_types_data_changed(self):
        logger.info("Holding back all 'data_changed' signals from AtomTypes")
        with EventContextManager(
                *
            [atom_type.data_changed.hold() for atom_type in self.atom_types]):
            yield

    @contextmanager
    def hold_specimens_data_changed(self):
        logger.info("Holding back all 'data_changed' signals from Specimens")
        with EventContextManager(
                *[specimen.data_changed.hold()
                  for specimen in self.specimens]):
            yield

    def update_all_mixtures(self):
        """
        Forces all mixtures in this project to update. If they have auto
        optimization enabled, this will also optimize them. 
        """
        for mixture in self.mixtures:
            with self.data_changed.ignore():
                mixture.update()

    def get_mixtures_by_name(self, mixture_name):
        """
        Convenience method that returns all the mixtures who's name match the
        passed name as a list.
        """
        return [
            mixture for mixture in self.mixtures
            if (mixture.name == mixture_name)
        ]

    # ------------------------------------------------------------
    #      Specimen list related
    # ------------------------------------------------------------
    def move_specimen_up(self, specimen):
        """
        Move the passed :class:`~pyxrd.specimen.models.Specimen` up one slot.
        Will raise and IndexError if the passed specimen is not in this project.
        """
        index = self.specimens.index(specimen)
        self.specimens.insert(min(index + 1, len(self.specimens)),
                              self.specimens.pop(index))

    def move_specimen_down(self, specimen):
        """
        Move the passed :class:`~pyxrd.specimen.models.Specimen` down one slot
        Will raise and IndexError if the passed specimen is not in this project.
        """
        index = self.specimens.index(specimen)
        self.specimens.insert(max(index - 1, 0), self.specimens.pop(index))
        pass

    # ------------------------------------------------------------
    #      Phases list related
    # ------------------------------------------------------------
    def load_phases(self, filename, parser, insert_index=0):
        """
        Loads all :class:`~pyxrd.phase.models.Phase` objects from the file
        'filename'. An optional index can be given where the phases need to be
        inserted at.
        """
        # make sure we have no duplicate UUID's
        insert_index = not_none(insert_index, 0)
        type(Project).object_pool.change_all_uuids()
        for phase in parser.parse(filename):
            phase.parent = self
            self.phases.insert(insert_index, phase)
            insert_index += 1

    # ------------------------------------------------------------
    #      AtomType's list related
    # ------------------------------------------------------------
    def load_atom_types(self, filename, parser):
        """
        Loads all :class:`~pyxrd.atoms.models.AtomType` objects from the
        file specified by *filename*.
        """
        # make sure we have no duplicate UUID's
        type(Project).object_pool.change_all_uuids()
        for atom_type in parser.parse(filename):
            atom_type.parent = self
            self.atom_types.append(atom_type)

    pass  # end of class
Exemplo n.º 8
0
class PyXRDLine(StorableXYData):
    """
        A PyXRDLine is an abstract attribute holder for a real 'Line' object,
        whatever the plotting library used may be. Attributes are line width and
        color.        
    """

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

    # OBSERVABLE PROPERTIES:

    #: The line label
    label = StringProperty(default="", text="Label", persistent=True)

    #: The line color
    color = StringProperty(default="#000000",
                           text="Label",
                           visible=True,
                           persistent=True,
                           widget_type="color",
                           inherit_flag="inherit_color",
                           inherit_from="parent.parent.display_exp_color",
                           signal_name="visuals_changed",
                           mix_with=(InheritableMixin, SignalMixin))

    #: Flag indicating whether to use the grandparents color yes/no
    inherit_color = BoolProperty(default=True,
                                 text="Inherit color",
                                 visible=True,
                                 persistent=True,
                                 signal_name="visuals_changed",
                                 mix_with=(SignalMixin, ))

    #: The linewidth in points
    lw = FloatProperty(
        default=2.0,
        text="Linewidth",
        visible=True,
        persistent=True,
        widget_type="spin",
        inherit_flag="inherit_lw",
        inherit_from="parent.parent.display_exp_lw",
        signal_name="visuals_changed",
        mix_with=(InheritableMixin, SignalMixin),
    )

    #: Flag indicating whether to use the grandparents linewidth yes/no
    inherit_lw = BoolProperty(
        default=True,
        text="Inherit linewidth",
        visible=True,
        persistent=True,
        signal_name="visuals_changed",
        mix_with=(SignalMixin, ),
    )

    #: A short string describing the (matplotlib) linestyle
    ls = StringChoiceProperty(
        default=settings.EXPERIMENTAL_LINESTYLE,
        text="Linestyle",
        visible=True,
        persistent=True,
        choices=settings.PATTERN_LINE_STYLES,
        mix_with=(
            InheritableMixin,
            SignalMixin,
        ),
        signal_name="visuals_changed",
        inherit_flag="inherit_ls",
        inherit_from="parent.parent.display_exp_ls",
    )

    #: Flag indicating whether to use the grandparents linestyle yes/no
    inherit_ls = BoolProperty(default=True,
                              text="Inherit linestyle",
                              visible=True,
                              persistent=True,
                              mix_with=(SignalMixin, ),
                              signal_name="visuals_changed")

    #: A short string describing the (matplotlib) marker
    marker = StringChoiceProperty(
        default=settings.EXPERIMENTAL_MARKER,
        text="Marker",
        visible=True,
        persistent=True,
        choices=settings.PATTERN_MARKERS,
        mix_with=(
            InheritableMixin,
            SignalMixin,
        ),
        signal_name="visuals_changed",
        inherit_flag="inherit_marker",
        inherit_from="parent.parent.display_exp_marker",
    )

    #: Flag indicating whether to use the grandparents linewidth yes/no
    inherit_marker = BoolProperty(
        default=True,
        text="Inherit marker",
        visible=True,
        persistent=True,
        mix_with=(SignalMixin, ),
        signal_name="visuals_changed",
    )

    #: z-data (e.g. relative humidity, temperature, for multi-column 'lines')
    z_data = ListProperty(default=None,
                          text="Z data",
                          data_type=float,
                          persistent=True,
                          visible=False)

    # REGULAR PROPERTIES:
    @property
    def max_display_y(self):
        if self.num_columns > 2:
            # If there's several y-columns, check if we have z-data associated with them
            # if so, it is a 2D pattern, otherwise this is a multi-line pattern
            if len(self.z_data) > 2:
                return np.max(self.z_data)
            else:
                return self.max_y
        else:
            # If there's a single comumn of y-data, just get the max value
            return self.max_y

    @property
    def min_intensity(self):
        if self.num_columns > 2:
            return np.min(self.z_data)
        else:
            return self.min_y

    @property
    def abs_max_intensity(self):
        return self.abs_max_y

    # ------------------------------------------------------------
    #      Initialisation and other internals
    # ------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        """
            Valid keyword arguments for a PyXRDLine are:
                data: the actual data containing x and y values
                label: the label for this line
                color: the color of this line
                inherit_color: whether to use the parent-level color or its own
                lw: the line width of this line
                inherit_lw: whether to use the parent-level line width or its own
                ls: the line style of this line
                inherit_ls: whether to use the parent-level line style or its own
                marker: the line marker of this line
                inherit_marker: whether to use the parent-level line marker or its own
                z_data: the z-data associated with the columns in a multi-column pattern
        """
        my_kwargs = self.pop_kwargs(
            kwargs, *[
                prop.label
                for prop in PyXRDLine.Meta.get_local_persistent_properties()
            ])
        super(PyXRDLine, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        with self.visuals_changed.hold():
            self.label = self.get_kwarg(kwargs, self.label, "label")
            self.color = self.get_kwarg(kwargs, self.color, "color")
            self.inherit_color = bool(
                self.get_kwarg(kwargs, self.inherit_color, "inherit_color"))
            self.lw = float(self.get_kwarg(kwargs, self.lw, "lw"))
            self.inherit_lw = bool(
                self.get_kwarg(kwargs, self.inherit_lw, "inherit_lw"))
            self.ls = self.get_kwarg(kwargs, self.ls, "ls")
            self.inherit_ls = bool(
                self.get_kwarg(kwargs, self.inherit_ls, "inherit_ls"))
            self.marker = self.get_kwarg(kwargs, self.marker, "marker")
            self.inherit_marker = bool(
                self.get_kwarg(kwargs, self.inherit_marker, "inherit_marker"))
            self.z_data = list(self.get_kwarg(kwargs, [0], "z_data"))

    # ------------------------------------------------------------
    #      Input/Output stuff
    # ------------------------------------------------------------
    @classmethod
    def from_json(cls, **kwargs):  # @ReservedAssignment
        if "xy_store" in kwargs:
            if "type" in kwargs["xy_store"]:
                kwargs["data"] = kwargs["xy_store"]["properties"]["data"]
        elif "xy_data" in kwargs:
            if "type" in kwargs["xy_data"]:
                kwargs["data"] = kwargs["xy_data"]["properties"]["data"]
            kwargs["label"] = kwargs["data_label"]
            del kwargs["data_name"]
            del kwargs["data_label"]
            del kwargs["xy_data"]
        return cls(**kwargs)

    # ------------------------------------------------------------
    #      Convenience Methods & Functions
    # ------------------------------------------------------------
    def interpolate(self, *x_vals, **kwargs):
        """
            Returns a list of (x, y) tuples for the passed x values. An optional
            column keyword argument can be passed to select a column, by default
            the first y-column is used. Returned y-values are interpolated. 
        """
        column = kwargs.get("column", 0)
        f = interp1d(self.data_x, self.data_y[:, column])
        return list(zip(x_vals, f(x_vals)))

    def get_plotted_y_at_x(self, x):
        """
            Gets the (interpolated) plotted value at the given x position.
            If this line has not been plotted (or does not have
            access to a '__plot_line' attribute set by the plotting routines)
            it will return 0.
        """
        try:
            xdata, ydata = getattr(self, "__plot_line").get_data()
        except AttributeError:
            logging.exception(
                "Attribute error when trying to get plotter data at x position!"
            )
        else:
            if len(xdata) > 0 and len(ydata) > 0:
                return np.interp(x, xdata, ydata)
        return 0

    def calculate_npeaks_for(self, max_threshold, steps):
        """
            Calculates the number of peaks for `steps` threshold values between
            0 and `max_threshold`. Returns a tuple containing two lists with the
            threshold values and the corresponding number of peaks. 
        """
        length = self.data_x.size

        resolution = length / (self.data_x[-1] - self.data_x[0])
        delta_angle = 0.05
        window = int(delta_angle * resolution)
        window += (window % 2) * 2

        steps = max(steps, 2) - 1
        factor = max_threshold / steps

        deltas = [i * factor for i in range(0, steps)]

        numpeaks = []

        maxtabs, mintabs = multi_peakdetect(self.data_y[:, 0], self.data_x, 5,
                                            deltas)
        for maxtab, _ in zip(maxtabs, mintabs):
            numpeak = len(maxtab)
            numpeaks.append(numpeak)
        numpeaks = list(map(float, numpeaks))

        return deltas, numpeaks

    def get_best_threshold(self,
                           max_threshold=None,
                           steps=None,
                           status_dict=None):
        """
            Estimates the best threshold for peak detection using an
            iterative algorithm. Assumes there is a linear contribution from noise.
            Returns a 4-tuple containing the selected threshold, the maximum
            threshold, a list of threshold values and a list with the corresponding
            number of peaks.
        """
        length = self.data_x.size
        steps = not_none(steps, 20)
        threshold = 0.1
        max_threshold = not_none(max_threshold, threshold * 3.2)

        def get_new_threshold(threshold, deltas, num_peaks, ln):
            # Left side line:
            x = deltas[:ln]
            y = num_peaks[:ln]
            slope, intercept, R, _, _ = stats.linregress(x, y)
            return R, -intercept / slope

        if length > 2:
            # Adjust the first distribution:
            deltas, num_peaks = self.calculate_npeaks_for(max_threshold, steps)

            #  Fit several lines with increasing number of points from the
            #  generated threshold / marker count graph. Stop when the
            #  R-coefficiënt drops below 0.95 (past linear increase from noise)
            #  Then repeat this by increasing the resolution of data points
            #  and continue until the result does not change anymore

            last_threshold = None
            solution = False
            max_iters = 10
            min_iters = 3
            itercount = 0
            if status_dict is not None:
                status_dict["progress"] = 0

            while not solution:
                # Number of points to use for the lin regress:
                ln = 4
                # Maximum number of points to use:
                max_ln = len(deltas)
                # Flag indicating if we can stop searching for the linear part
                stop = False
                while not stop:
                    R, threshold = get_new_threshold(threshold, deltas,
                                                     num_peaks, ln)
                    max_threshold = threshold * 3.2
                    if abs(R) < 0.98 or ln >= max_ln:
                        stop = True
                    else:
                        ln += 1
                itercount += 1  # Increase # of iterations
                if last_threshold:
                    # Check if we have run at least `min_iters`, at most `max_iters`
                    # and have not reached an equilibrium.
                    solution = bool(
                        itercount > min_iters
                        and not (itercount <= max_iters
                                 and last_threshold - threshold >= 0.001))
                    if not solution:
                        deltas, num_peaks = self.calculate_npeaks_for(
                            max_threshold, steps)
                last_threshold = threshold
                if status_dict is not None:
                    status_dict["progress"] = float(itercount / max_iters)

            return (deltas, num_peaks), threshold, max_threshold
        else:
            return ([], []), threshold, max_threshold

    pass  # end of class
Exemplo n.º 9
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):
        store_id = "Mixture"

    _data_object = None
    @property
    def data_object(self):
        self._data_object.parsed = False
        self._data_object.optimized = False
        self._data_object.calculated = False
        self._data_object.specimens = [None] * len(self.specimens)
                
        self._data_object.n = len(self.specimens)
        self._data_object.m = len(self.phases)
               
        self._data_object.scales_mask = np.ones_like(self.scales)
        if self.auto_scales:
            self._data_object.scales_mask = np.ones_like(self.bgshifts)
        else:
            self._data_object.scales_mask = np.zeros_like(self.bgshifts)
            
        if self.auto_bg:
            self._data_object.bgshifts_mask = np.ones_like(self.bgshifts)
        else:
            self._data_object.bgshifts_mask = np.zeros_like(self.bgshifts)
        
        for i, specimen in enumerate(self.specimens):
            if specimen is not None:
                data_object = specimen.data_object
                data_object.phases = [[None] * self._data_object.m for _ in data_object.z_list]
                for z_index in range(len(specimen.get_z_list())):
                    for phase_index in range(self.phase_matrix.shape[1]):
                        data_object.phases[z_index][phase_index] = self.get_phase_data_object(i, z_index, phase_index)
                self._data_object.specimens[i] = data_object
            else:
                self._data_object.specimens[i] = None
        return self._data_object

    def get_phase_data_object(self, specimen_index, z_index, phase_index):
        phase = self.phase_matrix[specimen_index, ...].flatten()[phase_index]
        return phase.data_object if phase is not None else None

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

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

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

    #: Flag, True if the mixture will automatically adjust phase fractions and scales
    auto_run = BoolProperty(
        default=False, text="Auto Run",
        visible=True, persistent=True, tabular=True
    )

    #: Flag, True if the mixture is allowed to also update the background level
    auto_bg = BoolProperty(
        default=False, text="Auto Bg",
        visible=True, persistent=True, tabular=True
    )

    #: Flag, True if the mixture is allowed to also update the background level
    auto_scales = BoolProperty(
        default=True, text="Auto Scales",
        visible=True, persistent=True, tabular=True
    )

    #: The tree of refinable properties
    @LabeledProperty(
        default=None, text="",
        visible=True, persistent=False, tabular=True,
        data_type=object,
    )
    def refinables(self):
        return self.refinement.refinables

    #: An integer describing which method to use for the refinement (see
    #: mixture.models.methods.get_all_refine_methods)
    @IntegerProperty(
        default=0, text="Refinement method index",
        visible=False, persistent=True,
    )
    def refine_method_index(self):
        return self.refinement.refine_method_index

    #: A dict containing the current refinement options
    @LabeledProperty(
        default=None, text="Current refinement method options",
        visible=False, persistent=True, tabular=False,
        data_type=object, store_private="all_refine_options"
    )
    def refine_options(self):
        return  self.refinement.refine_options

    #: A dict containing all refinement options
    @LabeledProperty(
        default=None, text="All refinement methods options",
        visible=False, persistent=False, tabular=False,
        data_type=object
    )
    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

    @property
    def fractions_mask(self):
        """ A mask indicating which fractions are to be optimized """
        return self._data_object.fractions_mask
    @fractions_mask.setter
    def fractions_mask(self, value):
        self._data_object.fractions_mask = 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", "fractions_mask", "bgshifts", "scales", "phases",
            *[prop.label for prop in Mixture.Meta.get_local_persistent_properties()]
        )
        super(Mixture, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        with self.data_changed.hold():

            self._data_object = MixtureData()

            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")
            self.auto_scales = self.get_kwarg(kwargs, True, "auto_scales")

            # 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[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[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"))
            # list with phase fractions mask, indexes match with cols in phase_matrix (=phases)
            self.fractions_mask = np.asarray(self.get_kwarg(kwargs, [1] * len(self.phases), "fractions_mask"))

            # 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 (%s) or phases (%d) lists do not match with column count of phase matrix (%d)" % (len(self.fractions), len(self.phases), m))

            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["fractions_mask"] = self.fractions_mask.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)
                self.fractions_mask = np.append(self.fractions_mask, 1)
                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.fractions_mask = np.delete(self.fractions_mask, 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)

                    # reset flag when needed:
                    mixture.calculated = mixture.calculated and not calculate
                    mixture = self.optimizer.calculate(mixture)

                    for i, (specimen_data, specimen) in enumerate(zip(mixture.specimens, self.specimens)):
                        if specimen is not None:
                            with specimen.data_changed.ignore():
                                specimen.update_pattern(
                                    specimen_data.total_intensity,
                                    specimen_data.scaled_phase_intensities,
                                    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)
            next(reader) # 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.items()):
                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
Exemplo n.º 10
0
class UnitCellProperty(ComponentPropMixin,
                       RefinementValue,
                       DataModel,
                       Storable,
                       metaclass=PyXRDRefinableMeta):
    """
        UnitCellProperty's are an integral part of a component and allow to 
        calculate the dimensions of the unit cell based on compositional
        information such as the iron content.
        This class is not responsible for keeping its value up-to-date.
        With other words, it is the responsibility of the higher-level class
        to call the 'update_value' method on this object whenever it emits a
        'data_changed' signal. The reason for this is to prevent infinite 
        recursion errors. 
    """
    class Meta(DataModel.Meta):
        store_id = "UnitCellProperty"

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

    # PROPERTIES:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    # PROPERTIES:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    pass  # end of class
Exemplo n.º 12
0
class AtomRelation(ComponentPropMixin,
                   RefinementValue,
                   DataModel,
                   Storable,
                   metaclass=PyXRDRefinableMeta):

    # MODEL INTEL:
    class Meta(DataModel.Meta):
        store_id = "AtomRelation"
        file_filters = [
            ("Atom relation", get_case_insensitive_glob("*.atr")),
        ]
        allowed_relations = {}

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

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

    #: The value of this AtomRelation
    value = FloatProperty(default=0.0,
                          text="Value",
                          visible=True,
                          persistent=True,
                          tabular=True,
                          widget_type='float_entry',
                          signal_name="data_changed",
                          refinable=True,
                          mix_with=(SignalMixin, RefinableMixin))

    #: Flag indicating whether this AtomRelation is enabled or not
    enabled = BoolProperty(default=True,
                           text="Enabled",
                           visible=True,
                           persistent=True,
                           tabular=True,
                           signal_name="data_changed",
                           mix_with=(SignalMixin, ))

    #: Is True when this AtomRelation's value is driven by another AtomRelation.
    #: Should never be set directly or things might break!
    driven_by_other = BoolProperty(default=False,
                                   text="Driven by other",
                                   visible=False,
                                   persistent=False,
                                   tabular=True)

    @property
    def applicable(self):
        """
        Is True when this AtomRelation was passed a component of which the atom
        ratios are not set to be inherited from another component.
        """
        return (self.parent is not None
                and not self.parent.inherit_atom_relations)

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

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

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

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

    @property
    def inside_linked_component(self):
        return (self.component.linked_with
                is not None) and self.component.inherit_atom_relations

    @property
    def is_refinable(self):
        return self.enabled and not self.driven_by_other and not self.inside_linked_component

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

    # ------------------------------------------------------------
    #      Initialisation and other internals
    # ------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        """
            Valid keyword arguments for an AtomRelation are:
                name: the name of this AtomRelation
                value: the value for this AtomRelation
                enabled: boolean indicating whether or not this AtomRelation is 
                 enabled
        """
        my_kwargs = self.pop_kwargs(
            kwargs, "data_name", "data_ratio", "ratio", *[
                prop.label for prop in
                AtomRelation.Meta.get_local_persistent_properties()
            ])
        super(AtomRelation, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        self.name = self.get_kwarg(kwargs, "", "name", "data_name")
        self.value = self.get_kwarg(kwargs, 0.0, "value", "ratio",
                                    "data_ratio")
        self.enabled = bool(self.get_kwarg(kwargs, True, "enabled"))

    # ------------------------------------------------------------
    #      Input/Output stuff
    # ------------------------------------------------------------
    def resolve_relations(self):
        raise NotImplementedError(
            "Subclasses should implement the resolve_relations method!")

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def create_prop_store(self, prop=None):
        if self.component is not None:
            store = Gtk.ListStore(object, str, str)
            for atom in self.component._layer_atoms:
                store.append([atom, "pn", atom.name])
            for atom in self.component._interlayer_atoms:
                store.append([atom, "pn", atom.name])
            for relation in self.component._atom_relations:
                tp = relation.Meta.store_id
                if tp in self.Meta.allowed_relations:
                    for prop, name in self.Meta.allowed_relations[tp]:
                        if callable(name):
                            name = name(relation)
                        store.append([relation, prop, name])
            return store

    def iter_references(self):
        raise NotImplementedError(
            "'iter_references' should be implemented by subclasses!")

    def _safe_is_referring(self, value):
        if value is not None and hasattr(value, "is_referring"):
            return value.is_referring([
                self,
            ])
        else:
            return False

    def is_referring(self, references=None):
        """
            Checks whether this AtomRelation is causing circular references.
            Can be used to check this before actually setting references by
            setting the 'references' keyword argument to a list containing the
            new reference value(s).
        """
        if references == None:
            references = []
        # 1. Bluntly check if we're not already somewhere referred to,
        #    if not, add ourselves to the list of references
        if self in references:
            return True
        references.append(self)

        # 2. Loop over our own references, check if they cause a circular
        #    reference, if not add them to the list of references.
        for reference in self.iter_references():
            if reference is not None and hasattr(reference, "is_referring"):
                if reference.is_referring(references):
                    return True
                else:
                    references.append(reference)

        return False

    def _set_driven_flag_for_prop(self, prop=None):
        """Internal method used to safely set the driven_by_other flag on an object.
        Subclasses can override to provide a check on the property set by the driver."""
        self.driven_by_other = True

    def apply_relation(self):
        raise NotImplementedError(
            "Subclasses should implement the apply_relation method!")

    pass  # end of class
Exemplo n.º 13
0
class Component(RefinementGroup,
                DataModel,
                Storable,
                metaclass=PyXRDRefinableMeta):

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

    _data_object = None

    @property
    def data_object(self):
        weight = 0.0

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

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

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

        return self._data_object

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

    # SIGNALS:
    atoms_changed = SignalProperty()

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

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

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

    # PROPERTIES:

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

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

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

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

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

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

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

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

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

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

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

    _linked_with_index = None
    _linked_with_uuid = None

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    pass  # end of class
Exemplo n.º 14
0
class Refinement(ChildModel):
    """
        A simple model that plugs onto the Mixture model. It provides
        the functionality related to refinement of parameters.
    """

    # MODEL INTEL:
    class Meta(ChildModel.Meta):
        store_id = "Refinement"

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

    #: Flag, True if after refinement plots should be generated of the parameter space
    make_psp_plots = BoolProperty(default=False,
                                  text="Make parameter space plots",
                                  tabular=False,
                                  visible=True,
                                  persistent=True)

    #: TreeNode containing the refinable properties
    refinables = ListProperty(default=None,
                              text="Refinables",
                              tabular=True,
                              persistent=False,
                              visible=True,
                              data_type=RefinableWrapper,
                              cast_to=None,
                              widget_type="object_tree_view")

    #: A dict containing an instance of each refinement method
    refine_methods = None

    #: An integer describing which method to use for the refinement
    refine_method_index = IntegerChoiceProperty(
        default=0,
        text="Refinement method index",
        tabular=True,
        persistent=True,
        visible=True,
        choices={
            key: method.name
            for key, method in RefineMethodManager.get_all_methods().items()
        })

    #: A dict containing the current refinement options
    @LabeledProperty(default=None,
                     text="Refine options",
                     persistent=False,
                     visible=False,
                     mix_with=(ReadOnlyMixin, ))
    def refine_options(self):
        return self.get_refinement_method().get_options()

    #: A dict containing all refinement options
    @property
    def all_refine_options(self):
        return {
            method.index: method.get_options()
            for method in list(self.refine_methods.values())
        }

    def __init__(self, *args, **kwargs):
        my_kwargs = self.pop_kwargs(kwargs, "refine_method_index",
                                    "refine_method", "refine_options")
        super(Refinement, self).__init__(*args, **kwargs)
        kwargs = my_kwargs

        # Setup the refinables treestore
        self.refinables = TreeNode()
        self.update_refinement_treestore()

        # Setup the refine methods
        try:
            self.refine_method_index = int(
                self.get_kwarg(kwargs, None, "refine_method_index",
                               "refine_method"))
        except ValueError:
            self.refine_method_index = self.refine_method_index
            pass  # ignore faulty values, these indices change from time to time.

        self.refine_methods = RefineMethodManager.initialize_methods(
            self.get_kwarg(kwargs, None, "refine_options"))

    # ------------------------------------------------------------
    #      Refiner methods
    # ------------------------------------------------------------
    def get_refiner(self):
        """
            This returns a Refiner object which can be used to refine the
            selected properties using the selected algorithm.
            Just call 'refine(stop)' on the returned object, with stop a
            threading.Event or multiprocessing.Event which you can use to stop
            the refinement before completion.
            The Refiner object also has a RefineHistory and RefineStatus object
            that can be used to track the status and history of the refinement.
        """

        return Refiner(method=self.get_refinement_method(),
                       data_callback=lambda: self.mixture.data_object,
                       refinables=self.refinables,
                       event_cmgr=EventContextManager(
                           self.mixture.needs_update,
                           self.mixture.data_changed),
                       metadata=dict(
                           phases=self.mixture.phases,
                           num_specimens=len(self.mixture.specimens),
                       ))

    # ------------------------------------------------------------
    #      Refinement Methods Management
    # ------------------------------------------------------------
    def get_refinement_method(self):
        """
            Returns the actual refinement method by translating the 
            `refine_method` attribute
        """
        return self.refine_methods[self.refine_method_index]

    # ------------------------------------------------------------
    #      Refinables Management
    # ------------------------------------------------------------
    # TODO set a restrict range attribute on the PropIntels, so we can use custom ranges for each property
    def auto_restrict(self):
        """
            Convenience function that restricts the selected properties 
            automatically by setting their minimum and maximum values.
        """
        with self.mixture.needs_update.hold():
            for node in self.refinables.iter_children():
                ref_prop = node.object
                if ref_prop.refine and ref_prop.refinable:
                    ref_prop.value_min = ref_prop.value * 0.8
                    ref_prop.value_max = ref_prop.value * 1.2

    def randomize(self):
        """
            Convenience function that randomize the selected properties.
            Respects the current minimum and maximum values.
            Executes an optimization after the randomization.
        """
        with self.mixture.data_changed.hold_and_emit():
            with self.mixture.needs_update.hold_and_emit():
                for node in self.refinables.iter_children():
                    ref_prop = node.object
                    if ref_prop.refine and ref_prop.refinable:
                        ref_prop.value = random.uniform(
                            ref_prop.value_min, ref_prop.value_max)

    def update_refinement_treestore(self):
        """
            This creates a tree store with all refinable properties and their
            minimum, maximum and current value.
        """
        if self.parent is not None:  # not linked so no valid phases!
            self.refinables.clear()

            def add_property(parent_node, obj, prop, is_grouper):
                rp = RefinableWrapper(obj=obj,
                                      prop=prop,
                                      parent=self.mixture,
                                      is_grouper=is_grouper)
                return parent_node.append(TreeNode(rp))

            def parse_attribute(obj, prop, root_node):
                """
                    obj: the object
                    attr: the attribute of obj or None if obj contains attributes
                    root_node: the root TreeNode new iters should be put under
                """
                if prop is not None:
                    if isinstance(prop, InheritableMixin):
                        value = prop.get_uninherited(obj)
                    else:
                        value = getattr(obj, prop.label)
                else:
                    value = obj

                if isinstance(
                        value,
                        RefinementValue):  # AtomRelation and UnitCellProperty
                    new_node = add_property(root_node, value, prop, False)
                elif hasattr(value, "__iter__"):  # List or similar
                    for new_obj in value:
                        parse_attribute(new_obj, None, root_node)
                elif isinstance(
                        value,
                        RefinementGroup):  # Phase, Component, Probability
                    if len(value.refinables) > 0:
                        new_node = add_property(root_node, value, prop, True)
                        for prop in value.refinables:
                            parse_attribute(value, prop, new_node)
                else:  # regular values
                    new_node = add_property(root_node, obj, prop, False)

            for phase in self.mixture.project.phases:
                if phase in self.mixture.phase_matrix:
                    parse_attribute(phase, None, self.refinables)

    pass  # end of class
Exemplo n.º 15
0
class R2G2Model(_AbstractProbability):
    r"""
    Probability model for Reichweite 2 with 2 components.
    
    The 4 (=g^2) independent variables are:
    
    .. math::
        :nowrap:
    
        \begin{align*}
            & W_1
            & P_{112} (W_1 leq \nicefrac{2}{3})
            \text{ or }P_{211} (W_1 > \nicefrac{2}{3}) \\
            & P_{21}
            & P_{122} (P_{21} leq \nicefrac{1}{2})
            \text{ or }P_{221} (P_{21} > \nicefrac{1}{2}) \\
        \end{align*}
            
    Calculation of the other variables happens as follows:
    
    .. math::
        :nowrap:

        \begin{align*}
            & W_2 = 1 - W_1 \\
            & P_{22} = 1 - P_{21} \\
            & \\
            & W_{21} = W_2 \cdot P_{21} \\
            & W_{21} = W_{12} \\
            & W_{11} = W_1 - W_{21} \\
            & W_{22} = W_{2} \cdot P_{22} \\
            & \\
            & \text{if $W_1 leq \nicefrac{2}{3}$:} \\
            & \quad \text{$P_{112}$ is given}\\
            & \quad P_{211} =
            \begin{dcases}
                \frac{W_{11}}{W_{21}} \cdot P_{112} , & \text{if $W_{21} > 0$} \\
                0, & \text{otherwise}
            \end{dcases} \\
            & \\
            & \text{if $W_1 > \nicefrac{2}{3}$:} \\
            & \quad \text{$P_{211}$ is given}\\
            & \quad P_{112} =
            \begin{dcases}
                \frac{W_{21}}{W_{11}} \cdot P_{211} , & \text{if $W_{11} > 0$} \\
                0, & \text{otherwise}
            \end{dcases} \\
            & \\
            & P_{212} = 1 - P_{211} \\
            & P_{111} = 1 - P_{112} \\
            & \\
            & \text{if $P_{21} leq \nicefrac{1}{2}$:} \\
            & \quad \text{$P_{122}$ is given}\\
            & \quad P_{221} =
            \begin{dcases}
                \frac{W_{12}}{W_{22}} \cdot P_{122} , & \text{if $W_{22} > 0$} \\
                0, & \text{otherwise}
            \end{dcases} \\
            & \\
            & \text{if $P_{21} > \nicefrac{1}{2}$:} \\
            & \quad \text{$P_{221}$ is given}\\
            & \quad P_{122} =
            \begin{dcases}
                \frac{W_{22}}{W_{12}} \cdot P_{221} , & \text{if $W_{12} > 0$} \\
                0, & \text{otherwise}
            \end{dcases} \\
            & P_{121} = 1 - P_{122} \\
            & P_{222} = 1 - P_{221} \\
        \end{align*}
    
    """

    # MODEL METADATA:
    class Meta(_AbstractProbability.Meta):
        store_id = "R2G2Model"

    # PROPERTIES:
    _G = 2
    twothirds = 2.0 / 3.0

    inherit_W1 = BoolProperty(default=False,
                              text="Inherit flag for W1",
                              persistent=True,
                              visible=True,
                              set_action_name="update",
                              mix_with=(SetActionMixin, ))
    W1 = FloatProperty(default=0.75,
                       text="W1 (> 0.5)",
                       math_text=r"$W_1 (> 0.5)$",
                       persistent=True,
                       visible=True,
                       refinable=True,
                       store_private=True,
                       minimum=0.5,
                       maximum=1.0,
                       is_independent=True,
                       inheritable=True,
                       inherit_flag="inherit_W1",
                       inherit_from="parent.based_on.probabilities.W1",
                       set_action_name="update",
                       mix_with=(SetActionMixin, RefinableMixin,
                                 InheritableMixin))

    inherit_P112_or_P211 = BoolProperty(default=False,
                                        text="Inherit flag for P112_or_P211",
                                        persistent=True,
                                        visible=True,
                                        set_action_name="update",
                                        mix_with=(SetActionMixin, ))
    P112_or_P211 = FloatProperty(
        default=0.75,
        text="P112 (W1 < 2/3) or\nP211 (W1 > 2/3)",
        math_text=r"$P_{112} %s$ or $\newlineP_{211} %s$" % (mt_range(
            1.0 / 2.0, "W_1", 2.0 / 3.0), mt_range(2.0 / 3.0, "W_1", 1.0)),
        persistent=True,
        visible=True,
        refinable=True,
        store_private=True,
        minimum=0.0,
        maximum=1.0,
        is_independent=True,
        inheritable=True,
        inherit_flag="inherit_P112_or_P211",
        inherit_from="parent.based_on.probabilities.P112_or_P211",
        set_action_name="update",
        mix_with=(SetActionMixin, RefinableMixin, InheritableMixin))

    inherit_P21 = BoolProperty(default=False,
                               text="Inherit flag for P21",
                               persistent=True,
                               visible=True,
                               set_action_name="update",
                               mix_with=(SetActionMixin, ))
    P21 = FloatProperty(default=0.75,
                        text="P21",
                        math_text=r"$P_{21}$",
                        persistent=True,
                        visible=True,
                        refinable=True,
                        store_private=True,
                        minimum=0.0,
                        maximum=1.0,
                        is_independent=True,
                        inheritable=True,
                        inherit_flag="inherit_P21",
                        inherit_from="parent.based_on.probabilities.P21",
                        set_action_name="update",
                        mix_with=(SetActionMixin, RefinableMixin,
                                  InheritableMixin))

    inherit_P122_or_P221 = BoolProperty(default=False,
                                        text="Inherit flag for P122_or_P221",
                                        persistent=True,
                                        visible=True,
                                        set_action_name="update",
                                        mix_with=(SetActionMixin, ))
    P122_or_P221 = FloatProperty(
        default=0.75,
        text="P112 (W1 < 1/2) or\nP221 (W1 > 1/2)",
        math_text=r"$P_{122} %s$ or $\newlineP_{221} %s$" %
        (mt_range(0.0, "W_1", 1.0 / 2.0), mt_range(1.0 / 2.0, "W_1", 1.0)),
        persistent=True,
        visible=True,
        refinable=True,
        store_private=True,
        minimum=0.0,
        maximum=1.0,
        is_independent=True,
        inheritable=True,
        inherit_flag="inherit_P122_or_P221",
        inherit_from="parent.based_on.probabilities.P122_or_P221",
        set_action_name="update",
        mix_with=(SetActionMixin, RefinableMixin, InheritableMixin))

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self,
                 W1=0.75,
                 P112_or_P211=0.75,
                 P21=0.75,
                 P122_or_P221=0.75,
                 inherit_W1=False,
                 inherit_P112_or_P211=False,
                 inherit_P21=False,
                 inherit_P122_or_P221=False,
                 *args,
                 **kwargs):
        super(R2G2Model, self).__init__(R=2, *args, **kwargs)

        with self.data_changed.hold():
            self.W1 = not_none(W1, 0.75)
            self.inherit_W1 = inherit_W1
            self.P112_or_P211 = not_none(P112_or_P211, 0.75)
            self.inherit_P112_or_P211 = inherit_P112_or_P211
            self.P21 = not_none(P21, 0.75)
            self.inherit_P21 = inherit_P21
            self.P122_or_P221 = not_none(P122_or_P221, 0.75)
            self.inherit_P122_or_P221 = inherit_P122_or_P221

            self.update()

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def update(self):
        with self.monitor_changes():
            self.mW[0] = self.W1
            self.mW[1] = 1.0 - self.mW[0]

            self.mP[1, 0] = self.P21
            self.mP[1, 1] = 1.0 - self.mP[1, 0]

            self.mW[1, 0] = self.mW[1] * self.mP[1, 0]
            self.mW[1, 1] = self.mW[1] * self.mP[1, 1]
            self.mW[0, 1] = self.mW[1, 0]
            self.mW[0, 0] = self.mW[0] - self.mW[1, 0]

            if self.mW[0] <= self.twothirds:
                self.mP[0, 0, 1] = self.P112_or_P211
                if self.mW[1, 0] == 0.0:
                    self.mP[1, 0, 0] = 0.0
                else:
                    self.mP[1, 0,
                            0] = self.mP[0, 0, 1] * self.mW[0, 0] / self.mW[1,
                                                                            0]
            else:
                self.mP[1, 0, 0] = self.P112_or_P211
                if self.mW[0, 0] == 0.0:
                    self.mP[0, 0, 1] = 0.0
                else:
                    self.mP[0, 0,
                            1] = self.mP[1, 0, 0] * self.mW[1, 0] / self.mW[0,
                                                                            0]
            self.mP[1, 0, 1] = 1.0 - self.mP[1, 0, 0]
            self.mP[0, 0, 0] = 1.0 - self.mP[0, 0, 1]

            if self.mP[1, 0] <= 0.5:
                self.mP[0, 1, 1] = self.P122_or_P221
                self.mP[1, 1,
                        0] = self.mP[0, 1, 1] * self.mW[0, 1] / self.mW[1, 1]
            else:
                self.mP[1, 1, 0] = self.P122_or_P221
                self.mP[0, 1,
                        1] = self.mP[1, 1, 0] * self.mW[1, 1] / self.mW[0, 1]
            self.mP[0, 1, 0] = 1.0 - self.mP[0, 1, 1]
            self.mP[1, 1, 1] = 1.0 - self.mP[1, 1, 0]

            self.solve()
            self.validate()

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

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

    _data_object = None

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

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

    # PROPERTIES:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        self._data_object = GonioData()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    pass  # end of class
Exemplo n.º 17
0
class R2G3Model(_AbstractProbability):
    r"""
    
    (Restricted) probability model for Reichweite 2 with 3 components.
    
    The (due to restrictions only) 6 independent variables are:
    
    .. math::
        :nowrap:

        \begin{align*}
            & W_{1}
            & P_{111} \text{(if $\nicefrac{1}{2} \leq W_1 < \nicefrac{2}{3}$) or} P_{x1x} \text{(if $\nicefrac{2}{3} \leq W_1 \leq 1)$ with $x \in \left\{ {2,3} \right\}$} \\
            & G_1 = \frac{W_2}{W_2 + W_3}
            & G_2 = \frac{W_{212} + W_{213}}{W_{212} + W_{213} + W_{312} + W_{313}} \\
            & G_3 = \frac{W_{212}}{W_{212} + W_{213}}
            & G_4 = \frac{W_{312}}{W_{312} + W_{313}} \\
        \end{align*}
        
    This model can not describe mixed layers in which the last two components
    occur right after each other in a stack. In other words there is always
    an alternation between (one or more) layers of the first component and a 
    single layer of the second or third component. Therefore, the weight 
    fraction of the first component (:math:`W_1`) needs to be > than 1/2.
    
    The restriction also translates in the following:
    
    .. math::
        :nowrap:
        
        \begin{align*}
            & P_{22} = P_{23} = P_{32} = P_{33} = 0 \\
            & P_{21} = P_{31} = 1 \\
            & \\
            & P_{122} = P_{123} = P_{132} = P_{133} = 0 \\
            & P_{121} = P_{131} = 1 \\
            & \\
            & P_{222} = P_{223} = P_{232} = P_{233} = 0 \\
            & P_{221} = P_{231} = 1 \\
            & \\
            & P_{322} = P_{323} = P_{332} = P_{333} = 0 \\
            & P_{321} = P_{331} = 1 \\
        \end{align*}
    
    Using the above, we can calculate a lot of the weight fractions of stacks:
    
    .. math::
        :nowrap:
    
        \begin{align*}
            & W_{22} = W_{23} = W_{32} = W_{33} 0 \\
            & W_{21} = W_{2} \\
            & W_{31} = W_{3} \\
            & \\
            & W_{122} = W_{123} = W_{132} = W_{133} = 0 \\
            & W_{121} = W_{12} = W_{21} = W_2 \\
            & W_{131} = W_{13} = W_{31} = W_3 \\
            & W_{11} = W_1 - W_{12} - W_{13} \\
            & \\
            & W_{221} = W_{231} = W_{222} = W_{223} = W_{232} = W_{233} = 0 \\
            & W_{331} = W_{331} = W_{322} = W_{323} = W_{332} = W_{333} = 0 \\             
        \end{align*}

    Then the remaining fractions and probablities can be calculated as follows:
    
    .. math::
        :nowrap:
    
        \begin{align*}
            & W_2 = G_1 * (1 - W_1) \\
            & W_3 = 1 - W_1 - W_2 \\
            & \\
            & W_x = W_2 + W_3 &
            & \text{if $W_1 < \nicefrac{2}{3}$:} \\
            & \quad \text{$P_{111}$ is given}\\
            & \quad P_{x1x} = 
            \begin{dcases}
                1 - \frac{W_1 - W_x}{W_x} \cdot (1 - P_{111}, & \text{if $W_x > 0$} \\
                0, & \text{otherwise}
            \end{dcases} \\ 
            & \\
            & \text{if $W_1 \geq \nicefrac{2}{3}$:} \\
            & \quad \text{$P_{x1x}$ is given}\\
            & \quad P_{111} = 
            \begin{dcases}
                1 - \frac{W_x}{W_1 - W_x} \cdot (1 - P_{x1x}, & \text{if $(W_1 - W_x) > 0$} \\
                0, & \text{otherwise}
            \end{dcases} \\
            & \\
            & W_{x1x} = W_x \cdot P_{x1x} \\
            & W_{21x} = G_2 \cdot W_{x1x} \\
            & W_{31x} = W_{x1x} - W_{21x} \\
            & \\
            & W_{212} = G_3 \cdot W_{21x} \\
            & W_{213} = (1 - G_3) \cdot W_{21x} \\
            & W_{211} = W_{21} - W_{212} - W_{213} \\
            & \\
            & W_{312} = G_4 \cdot W_{31x} \\
            & W_{313} = (1 - G_4) \cdot W_{31x} \\
            & W_{311} = W_{31} - W_{312} - W_{313} \\
            & \\
            & W_{111} = W_{11} \cdot P_{111} \\
            & W_{112} = W_{12} - W_{212} - W_{312} \\
            & W_{112} = W_{13} - W_{213} - W_{313} \\
            & \\
            & \text{Calculate the remaining P using:} \\
            & P_{ijk} = 
            \begin{dcases}
                \frac{W_{ijk}}{W_{ij}}, & \text{if $W_{ij} > 0$} \\
                0, & \text{otherwise}
            \end{dcases} \\ 
        \end{align*}
        
    """

    # MODEL METADATA:
    class Meta(_AbstractProbability.Meta):
        store_id = "R2G3Model"

    # PROPERTIES:
    _G = 3
    twothirds = 2.0 / 3.0

    inherit_W1 = BoolProperty(default=False,
                              text="Inherit flag for W1",
                              persistent=True,
                              visible=True,
                              set_action_name="update",
                              mix_with=(SetActionMixin, ))
    W1 = FloatProperty(default=0.8,
                       text="W1 (> 0.5)",
                       math_text=r"$W_1 (> 0.5)$",
                       persistent=True,
                       visible=True,
                       refinable=True,
                       store_private=True,
                       minimum=0.5,
                       maximum=1.0,
                       is_independent=True,
                       inheritable=True,
                       inherit_flag="inherit_W1",
                       inherit_from="parent.based_on.probabilities.W1",
                       set_action_name="update",
                       mix_with=(SetActionMixin, RefinableMixin,
                                 InheritableMixin))

    inherit_P111_or_P212 = BoolProperty(default=False,
                                        text="Inherit flag for P112_or_P211",
                                        persistent=True,
                                        visible=True,
                                        set_action_name="update",
                                        mix_with=(SetActionMixin, ))
    P111_or_P212 = FloatProperty(
        default=0.9,
        text="P111 (W1 < 2/3) or\nPx1x (W1 > 2/3)",
        math_text=r"$P_{111} %s$ or $\newline P_{x1x} %s$" %
        (mt_range(0.5, "W_1", 2.0 / 3.0), mt_range(2.0 / 3.0, "W_1", 1.0)),
        persistent=True,
        visible=True,
        refinable=True,
        store_private=True,
        minimum=0.0,
        maximum=1.0,
        is_independent=True,
        inheritable=True,
        inherit_flag="inherit_P111_or_P212",
        inherit_from="parent.based_on.probabilities.P111_or_P212",
        set_action_name="update",
        mix_with=(SetActionMixin, RefinableMixin, InheritableMixin))

    inherit_G1 = BoolProperty(default=False,
                              text="Inherit flag for G1",
                              persistent=True,
                              visible=True,
                              set_action_name="update",
                              mix_with=(SetActionMixin, ))
    G1 = FloatProperty(default=0.9,
                       text="W2/(W2+W3)",
                       math_text=r"$\large\frac{W_2}{W_3 + W_2}$",
                       persistent=True,
                       visible=True,
                       refinable=True,
                       store_private=True,
                       minimum=0.0,
                       maximum=1.0,
                       is_independent=True,
                       inheritable=True,
                       inherit_flag="inherit_G1",
                       inherit_from="parent.based_on.probabilities.G1",
                       set_action_name="update",
                       mix_with=(SetActionMixin, RefinableMixin,
                                 InheritableMixin))

    inherit_G2 = BoolProperty(default=False,
                              text="Inherit flag for G2",
                              persistent=True,
                              visible=True,
                              set_action_name="update",
                              mix_with=(SetActionMixin, ))
    G2 = FloatProperty(
        default=0.9,
        text="(W212+W213)/(W212+W213+W312+W313)",
        math_text=
        r"$\large\frac{W_{212} + W_{213}}{W_{212} + W_{213} + W_{312} + W_{313}}$",
        persistent=True,
        visible=True,
        refinable=True,
        store_private=True,
        minimum=0.0,
        maximum=1.0,
        is_independent=True,
        inheritable=True,
        inherit_flag="inherit_G2",
        inherit_from="parent.based_on.probabilities.G2",
        set_action_name="update",
        mix_with=(SetActionMixin, RefinableMixin, InheritableMixin))

    inherit_G3 = BoolProperty(default=False,
                              text="Inherit flag for G3",
                              persistent=True,
                              visible=True,
                              set_action_name="update",
                              mix_with=(SetActionMixin, ))
    G3 = FloatProperty(default=0.9,
                       text="W212/(W212+W213)",
                       math_text=r"$\large\frac{W_{212}}{W_{212} + W_{213}}$",
                       persistent=True,
                       visible=True,
                       refinable=True,
                       store_private=True,
                       minimum=0.0,
                       maximum=1.0,
                       is_independent=True,
                       inheritable=True,
                       inherit_flag="inherit_G3",
                       inherit_from="parent.based_on.probabilities.G3",
                       set_action_name="update",
                       mix_with=(SetActionMixin, RefinableMixin,
                                 InheritableMixin))

    inherit_G4 = BoolProperty(default=False,
                              text="Inherit flag for G4",
                              persistent=True,
                              visible=True,
                              set_action_name="update",
                              mix_with=(SetActionMixin, ))
    G4 = FloatProperty(default=0.9,
                       text="W312/(W312+W313)",
                       math_text=r"$\large\frac{W_{312}}{W_{312} + W_{313}}$",
                       persistent=True,
                       visible=True,
                       refinable=True,
                       store_private=True,
                       minimum=0.0,
                       maximum=1.0,
                       is_independent=True,
                       inheritable=True,
                       inherit_flag="inherit_G4",
                       inherit_from="parent.based_on.probabilities.G4",
                       set_action_name="update",
                       mix_with=(SetActionMixin, RefinableMixin,
                                 InheritableMixin))

    # ------------------------------------------------------------
    #      Initialization and other internals
    # ------------------------------------------------------------
    def __init__(self,
                 W1=0.8,
                 P111_or_P212=0.9,
                 G1=0.9,
                 G2=0.9,
                 G3=0.9,
                 G4=0.9,
                 inherit_W1=False,
                 inherit_P111_or_P212=False,
                 inherit_G1=False,
                 inherit_G2=False,
                 inherit_G3=False,
                 inherit_G4=False,
                 *args,
                 **kwargs):
        super(R2G3Model, self).__init__(R=2, *args, **kwargs)

        with self.data_changed.hold():
            self.W1 = not_none(W1, 0.8)
            self.inherit_W1 = inherit_W1
            self.P111_or_P212 = not_none(P111_or_P212, 0.9)
            self.inherit_P111_or_P212 = inherit_P111_or_P212
            self.G1 = not_none(G1, 0.9)
            self.inherit_G1 = inherit_G1
            self.G2 = not_none(G2, 0.9)
            self.inherit_G2 = inherit_G2
            self.G3 = not_none(G3, 0.9)
            self.inherit_G3 = inherit_G3
            self.G4 = not_none(G4, 0.9)
            self.inherit_G4 = inherit_G4

            self.update()

    # ------------------------------------------------------------
    #      Methods & Functions
    # ------------------------------------------------------------
    def update(self):
        with self.monitor_changes():

            # calculate Wx's:
            self.mW[0] = self.W1
            self.mW[1] = (1.0 - self.mW[0]) * self.G1
            self.mW[2] = 1.0 - self.mW[0] - self.mW[1]

            # consequences of restrictions:
            self.mW[1, 1] = 0
            self.mW[1, 2] = 0
            self.mW[2, 1] = 0
            self.mW[2, 2] = 0
            self.mW[0, 1, 0] = self.mW[0, 1] = self.mW[1, 0] = self.mW[1]
            self.mW[0, 2, 0] = self.mW[0, 2] = self.mW[2, 0] = self.mW[2]
            self.mW[0, 0] = self.mW[0] - self.mW[0, 1] - self.mW[0, 2]

            # continue calculations:
            Wx = self.mW[1] + self.mW[2]
            if self.mW[0] < self.twothirds:
                self.mP[0, 0, 0] = self.P111_or_P212
                Px0x = 1 - (self.mW[0] - Wx) / Wx * (
                    1 - self.mP[0, 0, 0]) if Wx != 0 else 0.0
            else:
                Px0x = self.P111_or_P212
                self.mP[0, 0, 0] = 1 - Wx / (self.mW[0] - Wx) * (1 - Px0x) if (
                    self.mW[0] - Wx) != 0 else 0.0

            Wx0x = Wx * Px0x
            W10x = self.G2 * Wx0x
            W20x = Wx0x - W10x

            self.mW[1, 0, 1] = self.G3 * W10x
            self.mW[1, 0, 2] = (1 - self.G3) * W10x
            self.mW[1, 0,
                    0] = self.mW[1, 0] - self.mW[1, 0, 1] - self.mW[1, 0, 2]

            self.mW[2, 0, 1] = self.G4 * W20x
            self.mW[2, 0, 2] = (1 - self.G4) * W20x
            self.mW[2, 0,
                    0] = self.mW[2, 0] - self.mW[2, 0, 1] - self.mW[2, 0, 2]

            self.mW[0, 0, 0] = self.mW[0, 0] * self.mP[0, 0, 0]
            self.mW[0, 0,
                    1] = self.mW[0, 1] - self.mW[1, 0, 1] - self.mW[2, 0, 1]
            self.mW[0, 0,
                    2] = self.mW[0, 2] - self.mW[1, 0, 2] - self.mW[2, 0, 2]

            # Calculate remaining P:
            for i in range(3):
                for j in range(3):
                    for k in range(3):
                        self.mP[i, j, k] = self.mW[i, j, k] / self.mW[
                            i, j] if self.mW[i, j] > 0 else 0.0

            self.solve()
            self.validate()

    pass  # end of class
Exemplo n.º 18
0
class Phase(RefinementGroup, AbstractPhase, metaclass=PyXRDRefinableMeta):

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

    _data_object = None

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

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

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

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

        return self._data_object

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

    # PROPERTIES:

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

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

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

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

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

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

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

    # INHERITABLE PROPERTIES:

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

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

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

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

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

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

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

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

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

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

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

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

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

        with self.data_changed.hold():

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    pass  # end of class
Exemplo n.º 19
0
class _AbstractCSDSDistribution(DataModel,
                                Storable,
                                metaclass=PyXRDRefinableMeta):

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

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

    # PROPERTIES:
    _data_object = None

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

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

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

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

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

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

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

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

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

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

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

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

        self._data_object = CSDSData()

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

        self._update_distribution()

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

    pass  # end of class
Exemplo n.º 20
0
def R0_model_generator(pasG):
    class _R0Meta(_AbstractProbability.Meta):
        store_id = "R0G%dModel" % pasG

    class _BaseR0Model(object):
        """
        Probability model for Reichweite = 0
        (g-1) independent variables:
        
        W0 = W0/sum(W0>Wg)
        W1/sum(W1>Wg)
        W2/sum(W2>Wg)
        etc.
        
    
        Pij = Wj
        ∑W = 1
        ∑P = 1
    
        indexes are NOT zero-based in external property names!
        """

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

            with self.data_changed.hold():
                if self.G > 1 and "W1" in my_kwargs:  # old-style model
                    for i in range(self.G - 1):
                        name = "W%d" % (i + 1)
                        self.mW[i] = not_none(my_kwargs.get(name, None), 0.8)
                        name = "F%d" % (i + 1)
                        setattr(
                            self, name,
                            self.mW[i] / (np.sum(np.diag(self._W)[i:]) or 1.0))
                else:
                    for i in range(self.G - 1):
                        name = "inherit_F%d" % (i + 1)
                        setattr(self, name, my_kwargs.get(name, False))
                        name = "F%d" % (i + 1)
                        setattr(self, name,
                                not_none(my_kwargs.get(name, None), 0.8))

                self.update()

        # ------------------------------------------------------------
        #      Methods & Functions
        # ------------------------------------------------------------
        def update(self):
            with self.monitor_changes():
                if self.G > 1:
                    for i in range(self.G - 1):
                        name = "F%d" % (i + 1)
                        if i > 0:
                            self.mW[i] = getattr(self, name) * (
                                1.0 - np.sum(np.diag(self._W)[0:i]))
                        else:
                            self.mW[i] = getattr(self, name)
                    self.mW[self.G - 1] = 1.0 - np.sum(np.diag(self._W)[:-1])
                else:
                    self.mW[0] = 1.0
                self._P[:] = np.repeat(
                    np.diag(self._W)[np.newaxis, :], self.G, 0)

                self.solve()
                self.validate()

        pass  # end of class

    _dict = dict()
    _dict["Meta"] = _R0Meta

    def set_attribute(name, value):  # @NoSelf
        """Sets an attribute on the class and the dict"""
        _dict[name] = value
        setattr(_BaseR0Model, name, value)

    set_attribute("G", pasG)

    # PROPERTIES:
    for g in range(pasG - 1):
        label = "F%d" % (g + 1)
        text = "W%(g)d/Sum(W%(g)d+...+W%(G)d)" % {'g': g + 1, 'G': pasG}
        math_text = r"$\large\frac{W_{%(g)d}}{\sum_{i=%(g)d}^{%(G)d} W_i}$" % {
            'g': g + 1,
            'G': pasG
        }
        inh_flag = "inherit_F%d" % (g + 1)
        inh_from = "parent.based_on.probabilities.F%d" % (g + 1)

        set_attribute(
            label,
            FloatProperty(default=0.8,
                          text=text,
                          math_text=math_text,
                          refinable=True,
                          persistent=True,
                          visible=True,
                          minimum=0.0,
                          maximum=1.0,
                          inheritable=True,
                          inherit_flag=inh_flag,
                          inherit_from=inh_from,
                          is_independent=True,
                          store_private=True,
                          set_action_name="update",
                          mix_with=(SetActionMixin, RefinableMixin,
                                    InheritableMixin)))

        label = "inherit_F%d" % (g + 1)
        text = "Inherit flag for F%d" % (g + 1)
        set_attribute(
            label,
            BoolProperty(default=False,
                         text=text,
                         refinable=False,
                         persistent=True,
                         visible=True,
                         set_action_name="update",
                         mix_with=(SetActionMixin, )))

    # CREATE TYPE AND REGISTER AS STORABLE:
    cls = type("R0G%dModel" % pasG, (_BaseR0Model, _AbstractProbability),
               _dict)
    storables.register_decorator(cls)

    return cls