Exemple #1
0
class NEBExplorer(QtGui.QMainWindow):
    def __init__(self, parent=None, system=None, app=None):
        QtGui.QMainWindow.__init__(self, parent=parent)
    
        self.ui = UI()
        self.ui.setupUi(self)
        
        self.system = system
        self.app = app
        self.mdi = QtGui.QMdiArea(self)
        self.setCentralWidget(self.mdi)
        
        self.nebrunner = NEBRunner(app, system)
        self.nebrunner.on_update_gui.connect(self.update)

        self.nebrunner.on_run_started.connect(self.run_started)
        self.nebrunner.on_run_finished.connect(self.run_finished)
         
#        from dlg_params import EditParamsWidget
#        w = QtGui.QDockWidget("NEB parameters", self)
#        w.setWidget(EditParamsWidget(self, 
#                         self.system.params.double_ended_connect.local_connect_params.NEBparams))
#        self.addDockWidget(QtCore.Qt.RightDockWidgetArea, w)
#        
#        self.editparams = w
#        
        
        self.energies = NEBEnergyWidget()
        self.view_energies = self.new_view("Energies", self.energies, QtCore.Qt.TopDockWidgetArea)
        self.view_distances = self.new_view("Distances", NEBDistanceWidget(), QtCore.Qt.TopDockWidgetArea)
        self.view_k = self.new_view("k", NEBTimeseries(attrname="k"), QtCore.Qt.BottomDockWidgetArea)
        self.view_nimages = self.new_view("nimages", NEBTimeseries(attrname="nimages"), QtCore.Qt.BottomDockWidgetArea)
        self.view_rms = self.new_view("rms", NEBTimeseries(attrname="rms", yscale='log'), QtCore.Qt.BottomDockWidgetArea)
        
        self.show3d = Show3DWithSlider()
        self.view_3d = QtGui.QDockWidget("NEB parameters", self)
        self.view_3d.setWidget(self.show3d)
        self.addDockWidget(QtCore.Qt.TopDockWidgetArea, self.view_3d)

        #self.view_3d.setFloating(True)
        self.view_3d.hide()
        self.show3d.setSystem(self.system)
        self.show3d.on_frame_updated.connect(self.set_current_frame)
        self.centralWidget().hide()
        
    def run_started(self):
        self.ui.actionRun.setEnabled(False)
        self.ui.actionReset.setEnabled(False)
        self.ui.actionTS.setEnabled(False)
        
    def run_finished(self):
        self.ui.actionRun.setEnabled(True)
        self.ui.actionReset.setEnabled(True)
        self.ui.actionTS.setEnabled(True)
        
    def set_current_frame(self, index, sender=None):
        self.energies.highlight_frame(index)
        
    def new_view(self, title, widget, pos=QtCore.Qt.RightDockWidgetArea):
        child = QtGui.QDockWidget(title, self)
        child.setWidget(widget)
        self.addDockWidget(pos, child)
        self.nebrunner.on_update_gui.connect(widget.update_gui)
        return child
    
    def new_neb(self, coords1, coords2, path=None, run=True):
        self.coords1 = coords1.copy()
        self.coords2 = coords2.copy()
        self.initial_path=path
        self.nebrunner.run(coords1, coords2, path=path, run=run)
        
    def toggle_view(self, view, show):
        if show:
            view.show()
        else:
            view.hide()

    def update(self, nebrunner):
        self.show3d.setCoordsPath(nebrunner.path)
        
    def on_actionRun_triggered(self, checked=None):
        if checked is None:
            return
        self.nebrunner.continue_run()
    
    def on_actionReset_triggered(self, checked=None):
        if checked is None:
            return
        self.nebrunner.run(self.coords1, self.coords2, run=False, path=self.initial_path)
        
    def on_actionParams_triggered(self, checked=None):
        if checked is None:
            return
        if not hasattr(self, "paramsdlg"):
            self.paramsdlg = DlgParams(self.system.params.double_ended_connect.local_connect_params.NEBparams, parent=self)
        self.paramsdlg.show()
        
    def on_actionSave_triggered(self, checked=None):
        if checked is None:
            return
        dialog = QtGui.QFileDialog(self)
        dialog.setFileMode(QtGui.QFileDialog.AnyFile)
        dialog.selectFile("path.pickle")
        dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave);
        if(not dialog.exec_()):
            return
        filename = dialog.selectedFiles()[0]
        pickle.dump(self.nebrunner.path, open(filename, "w"))

    def on_actionLoad_triggered(self, checked=None):
        if checked is None:
            return
        dialog = QtGui.QFileDialog(self)
        dialog.setFileMode(QtGui.QFileDialog.AnyFile)
        dialog.setAcceptMode(QtGui.QFileDialog.AcceptOpen);
        if(not dialog.exec_()):
            return
        filename = dialog.selectedFiles()[0]
        self.initial_path = pickle.load(open(filename))
        self.nebrunner.run(self.coords1, self.coords2, run=False, path=self.initial_path)

    def on_actionRms_toggled(self, checked):
        self.toggle_view(self.view_rms, checked)
    def on_actionE_toggled(self, checked):
        self.toggle_view(self.view_energies, checked)
    def on_actionS_toggled(self, checked):
        self.toggle_view(self.view_distances, checked)
    def on_actionK_toggled(self, checked):
        self.toggle_view(self.view_k, checked)
    def on_actionNimages_toggled(self, checked):
        self.toggle_view(self.view_nimages, checked)
    def on_action3D_toggled(self, checked):
        self.toggle_view(self.view_3d, checked)
                
    def on_actionTS_triggered(self, checked=None):
        if checked is None:
            return
        if not hasattr(self, "local_connect_explorer"):
            self.local_connect_explorer = ConnectExplorerDialog(self.system, self.app, parent=self)
        self.local_connect_explorer.show()
        self.local_connect_explorer.set_nebrunner(self.nebrunner)
