예제 #1
0
    def build_right_box(self):
        """Construct the right hand box to pack into the main model-free box.

        @return:    The right hand box element containing all model-free GUI elements (excluding the bitmap) to pack into the main model-free box.
        @rtype:     wx.BoxSizer instance
        """

        # Use a vertical packing of elements.
        box = wx.BoxSizer(wx.VERTICAL)

        # Add the frame title.
        self.add_title(box, "Model-free analysis")

        # Display the data pipe.
        Text_ctrl(box, self, text="The data pipe bundle:", default=self.data.pipe_bundle, tooltip="This is the data pipe bundle associated with this analysis.", editable=False, width_text=self.width_text, width_button=self.width_button, spacer=self.spacer_horizontal)

        # Add the results directory GUI element.
        self.field_results_dir = Text_ctrl(box, self, text="Results directory:", icon=fetch_icon('oxygen.actions.document-open-folder', "16x16"), default=self.data.save_dir, tooltip="The directory in which all automatically created files will be saved.", tooltip_button="Select the results directory.", fn=self.results_directory, button=True, width_text=self.width_text, width_button=self.width_button, spacer=self.spacer_horizontal)

        # Add the spin GUI element.
        self.add_spin_systems(box, self)

        # Add the relaxation data list GUI element, with spacing.
        box.AddSpacer(10)
        self.relax_data = Relax_data_list(gui=self.gui, parent=self, box=box, id=str(self.data_index))
        box.AddSpacer(10)

        # Add the value.set buttons.
        self.add_values(box)
        box.AddSpacer(10)

        # Add the local tau_m models GUI element, with spacing.
        self.local_tm_model_field = Local_tm_list(self, box)
        self.local_tm_model_field.set_value(self.data.local_tm_models)

        # Add the model-free models GUI element, with spacing.
        self.mf_model_field = Mf_list(self, box)
        self.mf_model_field.set_value(self.data.mf_models)

        # The optimisation settings.
        self.grid_inc = Spin_ctrl(box, self, text="Grid search increments:", default=11, min=1, max=100, tooltip="This is the number of increments per dimension of the grid search performed prior to numerical optimisation.", width_text=self.width_text, width_button=self.width_button, spacer=self.spacer_horizontal)
        self.mc_sim_num = Spin_ctrl(box, self, text="Monte Carlo simulation number:", default=500, min=1, max=100000, tooltip="This is the number of Monte Carlo simulations performed for error propagation and analysis.", width_text=self.width_text, width_button=self.width_button, spacer=self.spacer_horizontal)

        # Add maximum iteration selector.
        self.max_iter = Spin_ctrl(box, self, text="Maximum iterations:", default=self.data.max_iter, tooltip="The maximum number of iterations for the protocol.  This is the limit for the global looping over the optimisation of the model-free models, model elimination, model selection and then optimisation of the diffusion tensor.", min=25, max=100, width_text=self.width_text, width_button=self.width_button, spacer=self.spacer_horizontal)

        # The calculation mode.
        self.mode = Text_ctrl(box, self, text="Protocol mode:", default='Fully automated', tooltip="Select if the dauvergne_protocol analysis will be fully automated or whether the individual global models will be optimised separately.", tooltip_button="Open the protocol mode selection window.", icon=fetch_icon('oxygen.actions.system-run', "16x16"), fn=self.mode_dialog, editable=False, button=True, width_text=self.width_text, width_button=self.width_button, spacer=self.spacer_horizontal)

        # Stretchable spacing (with a minimal space).
        box.AddSpacer(30)
        box.AddStretchSpacer()

        # Add the execution GUI element.
        self.button_exec_relax = self.add_execute_analysis(box, self.execute)

        # Return the box.
        return box
예제 #2
0
    def build_right_box(self):
        """Construct the right hand box to pack into the main model-free box.

        @return:    The right hand box element containing all model-free GUI elements (excluding the bitmap) to pack into the main model-free box.
        @rtype:     wx.BoxSizer instance
        """

        # Use a vertical packing of elements.
        box = wx.BoxSizer(wx.VERTICAL)

        # Add the frame title.
        self.add_title(box, "Model-free analysis")

        # Display the data pipe.
        Text_ctrl(box, self, text="The data pipe bundle:", default=self.data.pipe_bundle, tooltip="This is the data pipe bundle associated with this analysis.", editable=False, width_text=self.width_text, width_button=self.width_button, spacer=self.spacer_horizontal)

        # Add the results directory GUI element.
        self.field_results_dir = Text_ctrl(box, self, text="Results directory:", icon=fetch_icon('oxygen.actions.document-open-folder', "16x16"), default=self.data.save_dir, tooltip="The directory in which all automatically created files will be saved.", tooltip_button="Select the results directory.", fn=self.results_directory, button=True, width_text=self.width_text, width_button=self.width_button, spacer=self.spacer_horizontal)

        # Add the spin GUI element.
        self.add_spin_systems(box, self)

        # Add the relaxation data list GUI element, with spacing.
        box.AddSpacer(10)
        self.relax_data = Relax_data_list(gui=self.gui, parent=self, box=box, id=str(self.data_index))
        box.AddSpacer(10)

        # Add the value.set buttons.
        self.add_values(box)
        box.AddSpacer(10)

        # Add the local tau_m models GUI element, with spacing.
        self.local_tm_model_field = Local_tm_list(self, box)
        self.local_tm_model_field.set_value(self.data.local_tm_models)

        # Add the model-free models GUI element, with spacing.
        self.mf_model_field = Mf_list(self, box)
        self.mf_model_field.set_value(self.data.mf_models)

        # The optimisation settings.
        self.grid_inc = Spin_ctrl(box, self, text="Grid search increments:", default=11, min=1, max=100, tooltip="This is the number of increments per dimension of the grid search performed prior to numerical optimisation.", width_text=self.width_text, width_button=self.width_button, spacer=self.spacer_horizontal)
        self.mc_sim_num = Spin_ctrl(box, self, text="Monte Carlo simulation number:", default=500, min=1, max=100000, tooltip="This is the number of Monte Carlo simulations performed for error propagation and analysis.", width_text=self.width_text, width_button=self.width_button, spacer=self.spacer_horizontal)

        # Add maximum iteration selector.
        self.max_iter = Spin_ctrl(box, self, text="Maximum iterations:", default=self.data.max_iter, tooltip="The maximum number of iterations for the protocol.  This is the limit for the global looping over the optimisation of the model-free models, model elimination, model selection and then optimisation of the diffusion tensor.", min=25, max=100, width_text=self.width_text, width_button=self.width_button, spacer=self.spacer_horizontal)

        # The calculation mode.
        self.mode = Text_ctrl(box, self, text="Protocol mode:", default='Fully automated', tooltip="Select if the dauvergne_protocol analysis will be fully automated or whether the individual global models will be optimised separately.", tooltip_button="Open the protocol mode selection window.", icon=fetch_icon('oxygen.actions.system-run', "16x16"), fn=self.mode_dialog, editable=False, button=True, width_text=self.width_text, width_button=self.width_button, spacer=self.spacer_horizontal)

        # Stretchable spacing (with a minimal space).
        box.AddSpacer(30)
        box.AddStretchSpacer()

        # Add the execution GUI element.
        self.button_exec_relax = self.add_execute_analysis(box, self.execute)

        # Return the box.
        return box