class NormalmodeBrowser(QtGui.QMainWindow):
    """
    the GUI for exploring normal modes
    """
    def __init__(self, parent=None, system=None, app=None):
        QtGui.QMainWindow.__init__(self, parent=parent)
        
        self.ui = UI()
        self.ui.setupUi(self)
        
        self.ui.view3D.setSystem(system)
        self.system = system
        self._params = dict()
        self._params["amplitude"]=1.0
        self._params["remove_known_zeroev"]=True
        export = self._params["export"] = dict()
        export["nframes"]=100
        
        self._params["nframes"] = 30
        
        self.app = app
        self.current_selection = None
        
        self.ui.actionShow_energies.setChecked(False)
        self.ui.mplwidget.hide()
        
        self.ui.actionRun.setVisible(False)
         
    def set_coords(self, coords, normalmodes=None):
        """
        set the coordinates for which the normal modes will be computed
        """
        self.coords = coords
        self.normalmodes = normalmodes        
        
        if normalmodes is None:
            self._calculate_normalmodes()
        
        self._fill_normalmodes()
        
        self.ui.view3D.setCoords(coords)
        
    def _calculate_normalmodes(self):
        """
        compute the normal modes
        """
#        pot = self.system.get_potential()
#        E, g, hess = pot.getEnergyGradientHessian(self.coords)
#        metric = self.system.get_metric_tensor(self.coords)
#        freq, mode = normalmodes(hess, metric = metric)
        freq, mode = self.system.get_normalmodes(self.coords)
        mode=np.real(mode.transpose())
        
        self.normalmodes = []
        #self.normalmodes.append((fre[0], m.flatten()))
        for f, m in zip(freq, mode):
            self.normalmodes.append((f, m)) #np.dot(metric, m)))
             
    def _fill_normalmodes(self):
        """
        populate the list of normal modes
        """
        self.ui.listNormalmodes.clear()
        for n in self.normalmodes:
            self.ui.listNormalmodes.addItem(NormalmodeItem(n))
        
    def on_listNormalmodes_currentItemChanged(self, newsel):
        """
        change which normal mode we're looking at
        """
        if newsel is None:
            self.currentmode = None
            return
        orthogopt = self.system.get_orthogonalize_to_zero_eigenvectors()
        mode = newsel.get_mode().copy()
        if self._params["remove_known_zeroev"]:
            mode = orthogopt(mode, self.coords)
         
        self.currentmode = mode
        self.current_selection = newsel
        
        # generate the configurations from the normal mode
        amp = self._params["amplitude"]
        vector = self.currentmode
        nframes = self._params["nframes"]
        dxlist = [amp * float(i) / nframes for i in xrange(-nframes/2,nframes/2)]
        coordspath = [self.coords + dx * vector for dx in dxlist] 
        coordspath = np.array(coordspath)
        
        self.dxlist = dxlist
        self.coordspath = coordspath
        
        self.ui.view3D.setCoordsPath(coordspath)#, labels=labels)
#        self.ui.view3D.ui.btn_animate.hide()

        if self.ui.actionShow_energies.isChecked():
            self.draw_energy_plot()
    
    def draw_energy_plot(self):
        """
        make a plot of the energies and the energies from the harmonic approximation
        """
        if self.current_selection is None: return
        dxlist = self.dxlist
        coordspath = self.coordspath
        
        # get the energies of the configurations
        pot = self.system.get_potential()
        energies = [pot.getEnergy(coords) for coords in coordspath]
        
        # get the energies of the harmonic approximation
        freq = self.current_selection.get_freq()
        expected_energies = np.array([ 0.5*(freq)*(dx)**2 for dx in dxlist])
        expected_energies += pot.getEnergy(self.coords)
        
        # make the plot
        ax = self.ui.mplwidget.axes
        ax.clear()
        ax.plot(dxlist, energies, label="energy")
        ax.plot(dxlist, expected_energies, label="harmonic approximation")
        ax.legend(loc='best')
        ax.set_xlabel("displacement")
        self.ui.mplwidget.draw()
            
        
    def on_actionRun_toggled(self, checked=None):
        if checked is None: return
        if checked:
            self.ui.view3D.start_animation()
        else:
            self.ui.view3D.stop_animation()
    
    def on_actionShow_energies_toggled(self, checked=None):
        if checked is None: return
        if checked:
            self.ui.mplwidget.show()
            self.draw_energy_plot()
        else:
            self.ui.mplwidget.hide()

    def on_actionSave_triggered(self, checked=None):
        """
        save the normal modes to disk
        """
        if checked is None:
            return
        dialog = QtGui.QFileDialog(self)
        dialog.setFileMode(QtGui.QFileDialog.AnyFile)
        dialog.selectFile("mode.pickle")
        dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave);
        if(not dialog.exec_()):
            return
        filename = dialog.selectedFiles()[0]
        path = []
        nframes = self._params["export"]["nframes"]
        for i in xrange(nframes):
            t = np.sin(i/float(nframes)*2.*np.pi)
            path.append(self.coords + self._params["amplitude"]*t*self.currentmode)
        pickle.dump(path, open(filename, "w"))

    def on_actionParameters_triggered(self, checked=None):
        """
        open a dialog box to change the parameters
        """
        if checked is None:
            return
        if not hasattr(self, "_paramsdlg"):
            self._paramsdlg = DlgParams(self._params, parent=self)
        self._paramsdlg.show()            