예제 #3
0
class Auto_model_free(Base_analysis):
    """The model-free auto-analysis GUI element."""
    def __init__(self,
                 parent,
                 id=-1,
                 pos=wx.Point(-1, -1),
                 size=wx.Size(-1, -1),
                 style=524288,
                 name='scrolledpanel',
                 gui=None,
                 analysis_name=None,
                 pipe_name=None,
                 pipe_bundle=None,
                 uf_exec=[],
                 data_index=None):
        """Build the automatic model-free protocol GUI element.

        @param parent:          The parent wx element.
        @type parent:           wx object
        @keyword id:            The unique ID number.
        @type id:               int
        @keyword pos:           The position.
        @type pos:              wx.Size object
        @keyword size:          The size.
        @type size:             wx.Size object
        @keyword style:         The style.
        @type style:            int
        @keyword name:          The name for the panel.
        @type name:             unicode
        @keyword gui:           The main GUI class.
        @type gui:              gui.relax_gui.Main instance
        @keyword analysis_name: The name of the analysis (the name in the tab part of the notebook).
        @type analysis_name:    str
        @keyword pipe_name:     The name of the original data pipe for this analysis.
        @type pipe_name:        str
        @keyword pipe_bundle:   The name of the data pipe bundle associated with this analysis.
        @type pipe_bundle:      str
        @keyword uf_exec:       The list of user function on_execute methods returned from the new analysis wizard.
        @type uf_exec:          list of methods
        @keyword data_index:    The index of the analysis in the relax data store (set to None if no data currently exists).
        @type data_index:       None or int
        """

        # Store the GUI main class.
        self.gui = gui

        # Init.
        self.init_flag = True

        # New data container.
        if data_index == None:
            # First create the data pipe if not already in existence.
            if not has_pipe(pipe_name):
                self.gui.interpreter.apply('pipe.create',
                                           pipe_name=pipe_name,
                                           pipe_type='mf',
                                           bundle=pipe_bundle)

            # Create the data pipe bundle if needed.
            if not has_bundle(pipe_bundle):
                self.gui.interpreter.apply('pipe.bundle',
                                           bundle=pipe_bundle,
                                           pipe=pipe_name)

            # Generate a storage container in the relax data store, and alias it for easy access.
            data_index = ds.relax_gui.analyses.add('model-free')

            # Store the analysis and pipe names.
            ds.relax_gui.analyses[data_index].analysis_name = analysis_name
            ds.relax_gui.analyses[data_index].pipe_name = pipe_name
            ds.relax_gui.analyses[data_index].pipe_bundle = pipe_bundle

            # Initialise the variables.
            ds.relax_gui.analyses[data_index].grid_inc = None
            ds.relax_gui.analyses[data_index].diff_tensor_grid_inc = {
                'sphere': 11,
                'prolate': 11,
                'oblate': 11,
                'ellipsoid': 6
            }
            ds.relax_gui.analyses[data_index].mc_sim_num = None
            ds.relax_gui.analyses[
                data_index].save_dir = self.gui.system_cwd_path
            ds.relax_gui.analyses[data_index].local_tm_models = [
                'tm0', 'tm1', 'tm2', 'tm3', 'tm4', 'tm5', 'tm6', 'tm7', 'tm8',
                'tm9'
            ]
            ds.relax_gui.analyses[data_index].mf_models = [
                'm0', 'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9'
            ]
            ds.relax_gui.analyses[data_index].max_iter = 30

        # Error checking.
        if ds.relax_gui.analyses[data_index].pipe_bundle == None:
            raise RelaxError("The pipe bundle must be supplied.")

        # Alias the data.
        self.data = ds.relax_gui.analyses[data_index]
        self.data_index = data_index

        # Backward compatibility.
        if not hasattr(self.data, 'local_tm_models'):
            self.data.local_tm_models = [
                'tm0', 'tm1', 'tm2', 'tm3', 'tm4', 'tm5', 'tm6', 'tm7', 'tm8',
                'tm9'
            ]
        if not hasattr(self.data, 'mf_models'):
            self.data.mf_models = [
                'm0', 'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9'
            ]

        # Initialise the mode selection window.
        self.mode_win = Protocol_mode_sel_window()

        # Register the method for updating the spin count for the completion of user functions.
        self.observer_register()

        # Execute the base class method to build the panel.
        super(Auto_model_free, self).__init__(parent,
                                              id=id,
                                              pos=pos,
                                              size=size,
                                              style=style,
                                              name=name)

    def _about(self, event=None):
        """The about window.

        @keyword event: The wx event.
        @type event:    wx event
        """

        # Initialise the dialog.
        self.about_dialog = About_window(self)

        # Show the dialog.
        if status.show_gui:
            self.about_dialog.Show()

    def activate(self):
        """Activate or deactivate certain elements of the analysis in response to the execution lock."""

        # Flag for enabling or disabling the elements.
        enable = False
        if not status.exec_lock.locked():
            enable = True

        # Activate or deactivate the elements.
        wx.CallAfter(self.field_results_dir.Enable, enable)
        wx.CallAfter(self.spin_systems.Enable, enable)
        wx.CallAfter(self.relax_data.Enable, enable)
        wx.CallAfter(self.button_dipole_pair.Enable, enable)
        wx.CallAfter(self.button_csa.Enable, enable)
        wx.CallAfter(self.button_isotope_heteronuc.Enable, enable)
        wx.CallAfter(self.button_isotope_proton.Enable, enable)
        wx.CallAfter(self.local_tm_model_field.Enable, enable)
        wx.CallAfter(self.mf_model_field.Enable, enable)
        wx.CallAfter(self.grid_inc.Enable, enable)
        wx.CallAfter(self.mc_sim_num.Enable, enable)
        wx.CallAfter(self.max_iter.Enable, enable)
        wx.CallAfter(self.mode.Enable, enable)
        wx.CallAfter(self.button_exec_relax.Enable, enable)

    def add_values(self, box):
        """Create and add the value.set buttons for the model-free analysis.

        @param box:     The box element to pack the GUI element into.
        @type box:      wx.BoxSizer instance
        """

        # Sizer.
        sizer = wx.BoxSizer(wx.HORIZONTAL)

        # Dipole-dipole relaxation setup button.
        self.button_dipole_pair = wx.lib.buttons.ThemedGenBitmapTextButton(
            self, -1, None, " Dipolar relaxation")
        self.button_dipole_pair.SetBitmapLabel(
            wx.Bitmap(fetch_icon("relax.dipole_pair", "22x22"),
                      wx.BITMAP_TYPE_ANY))
        self.button_dipole_pair.SetFont(font.normal)
        self.button_dipole_pair.SetSize((-1, 25))
        self.button_dipole_pair.SetToolTip(
            wx.ToolTip(
                "Define the magnetic dipole-dipole relaxation mechanism."))
        self.gui.Bind(wx.EVT_BUTTON, self.setup_dipole_pair,
                      self.button_dipole_pair)
        sizer.Add(self.button_dipole_pair, 1, wx.ALL | wx.EXPAND, 0)

        # CSA button.
        self.button_csa = wx.lib.buttons.ThemedGenBitmapTextButton(
            self, -1, None, " CSA relaxation")
        self.button_csa.SetBitmapLabel(
            wx.Bitmap(fetch_icon("relax.align_tensor", "22x22"),
                      wx.BITMAP_TYPE_ANY))
        self.button_csa.SetFont(font.normal)
        self.button_csa.SetSize((-1, 25))
        self.button_csa.SetToolTip(
            wx.ToolTip(
                "Define the Chemical Shift Anisotropy (CSA) relaxation mechanism via the value.set user function."
            ))
        self.gui.Bind(wx.EVT_BUTTON, self.value_set_csa, self.button_csa)
        sizer.Add(self.button_csa, 1, wx.ALL | wx.EXPAND, 0)

        # Isotope type button (heteronucleus).
        self.button_isotope_heteronuc = wx.lib.buttons.ThemedGenBitmapTextButton(
            self, -1, None, " X isotope")
        self.button_isotope_heteronuc.SetBitmapLabel(
            wx.Bitmap(fetch_icon("relax.nuclear_symbol", "22x22"),
                      wx.BITMAP_TYPE_ANY))
        self.button_isotope_heteronuc.SetFont(font.normal)
        self.button_isotope_heteronuc.SetSize((-1, 25))
        self.button_isotope_heteronuc.SetToolTip(
            wx.ToolTip(
                "Set the nuclear isotope types of the heteronuclear spins via the spin.isotope user function."
            ))
        self.gui.Bind(wx.EVT_BUTTON, self.spin_isotope_heteronuc,
                      self.button_isotope_heteronuc)
        sizer.Add(self.button_isotope_heteronuc, 1, wx.ALL | wx.EXPAND, 0)

        # Isotope type button (proton).
        self.button_isotope_proton = wx.lib.buttons.ThemedGenBitmapTextButton(
            self, -1, None, " H isotope")
        self.button_isotope_proton.SetBitmapLabel(
            wx.Bitmap(fetch_icon("relax.nuclear_symbol", "22x22"),
                      wx.BITMAP_TYPE_ANY))
        self.button_isotope_proton.SetFont(font.normal)
        self.button_isotope_proton.SetSize((-1, 25))
        self.button_isotope_proton.SetToolTip(
            wx.ToolTip(
                "Set the nuclear isotope types of the proton spins via the spin.isotope user function."
            ))
        self.gui.Bind(wx.EVT_BUTTON, self.spin_isotope_proton,
                      self.button_isotope_proton)
        sizer.Add(self.button_isotope_proton, 1, wx.ALL | wx.EXPAND, 0)

        # Add the element to the box.
        box.Add(sizer, 0, wx.ALL | wx.EXPAND, 0)

    def assemble_data(self):
        """Assemble the data required for the auto-analysis.

        See the docstring for auto_analyses.dauvernge_protocol for details.  All data is taken from the relax data store, so data upload from the GUI to there must have been previously performed.

        @return:    A container with all the data required for the auto-analysis.
        @rtype:     class instance, list of str
        """

        # The data container.
        data = Container()
        missing = []

        # The pipe name and bundle.
        data.pipe_name = self.data.pipe_name
        data.pipe_bundle = self.data.pipe_bundle

        # The model-free models (do not change these unless absolutely necessary).
        data.local_tm_models = self.local_tm_model_field.GetValue()
        data.mf_models = self.mf_model_field.GetValue()

        # Automatic looping over all rounds until convergence (must be a boolean value of True or False).
        data.conv_loop = True

        # Increment size.
        data.inc = gui_to_int(self.grid_inc.GetValue())
        if hasattr(self.data, 'diff_tensor_grid_inc'):
            data.diff_tensor_grid_inc = self.data.diff_tensor_grid_inc
        else:
            data.diff_tensor_grid_inc = {
                'sphere': 11,
                'prolate': 11,
                'oblate': 11,
                'ellipsoid': 6
            }

        # The number of Monte Carlo simulations to be used for error analysis at the end of the analysis.
        data.mc_sim_num = gui_to_int(self.mc_sim_num.GetValue())

        # Number of maximum iterations.
        data.max_iter = self.data.max_iter

        # Results directory.
        data.save_dir = self.data.save_dir

        # Check if sequence data is loaded
        if not exists_mol_res_spin_data():
            missing.append("Sequence data")

        # Relaxation data.
        if not hasattr(cdp, 'ri_ids') or len(cdp.ri_ids) == 0:
            missing.append("Relaxation data")

        # Insufficient data.
        if hasattr(cdp, 'ri_ids') and len(cdp.ri_ids) <= 3:
            missing.append(
                "Insufficient relaxation data, 4 or more data sets are essential for the execution of the dauvergne_protocol auto-analysis. Check that you have entered data for a least two spectrometer fields."
            )

        # Interatomic data containers.
        if not hasattr(cdp, 'interatomic') or len(cdp.interatomic) == 0:
            missing.append(
                "Interatomic data (for the dipole-dipole interaction)")

        # Get the mode.
        mode = gui_to_str(self.mode.GetValue())

        # Solve for all global models.
        if mode == 'Fully automated':
            # The global model list.
            data.global_models = [
                'local_tm', 'sphere', 'prolate', 'oblate', 'ellipsoid', 'final'
            ]

        # Any global model selected.
        else:
            data.global_models = [mode]

        # Check for vectors.
        vector_check = False
        if 'prolate' in data.global_models or 'oblate' in data.global_models or 'ellipsoid' in data.global_models:
            vector_check = True

        # Spin variables.
        for spin, spin_id in spin_loop(return_id=True):
            # Skip deselected spins.
            if not spin.select:
                continue

            # The message skeleton.
            msg = "Spin '%s' - %s (try the %s user function)." % (spin_id,
                                                                  "%s", "%s")

            # Test if the nuclear isotope type has been set.
            if not hasattr(spin, 'isotope') or spin.isotope == None:
                missing.append(msg % ("nuclear isotope data", "spin.isotope"))

            # Test if the CSA value has been set for the heteronuclei.
            if (hasattr(spin, 'isotope') and spin.isotope
                    in ['15N', '13C']) and (not hasattr(spin, 'csa')
                                            or spin.csa == None):
                missing.append(msg % ("CSA data", "value.set"))

        # Interatomic data container variables.
        for interatom in interatomic_loop():
            # Get the spin containers.
            spin1 = return_spin(spin_hash=interatom._spin_hash1)
            spin2 = return_spin(spin_hash=interatom._spin_hash2)

            # Skip deselected spins.
            if not spin1.select:
                continue
            if not spin2.select:
                continue

            # The message skeleton.
            msg = "Spin pair '%s' and '%s' - %s (try the %s user function)." % (
                interatom.spin_id1, interatom.spin_id2, "%s", "%s")

            # Test if the interatomic distance has been set.
            if not hasattr(interatom, 'r') or interatom.r == None:
                missing.append(msg % ("bond length data", "value.set"))

            # Test if the unit vectors have been loaded.
            if vector_check and (not hasattr(interatom, 'vector')
                                 or interatom.vector is None):
                missing.append(msg %
                               ("unit vectors", "interatom.unit_vectors"))

        # Return the container and list of missing data.
        return data, missing

    def build_left_box(self):
        """Construct the left hand box to pack into the main model-free box.

        @return:    The left hand box element containing the bitmap and about button to pack into the main model-free box.
        @rtype:     wx.BoxSizer instance
        """

        # Build the left hand box.
        left_box = wx.BoxSizer(wx.VERTICAL)

        # The images.
        bitmaps = [
            ANALYSIS_IMAGE_PATH + "model_free" + sep +
            "model_free_200x200.png", IMAGE_PATH + 'modelfree.png'
        ]

        # Add the model-free bitmap picture.
        for i in range(len(bitmaps)):
            # The bitmap.
            bitmap = wx.StaticBitmap(self, -1, bitmap_setup(bitmaps[i]))

            # Add it.
            left_box.Add(bitmap, 0, wx.ALL, 0)

        # A spacer.
        left_box.AddStretchSpacer()

        # A button sizer, with some initial spacing.
        button_sizer = wx.BoxSizer(wx.HORIZONTAL)

        # An about button.
        button = wx.lib.buttons.ThemedGenBitmapTextButton(
            self, -1, None, "About")
        button.SetBitmapLabel(
            wx.Bitmap(fetch_icon('oxygen.actions.help-about', "22x22"),
                      wx.BITMAP_TYPE_ANY))
        button.SetFont(font.normal)
        button.SetToolTip(
            wx.ToolTip("Information about this automatic analysis"))

        # Bind the click.
        self.Bind(wx.EVT_BUTTON, self._about, button)

        # A cursor for the button.
        if dep_check.wx_classic:
            cursor = wx.StockCursor(wx.CURSOR_QUESTION_ARROW)
        else:
            cursor = wx.Cursor(wx.CURSOR_QUESTION_ARROW)
        button.SetCursor(cursor)

        # Pack the button.
        button_sizer.Add(button, 0, 0, 0)
        left_box.Add(button_sizer, 0, wx.ALL, 0)

        # Return the packed box.
        return left_box

    def build_right_box(self):
        """Construct the right hand box to pack into the main model-free box.

        @return:    The right hand box element containing all model-free GUI elements (excluding the bitmap) to pack into the main model-free box.
        @rtype:     wx.BoxSizer instance
        """

        # Use a vertical packing of elements.
        box = wx.BoxSizer(wx.VERTICAL)

        # Add the frame title.
        self.add_title(box, "Model-free analysis")

        # Display the data pipe.
        Text_ctrl(
            box,
            self,
            text="The data pipe bundle:",
            default=self.data.pipe_bundle,
            tooltip=
            "This is the data pipe bundle associated with this analysis.",
            editable=False,
            width_text=self.width_text,
            width_button=self.width_button,
            spacer=self.spacer_horizontal)

        # Add the results directory GUI element.
        self.field_results_dir = Text_ctrl(
            box,
            self,
            text="Results directory:",
            icon=fetch_icon('oxygen.actions.document-open-folder', "16x16"),
            default=self.data.save_dir,
            tooltip=
            "The directory in which all automatically created files will be saved.",
            tooltip_button="Select the results directory.",
            fn=self.results_directory,
            button=True,
            width_text=self.width_text,
            width_button=self.width_button,
            spacer=self.spacer_horizontal)

        # Add the spin GUI element.
        self.add_spin_systems(box, self)

        # Add the relaxation data list GUI element, with spacing.
        box.AddSpacer(10)
        self.relax_data = Relax_data_list(gui=self.gui,
                                          parent=self,
                                          box=box,
                                          id=str(self.data_index))
        box.AddSpacer(10)

        # Add the value.set buttons.
        self.add_values(box)
        box.AddSpacer(10)

        # Add the local tau_m models GUI element, with spacing.
        self.local_tm_model_field = Local_tm_list(self, box)
        self.local_tm_model_field.set_value(self.data.local_tm_models)

        # Add the model-free models GUI element, with spacing.
        self.mf_model_field = Mf_list(self, box)
        self.mf_model_field.set_value(self.data.mf_models)

        # The optimisation settings.
        self.grid_inc = Spin_ctrl(
            box,
            self,
            text="Grid search increments:",
            default=11,
            min=1,
            max=100,
            tooltip=
            "This is the number of increments per dimension of the grid search performed prior to numerical optimisation.",
            width_text=self.width_text,
            width_button=self.width_button,
            spacer=self.spacer_horizontal)
        self.mc_sim_num = Spin_ctrl(
            box,
            self,
            text="Monte Carlo simulation number:",
            default=500,
            min=1,
            max=100000,
            tooltip=
            "This is the number of Monte Carlo simulations performed for error propagation and analysis.",
            width_text=self.width_text,
            width_button=self.width_button,
            spacer=self.spacer_horizontal)

        # Add maximum iteration selector.
        self.max_iter = Spin_ctrl(
            box,
            self,
            text="Maximum iterations:",
            default=self.data.max_iter,
            tooltip=
            "The maximum number of iterations for the protocol.  This is the limit for the global looping over the optimisation of the model-free models, model elimination, model selection and then optimisation of the diffusion tensor.",
            min=25,
            max=100,
            width_text=self.width_text,
            width_button=self.width_button,
            spacer=self.spacer_horizontal)

        # The calculation mode.
        self.mode = Text_ctrl(
            box,
            self,
            text="Protocol mode:",
            default='Fully automated',
            tooltip=
            "Select if the dauvergne_protocol analysis will be fully automated or whether the individual global models will be optimised separately.",
            tooltip_button="Open the protocol mode selection window.",
            icon=fetch_icon('oxygen.actions.system-run', "16x16"),
            fn=self.mode_dialog,
            editable=False,
            button=True,
            width_text=self.width_text,
            width_button=self.width_button,
            spacer=self.spacer_horizontal)

        # Stretchable spacing (with a minimal space).
        box.AddSpacer(30)
        box.AddStretchSpacer()

        # Add the execution GUI element.
        self.button_exec_relax = self.add_execute_analysis(box, self.execute)

        # Return the box.
        return box

    def delete(self):
        """Unregister the spin count from the user functions."""

        # Unregister the observer methods.
        self.observer_register(remove=True)

        # Clean up the relaxation data list object.
        self.relax_data.delete()

        # Destroy the dipole-dipole interaction wizard.
        if hasattr(self, 'dipole_wizard'):
            self.dipole_wizard.Destroy()
            del self.dipole_wizard

        # Destroy the mode selection window.
        self.mode_win.Destroy()
        del self.mode_win

        # Destroy the model list windows.
        self.local_tm_model_field.model_win.Destroy()
        del self.local_tm_model_field
        self.mf_model_field.model_win.Destroy()
        del self.mf_model_field

        # Destroy the missing data dialog, if present.
        if hasattr(self, 'missing_data'):
            self.missing_data.Destroy()
            del self.missing_data

    def execute(self, event=None):
        """Set up, execute, and process the automatic model-free protocol.

        @keyword event: The wx event.
        @type event:    wx event
        """

        # Flush the GUI interpreter internal queue to make sure all user functions are complete.
        self.gui.interpreter.flush()

        # relax execution lock.
        if status.exec_lock.locked():
            error_message("relax is currently executing.",
                          "relax execution lock")
            event.Skip()
            return

        # User warning to close windows.
        self.gui.close_windows()

        # Synchronise the frame data to the relax data store.
        self.sync_ds(upload=True)

        # Assemble all the data needed for the auto-analysis.
        data, missing = self.assemble_data()

        # Missing data.
        if len(missing):
            self.missing_data = Missing_data(missing)
            return

        # Display the relax controller, and go to the end of the log window.
        self.gui.show_controller(None)
        self.gui.controller.log_panel.on_goto_end(None)

        # Start the thread.
        self.thread = Execute_mf(self.gui, data, self.data_index)
        self.thread.start()

        # Terminate the event.
        event.Skip()

    def mode_dialog(self, event=None):
        """The calculation mode selection.

        @keyword event: The wx event.
        @type event:    wx event
        """

        # Show the model selector window.
        if status.show_gui:
            self.mode_win.ShowModal()

        # Set the model.
        self.mode.SetValue(str_to_gui(self.mode_win.select))

    def observer_register(self, remove=False):
        """Register and unregister methods with the observer objects.

        @keyword remove:    If set to True, then the methods will be unregistered.
        @type remove:       False
        """

        # Register.
        if not remove:
            status.observers.gui_uf.register(self.data.pipe_bundle,
                                             self.update_spin_count,
                                             method_name='update_spin_count')
            status.observers.exec_lock.register(self.data.pipe_bundle,
                                                self.activate,
                                                method_name='activate')

        # Unregister.
        else:
            # The model-free methods.
            status.observers.gui_uf.unregister(self.data.pipe_bundle)
            status.observers.exec_lock.unregister(self.data.pipe_bundle)

            # The embedded objects methods.
            self.relax_data.observer_register(remove=True)

    def results_directory(self, event=None):
        """The results directory selection.

        @keyword event: The wx event.
        @type event:    wx event
        """

        # The dialog.
        dialog = RelaxDirDialog(parent=self,
                                message='Select the results directory',
                                defaultPath=self.field_results_dir.GetValue())

        # Show the dialog and catch if no file has been selected.
        if status.show_gui and dialog.ShowModal() != wx.ID_OK:
            # Don't do anything.
            return

        # The path (don't do anything if not set).
        path = gui_to_str(dialog.get_path())
        if not path:
            return

        # Store the path.
        self.data.save_dir = path

        # Place the path in the text box.
        self.field_results_dir.SetValue(str_to_gui(path))

    def setup_dipole_pair(self, event=None):
        """Create the wizard for the dipole-dipole interaction.

        @keyword event: The wx event.
        @type event:    wx event
        """

        # Change the cursor to busy.
        wx.BeginBusyCursor()

        # Destroy the dipole-dipole interaction wizard, if it exists.
        if hasattr(self, 'dipole_wizard'):
            self.dipole_wizard.Destroy()

        # Create the wizard.
        self.dipole_wizard = Wiz_window(
            parent=self.gui,
            size_x=1000,
            size_y=750,
            title="Dipole-dipole interaction setup")

        # Structural data.
        if not hasattr(cdp, 'structure'):
            # Create the PDB reading page.
            page = uf_store['structure.read_pdb'].create_page(
                self.dipole_wizard, sync=True)
            self.dipole_wizard.add_page(page, skip_button=True)

            # Create the position reading page.
            page = uf_store['structure.get_pos'].create_page(
                self.dipole_wizard, sync=True)
            self.dipole_wizard.add_page(page, skip_button=True)

        # Create the interatom.define page.
        page = uf_store['interatom.define'].create_page(self.dipole_wizard,
                                                        sync=True)
        page.SetValue('spin_id1', '@N')
        page.SetValue('spin_id2', '@H')
        self.dipole_wizard.add_page(page)

        # Create the interatom.set_dist page.
        page = uf_store['interatom.set_dist'].create_page(self.dipole_wizard,
                                                          sync=True)
        page.SetValue('spin_id1', '@N*')
        page.SetValue('spin_id2', '@H*')
        page.SetValue('ave_dist', NH_BOND_LENGTH)
        self.dipole_wizard.add_page(page)

        # Create the interatom.unit_vectors page.
        page = uf_store['interatom.unit_vectors'].create_page(
            self.dipole_wizard, sync=True)
        self.dipole_wizard.add_page(page)

        # Reset the cursor.
        if wx.IsBusy():
            wx.EndBusyCursor()

        # Execute the wizard.
        self.dipole_wizard.run()

    def spin_isotope_heteronuc(self, event=None):
        """Set the nuclear isotope types of the heteronuclear spins via the spin.isotope user function.

        @keyword event: The wx event.
        @type event:    wx event
        """

        # Call the user function.
        uf_store['spin.isotope'](isotope='15N', spin_id='@N*')

    def spin_isotope_proton(self, event=None):
        """Set the nuclear isotope types of the proton spins via the spin.isotope user function.

        @keyword event: The wx event.
        @type event:    wx event
        """

        # Call the user function.
        uf_store['spin.isotope'](isotope='1H', spin_id='@H*')

    def sync_ds(self, upload=False):
        """Synchronise the analysis frame and the relax data store, both ways.

        This method allows the frame information to be uploaded into the relax data store, or for the information in the relax data store to be downloaded by the frame.

        @keyword upload:    A flag which if True will cause the frame to send data to the relax data store.  If False, data will be downloaded from the relax data store to update the frame.
        @type upload:       bool
        """

        # The local tau_m models to use.
        if upload:
            self.data.local_tm_models = self.local_tm_model_field.GetValue()
        else:
            self.local_tm_model_field.set_value(self.data.local_tm_models)

        # The model-free models to use.
        if upload:
            self.data.mf_models = self.mf_model_field.GetValue()
        else:
            self.mf_model_field.set_value(self.data.mf_models)

        # The grid incs.
        if upload:
            self.data.grid_inc = gui_to_int(self.grid_inc.GetValue())
        elif hasattr(self.data, 'grid_inc'):
            self.grid_inc.SetValue(int(self.data.grid_inc))

        # The MC sim number.
        if upload:
            self.data.mc_sim_num = gui_to_int(self.mc_sim_num.GetValue())
        elif hasattr(self.data, 'mc_sim_num'):
            self.mc_sim_num.SetValue(int(self.data.mc_sim_num))

        # The results directory.
        if upload:
            self.data.save_dir = str(self.field_results_dir.GetValue())
        else:
            self.field_results_dir.SetValue(str_to_gui(self.data.save_dir))

        # Maximum iterations.
        if upload:
            self.data.max_iter = gui_to_int(self.max_iter.GetValue())
        else:
            self.max_iter.SetValue(int(self.data.max_iter))

    def value_set_csa(self, event=None):
        """Set the CSA via the value.set uf.

        @keyword event: The wx event.
        @type event:    wx event
        """

        # Get the default value.
        api = return_api()
        val = api.default_value('csa')

        # Call the user function.
        uf_store['value.set'](val=val, param='csa', spin_id='@N*')