Exemple #3
0
class MyForm(QtGui.QMainWindow):
    def __init__(self, systemtype, parent=None):
        QtGui.QWidget.__init__(self)
        self.ui = MainWindow.Ui_MainWindow()
        self.ui.setupUi(self)
        self.listMinima = [
                           self.ui.listWidget,
                           self.ui.listMinima1,
                           self.ui.listMinima2,
                           self.ui.listFrom
                           ]
        self.systemtype = systemtype
        self.NewSystem()
        self.transition=None
        
        #try to load the pymol viewer.  
        self.usepymol = True
        try:
            from pymol_viewer import PymolViewer
            self.pymolviewer = PymolViewer(self.system.load_coords_pymol)
        except (ImportError or NotImplementedError):
            self.usepymol = False
            #note: glutInit() must be called exactly once.  pymol calls it
            #during pymol.finish_launching(), so if we call it again it will
            #give an error. On the other hand, if we're not using pymol we 
            #must call it.
            from OpenGL.GLUT import glutInit
            glutInit()
        
    def NewSystem(self):
        self.system = self.systemtype()
        db = self.system.create_database()
        self.system.database = db
        self.system.database.onMinimumAdded=self.NewMinimum
        self.system.database.onMinimumRemoved=self.RemoveMinimum
        for l in self.listMinima:
            l.clear()
            
    def edit_params(self):
        self.paramsdlg = DlgParams(self.system.params)
        self.paramsdlg.show()
        
    #def save(self):
    #    import pickle
    #    filename = QtGui.QFileDialog.getSaveFileName(self, 'Save File', '.')
    #    output = open(filename, "w")
    #    pickle.dump(self.system.storage, output)
       
    def connect(self):
        """
        connect to an existing database
        """
        filename = QtGui.QFileDialog.getSaveFileName(self, 'Open File', '.')
        self.connect_db(filename)

    def connect_db(self, filename):
        db = self.system.create_database(db=filename)
        self.system.database = db
        for minimum in self.system.database.minima():
            self.NewMinimum(minimum)
        self.system.database.onMinimumAdded=self.NewMinimum
        self.system.database.onMinimumRemoved=self.RemoveMinimum
        
    def SelectMinimum(self, item):
        print "selecting minimum", item.minimum._id, item.minimum.energy
        self.ui.widget.setSystem(self.system)
        self.ui.widget.setCoords(item.coords)
        self.ui.widget.setMinimum(item.minimum)
        self.ui.oglTS.setSystem(self.system)
        self.ui.oglTS.setCoords(item.coords)
        if self.usepymol:
            self.pymolviewer.update_coords([item.coords], index=1, delete_all=True)
        
    def _SelectMinimum1(self, minimum):
        """by minimum"""
        self.ui.oglPath.setSystem(self.system)
        self.ui.oglPath.setCoords(minimum.coords, index=1)
        self.ui.oglPath.setMinimum(minimum, index=1)
        self.neb = None
        if self.usepymol:
            self.pymolviewer.update_coords([minimum.coords], index=1)


    def SelectMinimum1(self, item):
        """called by the ui"""
        return self._SelectMinimum1(item.minimum)
 
    def _SelectMinimum2(self, minimum):
        """by minimum"""
        self.ui.oglPath.setSystem(self.system)
        self.ui.oglPath.setCoords(minimum.coords, index=2)
        self.ui.oglPath.setMinimum(minimum, index=2)
        self.neb = None
        if self.usepymol:
            self.pymolviewer.update_coords([minimum.coords], index=2)


    def SelectMinimum2(self, item):
        """called by the ui"""
        return self._SelectMinimum2(item.minimum)
    
    
    def Invert(self):
        coords2 = self.ui.oglPath.coords[2]
        self.ui.oglPath.setCoords(-coords2, 2)
        if self.usepymol:
            self.pymolviewer.update_coords([-coords2], index=2)

    
    def AlignMinima(self):
        coords1 = self.ui.oglPath.coords[1]
        coords2 = self.ui.oglPath.coords[2]
        align = self.system.get_mindist()
        dist, coords1, coords2 = align(coords1, coords2)
        self.ui.oglPath.setCoords(coords1, 1)
        self.ui.oglPath.setCoords(coords2, 2)
        if self.usepymol:
            self.pymolviewer.update_coords([coords1], index=1)
            self.pymolviewer.update_coords([coords2], index=2)
        print "best alignment distance", dist
        pass    
    
    def ConnectMinima(self):
        self.neb = self.system.createNEB(self.ui.oglPath.coords[1], self.ui.oglPath.coords[2])
        self.neb.optimize()
        self.nebcoords = self.neb.coords
        self.nebenergies = self.neb.energies
        self.ui.oglPath.setCoords(self.neb.coords[0,:], 1)
        self.ui.oglPath.setCoords(None, 2)
        self.ui.sliderFrame.setRange(0,self.neb.coords.shape[0]-1)
        if self.usepymol:
            self.pymolviewer.update_coords(self.nebcoords, index=1, delete_all=True)

    def showFrame(self, i):
        if hasattr(self, "nebcoords"):
            self.ui.oglPath.setCoords(self.nebcoords[i,:])
    
    def show_disconnectivity_graph(self):
        import pylab as pl
        pl.ion()
        pl.clf()
        ax = pl.gca()
        fig = pl.gcf()

        graphwrapper = Graph(self.system.database)
        dg = DisconnectivityGraph(graphwrapper.graph, subgraph_size=2)
        dg.calculate()
        
        #draw minima as points
        xpos, minima = dg.get_minima_layout()
        energies = [m.energy for m in minima]
        points = ax.scatter(xpos, energies, picker=5)
        
        #draw line segments connecting minima
        line_segments = dg.line_segments
        for x, y in line_segments:
            ax.plot(x, y, 'k')
        
        
        #define what happens when a point is clicked on
        global pick_count
        pick_count = 0
        def on_pick(event):
            if event.artist != points:
#                print "you clicked on something other than a node"
                return True
            thispoint = event.artist
            ind = event.ind[0]
            min1 = minima[ind]
            print "you clicked on minimum with id", min1._id, "and energy", min1.energy
            global pick_count
            #print pick_count
            pick_count += 1
            if (pick_count % 2) == 0:
                self._SelectMinimum1(min1)
            else:
                self._SelectMinimum2(min1)
        fig = pl.gcf()
        cid = fig.canvas.mpl_connect('pick_event', on_pick)

        pl.show()
        

    
    def show_graph(self):
        import pylab as pl
        import networkx as nx
        pl.ion()
        pl.clf()
        ax = pl.gca()
        fig = pl.gcf()
        
        #get the graph object, eliminate nodes without edges
        graphwrapper = Graph(self.system.database)
        graph = graphwrapper.graph
        degree = graph.degree()
        nodes = [n for n, nedges in degree.items() if nedges > 0]
        graph = graph.subgraph(nodes)
        
        #get the layout of the nodes from networkx
        layout = nx.spring_layout(graph)
        layoutlist = layout.items()
        xypos = np.array([xy for n, xy in layoutlist])
        #color the nodes by energy
        e = np.array([m.energy for m, xy in layoutlist])
        #plot the nodes
        points = ax.scatter(xypos[:,0], xypos[:,1], picker=5, 
                            s=8**2, c=e, cmap=pl.cm.autumn)
        fig.colorbar(points)
        #label the nodes
        ids = [n._id for n, xy in layoutlist]
        for i in range(len(ids)):
            ax.annotate( ids[i], xypos[i] )
        
        
    
        #plot the edges as lines
        for u, v in graph.edges():
            line = np.array([layout[u], layout[v]])
            ax.plot(line[:,0], line[:,1], '-k')
        
        #scale the axes so the points are not cutoff
        xmin, ymin = np.min(xypos, 0)
        xmax, ymax = np.max(xypos, 0)
        dx = (xmax - xmin)*.1
        dy = (ymax - ymin)*.1
        ax.set_xlim([xmin-dx, xmax+dx])
        ax.set_ylim([ymin-dy, ymax+dy])
        
        global pick_count
        pick_count = 0

        def on_pick(event):
            if event.artist != points:
#                print "you clicked on something other than a node"
                return True
            thispoint = event.artist
            ind = event.ind[0]
            min1 = layoutlist[ind][0]
            print "you clicked on minimum with id", min1._id, "and energy", min1.energy
            global pick_count
            #print pick_count
            pick_count += 1
            if (pick_count % 2) == 0:
                self._SelectMinimum1(min1)
            else:
                self._SelectMinimum2(min1)

        fig = pl.gcf()
        cid = fig.canvas.mpl_connect('pick_event', on_pick)
        
        #ids = dict([(n, n._id) for n in nodes])
        #nx.draw(graph, labels=ids, nodelist=nodes)
        #ax.draw()
#        pl.draw()
        pl.show()
        
    
    def showEnergies(self):
        #note: this breaks if pylab isn't a local import.  I don't know why
        import pylab as pl
        pl.ion()
        pl.plot(self.nebenergies, "o-", label="energies")
        if False: #show climbing images
            neb = self.neb
            cl=[]
            en=[]
            for i in xrange(len(neb.energies)):
                if(neb.isclimbing[i]):
                    print "climbing image :", i, neb.energies[i]
                    cl.append(i)
                    en.append(neb.energies[i])
                    
            pl.plot(cl, en, "s", label="climbing images", markersize=10, markerfacecolor="none", markeredgewidth=2)
        pl.legend(loc='best')
        pl.show()
     
    def NewMinimum(self, minimum):
        E=minimum.energy
        minid=id(minimum)
        coords=minimum.coords
        for obj in self.listMinima:
            item = QMinimumInList('%.4f'%E)
            item.setCoords(coords)
            item.setMinimum(minimum)
            obj.addItem(item)    
            obj.sortItems(1)
                
    def RemoveMinimum(self, minimum):
        minid = id(minimum)
        for obj in self.listMinima:
            itms = obj.findItems('*', QtCore.Qt.MatchWildcard)
            for i in itms:
                if(i.minid == minid):
                    obj.takeItem(obj.row(i))
                        
    def StartBasinHopping(self):
        db = self.system.database
        self.system.database = None
        self.bhrunner = bhrunner.BHRunner(self.system)
        self.bhrunner.start()
        self.system.database = db
        
    def tsSearch(self):
        import numpy as np
        ts = self.system.findTS(self.ui.oglTS.coords[1])
        self.transition = [ts[1][0], ts[0][0], ts[2][0]]

    def showFrameTS(self, i):
        if(self.transition):
            self.ui.oglTS.setCoords(self.transition[i])

    def selectTransition(self):
        pass

    def delete_minimum(self):
        min1 = self.ui.widget.minima[1]
        ret = QtGui.QMessageBox.question(self, "Deleting minima", 
                                   "Do you want to delete minima %d with energy %g"%(min1._id, min1.energy), 
                                   QtGui.QMessageBox.Ok, QtGui.QMessageBox.Cancel)
        if(ret == QtGui.QMessageBox.Ok):
            print "deleting minima"
            print "deleting minimum", min1._id, min1.energy
            self.RemoveMinimum(min1)
            self.system.database.removeMinimum(min1)