예제 #4
0
class Auto_model_free(Base_analysis):
    """The model-free auto-analysis GUI element."""

    def __init__(self, parent, id=-1, pos=wx.Point(-1, -1), size=wx.Size(-1, -1), style=524288, name='scrolledpanel', gui=None, analysis_name=None, pipe_name=None, pipe_bundle=None, uf_exec=[], data_index=None):
        """Build the automatic model-free protocol GUI element.

        @param parent:          The parent wx element.
        @type parent:           wx object
        @keyword id:            The unique ID number.
        @type id:               int
        @keyword pos:           The position.
        @type pos:              wx.Size object
        @keyword size:          The size.
        @type size:             wx.Size object
        @keyword style:         The style.
        @type style:            int
        @keyword name:          The name for the panel.
        @type name:             unicode
        @keyword gui:           The main GUI class.
        @type gui:              gui.relax_gui.Main instance
        @keyword analysis_name: The name of the analysis (the name in the tab part of the notebook).
        @type analysis_name:    str
        @keyword pipe_name:     The name of the original data pipe for this analysis.
        @type pipe_name:        str
        @keyword pipe_bundle:   The name of the data pipe bundle associated with this analysis.
        @type pipe_bundle:      str
        @keyword uf_exec:       The list of user function on_execute methods returned from the new analysis wizard.
        @type uf_exec:          list of methods
        @keyword data_index:    The index of the analysis in the relax data store (set to None if no data currently exists).
        @type data_index:       None or int
        """

        # Store the GUI main class.
        self.gui = gui

        # Init.
        self.init_flag = True

        # New data container.
        if data_index == None:
            # First create the data pipe if not already in existence.
            if not has_pipe(pipe_name):
                self.gui.interpreter.apply('pipe.create', pipe_name=pipe_name, pipe_type='mf', bundle=pipe_bundle)

            # Create the data pipe bundle if needed.
            if not has_bundle(pipe_bundle):
                self.gui.interpreter.apply('pipe.bundle', bundle=pipe_bundle, pipe=pipe_name)

            # Generate a storage container in the relax data store, and alias it for easy access.
            data_index = ds.relax_gui.analyses.add('model-free')

            # Store the analysis and pipe names.
            ds.relax_gui.analyses[data_index].analysis_name = analysis_name
            ds.relax_gui.analyses[data_index].pipe_name = pipe_name
            ds.relax_gui.analyses[data_index].pipe_bundle = pipe_bundle

            # Initialise the variables.
            ds.relax_gui.analyses[data_index].grid_inc = None
            ds.relax_gui.analyses[data_index].diff_tensor_grid_inc = {'sphere': 11, 'prolate': 11, 'oblate': 11, 'ellipsoid': 6}
            ds.relax_gui.analyses[data_index].mc_sim_num = None
            ds.relax_gui.analyses[data_index].save_dir = self.gui.system_cwd_path
            ds.relax_gui.analyses[data_index].local_tm_models = ['tm0', 'tm1', 'tm2', 'tm3', 'tm4', 'tm5', 'tm6', 'tm7', 'tm8', 'tm9']
            ds.relax_gui.analyses[data_index].mf_models = ['m0', 'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9']
            ds.relax_gui.analyses[data_index].max_iter = 30

        # Error checking.
        if ds.relax_gui.analyses[data_index].pipe_bundle == None:
            raise RelaxError("The pipe bundle must be supplied.")

        # Alias the data.
        self.data = ds.relax_gui.analyses[data_index]
        self.data_index = data_index

        # Backward compatibility.
        if not hasattr(self.data, 'local_tm_models'):
            self.data.local_tm_models = ['tm0', 'tm1', 'tm2', 'tm3', 'tm4', 'tm5', 'tm6', 'tm7', 'tm8', 'tm9']
        if not hasattr(self.data, 'mf_models'):
            self.data.mf_models = ['m0', 'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9']

        # Initialise the mode selection window.
        self.mode_win = Protocol_mode_sel_window()

        # Register the method for updating the spin count for the completion of user functions.
        self.observer_register()

        # Execute the base class method to build the panel.
        super(Auto_model_free, self).__init__(parent, id=id, pos=pos, size=size, style=style, name=name)


    def _about(self, event=None):
        """The about window.

        @keyword event: The wx event.
        @type event:    wx event
        """

        # Initialise the dialog.
        self.about_dialog = About_window(self)

        # Show the dialog.
        if status.show_gui:
            self.about_dialog.Show()


    def activate(self):
        """Activate or deactivate certain elements of the analysis in response to the execution lock."""

        # Flag for enabling or disabling the elements.
        enable = False
        if not status.exec_lock.locked():
            enable = True

        # Activate or deactivate the elements.
        wx.CallAfter(self.field_results_dir.Enable, enable)
        wx.CallAfter(self.spin_systems.Enable, enable)
        wx.CallAfter(self.relax_data.Enable, enable)
        wx.CallAfter(self.button_dipole_pair.Enable, enable)
        wx.CallAfter(self.button_csa.Enable, enable)
        wx.CallAfter(self.button_isotope_heteronuc.Enable, enable)
        wx.CallAfter(self.button_isotope_proton.Enable, enable)
        wx.CallAfter(self.local_tm_model_field.Enable, enable)
        wx.CallAfter(self.mf_model_field.Enable, enable)
        wx.CallAfter(self.grid_inc.Enable, enable)
        wx.CallAfter(self.mc_sim_num.Enable, enable)
        wx.CallAfter(self.max_iter.Enable, enable)
        wx.CallAfter(self.mode.Enable, enable)
        wx.CallAfter(self.button_exec_relax.Enable, enable)


    def add_values(self, box):
        """Create and add the value.set buttons for the model-free analysis.

        @param box:     The box element to pack the GUI element into.
        @type box:      wx.BoxSizer instance
        """

        # Sizer.
        sizer = wx.BoxSizer(wx.HORIZONTAL)

        # Dipole-dipole relaxation setup button.
        self.button_dipole_pair = wx.lib.buttons.ThemedGenBitmapTextButton(self, -1, None, " Dipolar relaxation")
        self.button_dipole_pair.SetBitmapLabel(wx.Bitmap(fetch_icon("relax.dipole_pair", "22x22"), wx.BITMAP_TYPE_ANY))
        self.button_dipole_pair.SetFont(font.normal)
        self.button_dipole_pair.SetSize((-1, 25))
        self.button_dipole_pair.SetToolTipString("Define the magnetic dipole-dipole relaxation mechanism.")
        self.gui.Bind(wx.EVT_BUTTON, self.setup_dipole_pair, self.button_dipole_pair)
        sizer.Add(self.button_dipole_pair, 1, wx.ALL|wx.EXPAND, 0)

        # CSA button.
        self.button_csa = wx.lib.buttons.ThemedGenBitmapTextButton(self, -1, None, " CSA relaxation")
        self.button_csa.SetBitmapLabel(wx.Bitmap(fetch_icon("relax.align_tensor", "22x22"), wx.BITMAP_TYPE_ANY))
        self.button_csa.SetFont(font.normal)
        self.button_csa.SetSize((-1, 25))
        self.button_csa.SetToolTipString("Define the Chemical Shift Anisotropy (CSA) relaxation mechanism via the value.set user function.")
        self.gui.Bind(wx.EVT_BUTTON, self.value_set_csa, self.button_csa)
        sizer.Add(self.button_csa, 1, wx.ALL|wx.EXPAND, 0)

        # Isotope type button (heteronucleus).
        self.button_isotope_heteronuc = wx.lib.buttons.ThemedGenBitmapTextButton(self, -1, None, " X isotope")
        self.button_isotope_heteronuc.SetBitmapLabel(wx.Bitmap(fetch_icon("relax.nuclear_symbol", "22x22"), wx.BITMAP_TYPE_ANY))
        self.button_isotope_heteronuc.SetFont(font.normal)
        self.button_isotope_heteronuc.SetSize((-1, 25))
        self.button_isotope_heteronuc.SetToolTipString("Set the nuclear isotope types of the heteronuclear spins via the spin.isotope user function.")
        self.gui.Bind(wx.EVT_BUTTON, self.spin_isotope_heteronuc, self.button_isotope_heteronuc)
        sizer.Add(self.button_isotope_heteronuc, 1, wx.ALL|wx.EXPAND, 0)

        # Isotope type button (proton).
        self.button_isotope_proton = wx.lib.buttons.ThemedGenBitmapTextButton(self, -1, None, " H isotope")
        self.button_isotope_proton.SetBitmapLabel(wx.Bitmap(fetch_icon("relax.nuclear_symbol", "22x22"), wx.BITMAP_TYPE_ANY))
        self.button_isotope_proton.SetFont(font.normal)
        self.button_isotope_proton.SetSize((-1, 25))
        self.button_isotope_proton.SetToolTipString("Set the nuclear isotope types of the proton spins via the spin.isotope user function.")
        self.gui.Bind(wx.EVT_BUTTON, self.spin_isotope_proton, self.button_isotope_proton)
        sizer.Add(self.button_isotope_proton, 1, wx.ALL|wx.EXPAND, 0)

        # Add the element to the box.
        box.Add(sizer, 0, wx.ALL|wx.EXPAND, 0)


    def assemble_data(self):
        """Assemble the data required for the auto-analysis.

        See the docstring for auto_analyses.dauvernge_protocol for details.  All data is taken from the relax data store, so data upload from the GUI to there must have been previously performed.

        @return:    A container with all the data required for the auto-analysis.
        @rtype:     class instance, list of str
        """

        # The data container.
        data = Container()
        missing = []

        # The pipe name and bundle.
        data.pipe_name = self.data.pipe_name
        data.pipe_bundle = self.data.pipe_bundle

        # The model-free models (do not change these unless absolutely necessary).
        data.local_tm_models = self.local_tm_model_field.GetValue()
        data.mf_models = self.mf_model_field.GetValue()

        # Automatic looping over all rounds until convergence (must be a boolean value of True or False).
        data.conv_loop = True

        # Increment size.
        data.inc = gui_to_int(self.grid_inc.GetValue())
        if hasattr(self.data, 'diff_tensor_grid_inc'):
            data.diff_tensor_grid_inc = self.data.diff_tensor_grid_inc
        else:
            data.diff_tensor_grid_inc = {'sphere': 11, 'prolate': 11, 'oblate': 11, 'ellipsoid': 6}

        # The number of Monte Carlo simulations to be used for error analysis at the end of the analysis.
        data.mc_sim_num = gui_to_int(self.mc_sim_num.GetValue())

        # Number of maximum iterations.
        data.max_iter = self.data.max_iter

        # Results directory.
        data.save_dir = self.data.save_dir

        # Check if sequence data is loaded
        if not exists_mol_res_spin_data():
            missing.append("Sequence data")

        # Relaxation data.
        if not hasattr(cdp, 'ri_ids') or len(cdp.ri_ids) == 0:
            missing.append("Relaxation data")

        # Insufficient data.
        if hasattr(cdp, 'ri_ids') and len(cdp.ri_ids) <= 3:
            missing.append("Insufficient relaxation data, 4 or more data sets are essential for the execution of the dauvergne_protocol auto-analysis. Check that you have entered data for a least two spectrometer fields.")

        # Interatomic data containers.
        if not hasattr(cdp, 'interatomic') or len(cdp.interatomic) == 0:
            missing.append("Interatomic data (for the dipole-dipole interaction)")

        # Get the mode.
        mode = gui_to_str(self.mode.GetValue())

        # Solve for all global models.
        if mode == 'Fully automated':
            # The global model list.
            data.global_models = ['local_tm', 'sphere', 'prolate', 'oblate', 'ellipsoid', 'final']

        # Any global model selected.
        else:
            data.global_models = [mode]

        # Check for vectors.
        vector_check = False
        if 'prolate' in data.global_models or 'oblate' in data.global_models or 'ellipsoid' in data.global_models:
            vector_check = True

        # Spin variables.
        for spin, spin_id in spin_loop(return_id=True):
            # Skip deselected spins.
            if not spin.select:
                continue

            # The message skeleton.
            msg = "Spin '%s' - %s (try the %s user function)." % (spin_id, "%s", "%s")

            # Test if the nuclear isotope type has been set.
            if not hasattr(spin, 'isotope') or spin.isotope == None:
                missing.append(msg % ("nuclear isotope data", "spin.isotope"))

            # Test if the CSA value has been set for the heteronuclei.
            if (hasattr(spin, 'isotope') and spin.isotope in ['15N', '13C']) and (not hasattr(spin, 'csa') or spin.csa == None):
                missing.append(msg % ("CSA data", "value.set"))

        # Interatomic data container variables.
        for interatom in interatomic_loop():
            # Get the spin containers.
            spin1 = return_spin(interatom.spin_id1)
            spin2 = return_spin(interatom.spin_id2)

            # Skip deselected spins.
            if not spin1.select:
                continue
            if not spin2.select:
                continue

            # The message skeleton.
            msg = "Spin pair '%s' and '%s' - %s (try the %s user function)." % (interatom.spin_id1, interatom.spin_id2, "%s", "%s")

            # Test if the interatomic distance has been set.
            if not hasattr(interatom, 'r') or interatom.r == None:
                missing.append(msg % ("bond length data", "value.set"))

            # Test if the unit vectors have been loaded.
            if vector_check and (not hasattr(interatom, 'vector') or interatom.vector == None):
                missing.append(msg % ("unit vectors", "interatom.unit_vectors"))

        # Return the container and list of missing data.
        return data, missing


    def build_left_box(self):
        """Construct the left hand box to pack into the main model-free box.

        @return:    The left hand box element containing the bitmap and about button to pack into the main model-free box.
        @rtype:     wx.BoxSizer instance
        """

        # Build the left hand box.
        left_box = wx.BoxSizer(wx.VERTICAL)

        # The images.
        bitmaps = [ANALYSIS_IMAGE_PATH+"model_free"+sep+"model_free_200x200.png",
                   IMAGE_PATH+'modelfree.png']

        # Add the model-free bitmap picture.
        for i in range(len(bitmaps)):
            # The bitmap.
            bitmap = wx.StaticBitmap(self, -1, bitmap_setup(bitmaps[i]))

            # Add it.
            left_box.Add(bitmap, 0, wx.ALL, 0)

        # A spacer.
        left_box.AddStretchSpacer()

        # A button sizer, with some initial spacing.
        button_sizer = wx.BoxSizer(wx.HORIZONTAL)

        # An about button.
        button = wx.lib.buttons.ThemedGenBitmapTextButton(self, -1, None, "About")
        button.SetBitmapLabel(wx.Bitmap(fetch_icon('oxygen.actions.help-about', "22x22"), wx.BITMAP_TYPE_ANY))
        button.SetFont(font.normal)
        button.SetToolTipString("Information about this automatic analysis")

        # Bind the click.
        self.Bind(wx.EVT_BUTTON, self._about, button)

        # A cursor for the button.
        cursor = wx.StockCursor(wx.CURSOR_QUESTION_ARROW)
        button.SetCursor(cursor)

        # Pack the button.
        button_sizer.Add(button, 0, 0, 0)
        left_box.Add(button_sizer, 0, wx.ALL, 0)

        # Return the packed box.
        return left_box


    def build_right_box(self):
        """Construct the right hand box to pack into the main model-free box.

        @return:    The right hand box element containing all model-free GUI elements (excluding the bitmap) to pack into the main model-free box.
        @rtype:     wx.BoxSizer instance
        """

        # Use a vertical packing of elements.
        box = wx.BoxSizer(wx.VERTICAL)

        # Add the frame title.
        self.add_title(box, "Model-free analysis")

        # Display the data pipe.
        Text_ctrl(box, self, text="The data pipe bundle:", default=self.data.pipe_bundle, tooltip="This is the data pipe bundle associated with this analysis.", editable=False, width_text=self.width_text, width_button=self.width_button, spacer=self.spacer_horizontal)

        # Add the results directory GUI element.
        self.field_results_dir = Text_ctrl(box, self, text="Results directory:", icon=fetch_icon('oxygen.actions.document-open-folder', "16x16"), default=self.data.save_dir, tooltip="The directory in which all automatically created files will be saved.", tooltip_button="Select the results directory.", fn=self.results_directory, button=True, width_text=self.width_text, width_button=self.width_button, spacer=self.spacer_horizontal)

        # Add the spin GUI element.
        self.add_spin_systems(box, self)

        # Add the relaxation data list GUI element, with spacing.
        box.AddSpacer(10)
        self.relax_data = Relax_data_list(gui=self.gui, parent=self, box=box, id=str(self.data_index))
        box.AddSpacer(10)

        # Add the value.set buttons.
        self.add_values(box)
        box.AddSpacer(10)

        # Add the local tau_m models GUI element, with spacing.
        self.local_tm_model_field = Local_tm_list(self, box)
        self.local_tm_model_field.set_value(self.data.local_tm_models)

        # Add the model-free models GUI element, with spacing.
        self.mf_model_field = Mf_list(self, box)
        self.mf_model_field.set_value(self.data.mf_models)

        # The optimisation settings.
        self.grid_inc = Spin_ctrl(box, self, text="Grid search increments:", default=11, min=1, max=100, tooltip="This is the number of increments per dimension of the grid search performed prior to numerical optimisation.", width_text=self.width_text, width_button=self.width_button, spacer=self.spacer_horizontal)
        self.mc_sim_num = Spin_ctrl(box, self, text="Monte Carlo simulation number:", default=500, min=1, max=100000, tooltip="This is the number of Monte Carlo simulations performed for error propagation and analysis.", width_text=self.width_text, width_button=self.width_button, spacer=self.spacer_horizontal)

        # Add maximum iteration selector.
        self.max_iter = Spin_ctrl(box, self, text="Maximum iterations:", default=self.data.max_iter, tooltip="The maximum number of iterations for the protocol.  This is the limit for the global looping over the optimisation of the model-free models, model elimination, model selection and then optimisation of the diffusion tensor.", min=25, max=100, width_text=self.width_text, width_button=self.width_button, spacer=self.spacer_horizontal)

        # The calculation mode.
        self.mode = Text_ctrl(box, self, text="Protocol mode:", default='Fully automated', tooltip="Select if the dauvergne_protocol analysis will be fully automated or whether the individual global models will be optimised separately.", tooltip_button="Open the protocol mode selection window.", icon=fetch_icon('oxygen.actions.system-run', "16x16"), fn=self.mode_dialog, editable=False, button=True, width_text=self.width_text, width_button=self.width_button, spacer=self.spacer_horizontal)

        # Stretchable spacing (with a minimal space).
        box.AddSpacer(30)
        box.AddStretchSpacer()

        # Add the execution GUI element.
        self.button_exec_relax = self.add_execute_analysis(box, self.execute)

        # Return the box.
        return box


    def delete(self):
        """Unregister the spin count from the user functions."""

        # Unregister the observer methods.
        self.observer_register(remove=True)

        # Clean up the relaxation data list object.
        self.relax_data.delete()

        # Destroy the dipole-dipole interaction wizard.
        if hasattr(self, 'dipole_wizard'):
            self.dipole_wizard.Destroy()
            del self.dipole_wizard

        # Destroy the mode selection window.
        self.mode_win.Destroy()
        del self.mode_win

        # Destroy the model list windows.
        self.local_tm_model_field.model_win.Destroy()
        del self.local_tm_model_field
        self.mf_model_field.model_win.Destroy()
        del self.mf_model_field

        # Destroy the missing data dialog, if present.
        if hasattr(self, 'missing_data'):
            self.missing_data.Destroy()
            del self.missing_data


    def execute(self, event=None):
        """Set up, execute, and process the automatic model-free protocol.

        @keyword event: The wx event.
        @type event:    wx event
        """

        # Flush the GUI interpreter internal queue to make sure all user functions are complete.
        self.gui.interpreter.flush()

        # relax execution lock.
        if status.exec_lock.locked():
            error_message("relax is currently executing.", "relax execution lock")
            event.Skip()
            return

        # User warning to close windows.
        self.gui.close_windows()

        # Synchronise the frame data to the relax data store.
        self.sync_ds(upload=True)

        # Assemble all the data needed for the auto-analysis.
        data, missing = self.assemble_data()

        # Missing data.
        if len(missing):
            self.missing_data = Missing_data(missing)
            return

        # Display the relax controller, and go to the end of the log window.
        self.gui.show_controller(None)
        self.gui.controller.log_panel.on_goto_end(None)

        # Start the thread.
        self.thread = Execute_mf(self.gui, data, self.data_index)
        self.thread.start()

        # Terminate the event.
        event.Skip()


    def mode_dialog(self, event=None):
        """The calculation mode selection.

        @keyword event: The wx event.
        @type event:    wx event
        """

        # Show the model selector window.
        if status.show_gui:
            self.mode_win.ShowModal()

        # Set the model.
        self.mode.SetValue(str_to_gui(self.mode_win.select))


    def observer_register(self, remove=False):
        """Register and unregister methods with the observer objects.

        @keyword remove:    If set to True, then the methods will be unregistered.
        @type remove:       False
        """

        # Register.
        if not remove:
            status.observers.gui_uf.register(self.data.pipe_bundle, self.update_spin_count, method_name='update_spin_count')
            status.observers.exec_lock.register(self.data.pipe_bundle, self.activate, method_name='activate')

        # Unregister.
        else:
            # The model-free methods.
            status.observers.gui_uf.unregister(self.data.pipe_bundle)
            status.observers.exec_lock.unregister(self.data.pipe_bundle)

            # The embedded objects methods.
            self.relax_data.observer_register(remove=True)


    def results_directory(self, event=None):
        """The results directory selection.

        @keyword event: The wx event.
        @type event:    wx event
        """

        # The dialog.
        dialog = RelaxDirDialog(parent=self, message='Select the results directory', defaultPath=self.field_results_dir.GetValue())

        # Show the dialog and catch if no file has been selected.
        if status.show_gui and dialog.ShowModal() != wx.ID_OK:
            # Don't do anything.
            return

        # The path (don't do anything if not set).
        path = gui_to_str(dialog.get_path())
        if not path:
            return

        # Store the path.
        self.data.save_dir = path

        # Place the path in the text box.
        self.field_results_dir.SetValue(str_to_gui(path))


    def setup_dipole_pair(self, event=None):
        """Create the wizard for the dipole-dipole interaction.

        @keyword event: The wx event.
        @type event:    wx event
        """

        # Change the cursor to busy.
        wx.BeginBusyCursor()

        # Destroy the dipole-dipole interaction wizard, if it exists.
        if hasattr(self, 'dipole_wizard'):
            self.dipole_wizard.Destroy()

        # Create the wizard.
        self.dipole_wizard = Wiz_window(parent=self.gui, size_x=1000, size_y=750, title="Dipole-dipole interaction setup")

        # Structural data.
        if not hasattr(cdp, 'structure'):
            # Create the PDB reading page.
            page = uf_store['structure.read_pdb'].create_page(self.dipole_wizard, sync=True)
            self.dipole_wizard.add_page(page, skip_button=True)

            # Create the position reading page.
            page = uf_store['structure.get_pos'].create_page(self.dipole_wizard, sync=True)
            self.dipole_wizard.add_page(page, skip_button=True)

        # Create the interatom.define page.
        page = uf_store['interatom.define'].create_page(self.dipole_wizard, sync=True)
        page.SetValue('spin_id1', '@N')
        page.SetValue('spin_id2', '@H')
        self.dipole_wizard.add_page(page)

        # Create the interatom.set_dist page.
        page = uf_store['interatom.set_dist'].create_page(self.dipole_wizard, sync=True)
        page.SetValue('spin_id1', '@N*')
        page.SetValue('spin_id2', '@H*')
        page.SetValue('ave_dist', NH_BOND_LENGTH)
        self.dipole_wizard.add_page(page)

        # Create the interatom.unit_vectors page.
        page = uf_store['interatom.unit_vectors'].create_page(self.dipole_wizard, sync=True)
        self.dipole_wizard.add_page(page)

        # Reset the cursor.
        if wx.IsBusy():
            wx.EndBusyCursor()

        # Execute the wizard.
        self.dipole_wizard.run()


    def spin_isotope_heteronuc(self, event=None):
        """Set the nuclear isotope types of the heteronuclear spins via the spin.isotope user function.

        @keyword event: The wx event.
        @type event:    wx event
        """

        # Call the user function.
        uf_store['spin.isotope'](isotope='15N', spin_id='@N*')


    def spin_isotope_proton(self, event=None):
        """Set the nuclear isotope types of the proton spins via the spin.isotope user function.

        @keyword event: The wx event.
        @type event:    wx event
        """

        # Call the user function.
        uf_store['spin.isotope'](isotope='1H', spin_id='@H*')


    def sync_ds(self, upload=False):
        """Synchronise the analysis frame and the relax data store, both ways.

        This method allows the frame information to be uploaded into the relax data store, or for the information in the relax data store to be downloaded by the frame.

        @keyword upload:    A flag which if True will cause the frame to send data to the relax data store.  If False, data will be downloaded from the relax data store to update the frame.
        @type upload:       bool
        """

        # The local tau_m models to use.
        if upload:
            self.data.local_tm_models = self.local_tm_model_field.GetValue()
        else:
            self.local_tm_model_field.set_value(self.data.local_tm_models)

        # The model-free models to use.
        if upload:
            self.data.mf_models = self.mf_model_field.GetValue()
        else:
            self.mf_model_field.set_value(self.data.mf_models)

        # The grid incs.
        if upload:
            self.data.grid_inc = gui_to_int(self.grid_inc.GetValue())
        elif hasattr(self.data, 'grid_inc'):
            self.grid_inc.SetValue(int(self.data.grid_inc))

        # The MC sim number.
        if upload:
            self.data.mc_sim_num = gui_to_int(self.mc_sim_num.GetValue())
        elif hasattr(self.data, 'mc_sim_num'):
            self.mc_sim_num.SetValue(int(self.data.mc_sim_num))

        # The results directory.
        if upload:
            self.data.save_dir = str(self.field_results_dir.GetValue())
        else:
            self.field_results_dir.SetValue(str_to_gui(self.data.save_dir))

        # Maximum iterations.
        if upload:
            self.data.max_iter = gui_to_int(self.max_iter.GetValue())
        else:
            self.max_iter.SetValue(int(self.data.max_iter))


    def value_set_csa(self, event=None):
        """Set the CSA via the value.set uf.

        @keyword event: The wx event.
        @type event:    wx event
        """

        # Get the default value.
        api = return_api()
        val = api.default_value('csa')

        # Call the user function.
        uf_store['value.set'](val=val, param='csa', spin_id='@N*')