#this is currently not used.  it may be used later though
#    def LocalConnect(self):
#        self.local_connect = self.system.create_local_connect()
#        
#        min1 = self.ui.listMinima1.selectedItems()[0].minimum
#        min2 = self.ui.listMinima2.selectedItems()[0].minimum
#        res = self.local_connect.connect(min1, min2)
#        ntriplets = len(res.new_transition_states)
#        
#        path = []
#        for i in range(ntriplets):
#            tsret, m1ret, m2ret = res.new_transition_states[i]
#            local_path = []
#            local_path.append(m1ret[0])
#            local_path.append(tsret.coords)
#            local_path.append(m2ret[0])
#            smoothpath = self.system.smooth_path(local_path)
#            path += list(smoothpath)
#        
#        coords = np.array(path)
#        self.nebcoords = coords
#        self.ui.oglPath.setCoords(coords[0,:], 1)
#        self.ui.oglPath.setCoords(None, 2)
#        self.ui.sliderFrame.setRange(0, coords.shape[0]-1)
    
    def doubleEndedConnect(self):
        return self._doubleEndedConnect(reconnect=False)

    def doubleEndedReConnect(self):
        return self._doubleEndedConnect(reconnect=True)

    def _doubleEndedConnect(self, reconnect=False):
#        min1 = self.ui.listMinima1.selectedItems()[0].minimum
#        min2 = self.ui.listMinima2.selectedItems()[0].minimum
#        min1 = self.ui.
        min1 = self.ui.oglPath.minima[1]
        min2 = self.ui.oglPath.minima[2]
        database = self.system.database
        double_ended_connect = self.system.get_double_ended_connect(min1, min2, database, 
                                                                       fresh_connect=reconnect)
        double_ended_connect.connect()
        mints, S, energies = double_ended_connect.returnPath()
        clist = [m.coords for m in mints]
        print "done finding path, now just smoothing path.  This can take a while"
        smoothpath = self.system.smooth_path(clist)
        print "done"
        
        coords = np.array(smoothpath)
        self.nebcoords = coords
        self.nebenergies = np.array(energies)
        self.ui.oglPath.setCoords(coords[0,:], 1)
        self.ui.oglPath.setCoords(None, 2)
        self.ui.sliderFrame.setRange(0, coords.shape[0]-1)
        
        if self.usepymol:
            self.pymolviewer.update_coords(self.nebcoords, index=1, delete_all=True)
Exemple #4
0
class NEBExplorer(QtGui.QMainWindow):
    def __init__(self, parent=None, system=None, app=None):
        QtGui.QMainWindow.__init__(self, parent=parent)

        self.ui = UI()
        self.ui.setupUi(self)

        self.system = system
        self.app = app
        self.mdi = QtGui.QMdiArea(self)
        self.setCentralWidget(self.mdi)

        self.nebrunner = NEBRunner(app, system)
        self.nebrunner.on_update_gui.connect(self.update)

        self.nebrunner.on_run_started.connect(self.run_started)
        self.nebrunner.on_run_finished.connect(self.run_finished)

        #        from dlg_params import EditParamsWidget
        #        w = QtGui.QDockWidget("NEB parameters", self)
        #        w.setWidget(EditParamsWidget(self,
        #                         self.system.params.double_ended_connect.local_connect_params.NEBparams))
        #        self.addDockWidget(QtCore.Qt.RightDockWidgetArea, w)
        #
        #        self.editparams = w
        #

        self.energies = NEBEnergyWidget()
        self.view_energies = self.new_view("Energies", self.energies,
                                           QtCore.Qt.TopDockWidgetArea)
        self.view_distances = self.new_view("Distances", NEBDistanceWidget(),
                                            QtCore.Qt.TopDockWidgetArea)
        self.view_k = self.new_view("k", NEBTimeseries(attrname="k"),
                                    QtCore.Qt.BottomDockWidgetArea)
        self.view_nimages = self.new_view("nimages",
                                          NEBTimeseries(attrname="nimages"),
                                          QtCore.Qt.BottomDockWidgetArea)
        self.view_rms = self.new_view(
            "rms", NEBTimeseries(attrname="rms", yscale='log'),
            QtCore.Qt.BottomDockWidgetArea)

        self.show3d = Show3DWithSlider()
        self.view_3d = QtGui.QDockWidget("NEB parameters", self)
        self.view_3d.setWidget(self.show3d)
        self.addDockWidget(QtCore.Qt.TopDockWidgetArea, self.view_3d)

        #self.view_3d.setFloating(True)
        self.view_3d.hide()
        self.show3d.setSystem(self.system)
        self.show3d.on_frame_updated.connect(self.set_current_frame)
        self.centralWidget().hide()

    def run_started(self):
        self.ui.actionRun.setEnabled(False)
        self.ui.actionReset.setEnabled(False)
        self.ui.actionTS.setEnabled(False)

    def run_finished(self):
        self.ui.actionRun.setEnabled(True)
        self.ui.actionReset.setEnabled(True)
        self.ui.actionTS.setEnabled(True)

    def set_current_frame(self, index, sender=None):
        self.energies.highlight_frame(index)

    def new_view(self, title, widget, pos=QtCore.Qt.RightDockWidgetArea):
        child = QtGui.QDockWidget(title, self)
        child.setWidget(widget)
        self.addDockWidget(pos, child)
        self.nebrunner.on_update_gui.connect(widget.update_gui)
        return child

    def new_neb(self, coords1, coords2, path=None, run=True):
        self.coords1 = coords1.copy()
        self.coords2 = coords2.copy()
        self.initial_path = path
        self.nebrunner.run(coords1, coords2, path=path, run=run)

    def toggle_view(self, view, show):
        if show:
            view.show()
        else:
            view.hide()

    def update(self, nebrunner):
        self.show3d.setCoordsPath(nebrunner.path)

    def on_actionRun_triggered(self, checked=None):
        if checked is None:
            return
        self.nebrunner.continue_run()

    def on_actionReset_triggered(self, checked=None):
        if checked is None:
            return
        self.nebrunner.run(self.coords1,
                           self.coords2,
                           run=False,
                           path=self.initial_path)

    def on_actionParams_triggered(self, checked=None):
        if checked is None:
            return
        if not hasattr(self, "paramsdlg"):
            self.paramsdlg = DlgParams(self.system.params.double_ended_connect.
                                       local_connect_params.NEBparams,
                                       parent=self)
        self.paramsdlg.show()

    def on_actionSave_triggered(self, checked=None):
        if checked is None:
            return
        dialog = QtGui.QFileDialog(self)
        dialog.setFileMode(QtGui.QFileDialog.AnyFile)
        dialog.selectFile("path.pickle")
        dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
        if (not dialog.exec_()):
            return
        filename = dialog.selectedFiles()[0]
        pickle.dump(self.nebrunner.path, open(filename, "w"))

    def on_actionLoad_triggered(self, checked=None):
        if checked is None:
            return
        dialog = QtGui.QFileDialog(self)
        dialog.setFileMode(QtGui.QFileDialog.AnyFile)
        dialog.setAcceptMode(QtGui.QFileDialog.AcceptOpen)
        if (not dialog.exec_()):
            return
        filename = dialog.selectedFiles()[0]
        self.initial_path = pickle.load(open(filename))
        self.nebrunner.run(self.coords1,
                           self.coords2,
                           run=False,
                           path=self.initial_path)

    def on_actionRms_toggled(self, checked):
        self.toggle_view(self.view_rms, checked)

    def on_actionE_toggled(self, checked):
        self.toggle_view(self.view_energies, checked)

    def on_actionS_toggled(self, checked):
        self.toggle_view(self.view_distances, checked)

    def on_actionK_toggled(self, checked):
        self.toggle_view(self.view_k, checked)

    def on_actionNimages_toggled(self, checked):
        self.toggle_view(self.view_nimages, checked)

    def on_action3D_toggled(self, checked):
        self.toggle_view(self.view_3d, checked)

    def on_actionTS_triggered(self, checked=None):
        if checked is None:
            return
        if not hasattr(self, "local_connect_explorer"):
            self.local_connect_explorer = ConnectExplorerDialog(self.system,
                                                                self.app,
                                                                parent=self)
        self.local_connect_explorer.show()
        self.local_connect_explorer.set_nebrunner(self.nebrunner)
class NormalmodeBrowser(QtGui.QMainWindow):
    """
    the GUI for exploring normal modes
    """
    def __init__(self, parent=None, system=None, app=None):
        QtGui.QMainWindow.__init__(self, parent=parent)

        self.ui = UI()
        self.ui.setupUi(self)

        self.ui.view3D.setSystem(system)
        self.system = system
        self._params = dict()
        self._params["amplitude"] = 1.0
        self._params["remove_known_zeroev"] = True
        export = self._params["export"] = dict()
        export["nframes"] = 100

        self._params["nframes"] = 30

        self.app = app
        self.current_selection = None

        self.ui.actionShow_energies.setChecked(False)
        self.ui.mplwidget.hide()

        self.ui.actionRun.setVisible(False)

    def set_coords(self, coords, normalmodes=None):
        """
        set the coordinates for which the normal modes will be computed
        """
        self.coords = coords
        self.normalmodes = normalmodes

        if normalmodes is None:
            self._calculate_normalmodes()

        self._fill_normalmodes()

        self.ui.view3D.setCoords(coords)

    def _calculate_normalmodes(self):
        """
        compute the normal modes
        """
        #        pot = self.system.get_potential()
        #        E, g, hess = pot.getEnergyGradientHessian(self.coords)
        #        metric = self.system.get_metric_tensor(self.coords)
        #        freq, mode = normalmodes(hess, metric = metric)
        freq, mode = self.system.get_normalmodes(self.coords)
        mode = np.real(mode.transpose())

        self.normalmodes = []
        #self.normalmodes.append((fre[0], m.flatten()))
        for f, m in zip(freq, mode):
            self.normalmodes.append((f, m))  #np.dot(metric, m)))

    def _fill_normalmodes(self):
        """
        populate the list of normal modes
        """
        self.ui.listNormalmodes.clear()
        for n in self.normalmodes:
            self.ui.listNormalmodes.addItem(NormalmodeItem(n))

    def on_listNormalmodes_currentItemChanged(self, newsel):
        """
        change which normal mode we're looking at
        """
        if newsel is None:
            self.currentmode = None
            return
        orthogopt = self.system.get_orthogonalize_to_zero_eigenvectors()
        mode = newsel.get_mode().copy()
        if self._params["remove_known_zeroev"] and orthogopt is not None:
            mode = orthogopt(mode, self.coords)

        self.currentmode = mode
        self.current_selection = newsel

        # generate the configurations from the normal mode
        amp = self._params["amplitude"]
        vector = self.currentmode
        nframes = self._params["nframes"]
        dxlist = [
            amp * float(i) / nframes for i in xrange(-nframes / 2, nframes / 2)
        ]
        coordspath = [self.coords + dx * vector for dx in dxlist]
        coordspath = np.array(coordspath)

        self.dxlist = dxlist
        self.coordspath = coordspath

        self.ui.view3D.setCoordsPath(coordspath)  #, labels=labels)
        #        self.ui.view3D.ui.btn_animate.hide()

        if self.ui.actionShow_energies.isChecked():
            self.draw_energy_plot()

    def draw_energy_plot(self):
        """
        make a plot of the energies and the energies from the harmonic approximation
        """
        if self.current_selection is None: return
        dxlist = self.dxlist
        coordspath = self.coordspath

        # get the energies of the configurations
        pot = self.system.get_potential()
        energies = [pot.getEnergy(coords) for coords in coordspath]

        # get the energies of the harmonic approximation
        freq = self.current_selection.get_freq()
        expected_energies = np.array([0.5 * (freq) * (dx)**2 for dx in dxlist])
        expected_energies += pot.getEnergy(self.coords)

        # make the plot
        ax = self.ui.mplwidget.axes
        ax.clear()
        ax.plot(dxlist, energies, label="energy")
        ax.plot(dxlist, expected_energies, label="harmonic approximation")
        ax.legend(loc='best')
        ax.set_xlabel("displacement")
        self.ui.mplwidget.draw()

    def on_actionRun_toggled(self, checked=None):
        if checked is None: return
        if checked:
            self.ui.view3D.start_animation()
        else:
            self.ui.view3D.stop_animation()

    def on_actionShow_energies_toggled(self, checked=None):
        if checked is None: return
        if checked:
            self.ui.mplwidget.show()
            self.draw_energy_plot()
        else:
            self.ui.mplwidget.hide()

    def on_actionSave_triggered(self, checked=None):
        """
        save the normal modes to disk
        """
        if checked is None:
            return
        dialog = QtGui.QFileDialog(self)
        dialog.setFileMode(QtGui.QFileDialog.AnyFile)
        dialog.selectFile("mode.pickle")
        dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
        if (not dialog.exec_()):
            return
        filename = dialog.selectedFiles()[0]
        path = []
        nframes = self._params["export"]["nframes"]
        for i in xrange(nframes):
            t = np.sin(i / float(nframes) * 2. * np.pi)
            path.append(self.coords +
                        self._params["amplitude"] * t * self.currentmode)
        pickle.dump(path, open(filename, "w"))

    def on_actionParameters_triggered(self, checked=None):
        """
        open a dialog box to change the parameters
        """
        if checked is None:
            return
        if not hasattr(self, "_paramsdlg"):
            self._paramsdlg = DlgParams(self._params, parent=self)
        self._paramsdlg.show()