def __init__(self, tddftb, parent=None): self.tddftb = tddftb QtGui.QWidget.__init__(self, parent) layout = QtGui.QVBoxLayout(self) # upperFrame = QtGui.QFrame() layout.addWidget(upperFrame) upperLayout = QtGui.QHBoxLayout(upperFrame) # table with MOs and Kohn-Sham energies tableFrame = QtGui.QFrame() upperLayout.addWidget(tableFrame) tableLayout = QtGui.QVBoxLayout(tableFrame) tableLayout.addWidget(QtGui.QLabel("Molecular Orbitals:")) self.tableMOs = QtGui.QTableWidget() self.tableMOs.setToolTip( "Select a row to highlight the respective orbital in the density of states" ) self.tableMOs.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) self.tableMOs.itemSelectionChanged.connect(self.plotDOS) tableLayout.addWidget(self.tableMOs) cubeFrame = QtGui.QFrame() tableLayout.addWidget(cubeFrame) cubeLayout = QtGui.QHBoxLayout(cubeFrame) self.ppbGrid = QtGui.QComboBox() self.ppbGrid.addItems( ["crude: 1.0", "coarse: 2.0", "medium: 3.0", "fine: 4.0"]) self.ppbGrid.setCurrentIndex(1) self.ppbGrid.setToolTip( "The number of points per bohr determines the resolution of the grid on which the cubes are calculated" ) self.ppbGrid.currentIndexChanged.connect(self.recalculateCubes) cubeLayout.addWidget(self.ppbGrid) calcCubesButton = QtGui.QPushButton("Calc. cubes") calcCubesButton.setToolTip("Calculate cubes for the selected orbitals") calcCubesButton.clicked.connect(self.calculateCubes) cubeLayout.addWidget(calcCubesButton) # MOs moFrame = QtGui.QFrame() upperLayout.addWidget(moFrame) moLayout = QtGui.QVBoxLayout(moFrame) self.moLabel = QtGui.QLabel("Ground State:") moLayout.addWidget(self.moLabel) moTab = QtGui.QTabWidget() moLayout.addWidget(moTab) # 3D cubes - MOs self.bs = AtomicBasisSet(self.tddftb.dftb2.atomlist) self.molecularOrbitalViewer = QCubeViewerWidget() self.molecularOrbitalViewer.setIsoValue(0.02) moTab.addTab(self.molecularOrbitalViewer, "3D Molecular Orbital") # 3D cubes - MOs # Mulliken Charges self.chargesViewer = QCubeViewerWidget() moTab.addTab(self.chargesViewer, "Mulliken Charges") atomlist = self.tddftb.dftb2.getGeometry() partial_charges = -self.tddftb.dftb2.getPartialCharges() self.chargesViewer.setGeometriesAndCharges([atomlist], [partial_charges]) self.chargesViewer.selectShowOptions(options=["charges"]) # figure with DOS dosFrame = QtGui.QFrame() layout.addWidget(dosFrame) dosLayout = QtGui.QVBoxLayout(dosFrame) self.figDOS = Figure() self.canvasDOS = FigureCanvas(self.figDOS) dosLayout.addWidget(self.canvasDOS) NavigationToolbar(self.canvasDOS, dosFrame, coordinates=True) # controls controlFrame = QtGui.QFrame() dosLayout.addWidget(controlFrame) controlLayout = QtGui.QHBoxLayout(controlFrame) self.energyUnits = QtGui.QComboBox() self.energyUnits.addItems(["Hartree", "eV", "cm-1"]) self.energyUnits.currentIndexChanged.connect(self.plotDOS) controlLayout.addWidget(QtGui.QLabel("units:")) controlLayout.addWidget(self.energyUnits) controlLayout.addWidget(QtGui.QLabel("broadening:")) self.broadening = QtGui.QLineEdit() self.broadening.setText("0.01") self.broadening.editingFinished.connect(self.plotDOS) self.broadening.setToolTip( "The sticks are convolved with a Gaussian to simulated a temperature broadened DOS" ) controlLayout.addWidget(self.broadening) # load KS orbitals tab = self.tableMOs dftb = self.tddftb.dftb2 orbe = dftb.getKSEnergies() f = dftb.getOccupation() self.H**O, self.LUMO = dftb.getFrontierOrbitals() tab.setRowCount(len(orbe)) headers = [ "N", "name", "occ.", "orb. en. / hartree", "orb. en. / eV", "Cube" ] tab.setColumnCount(len(headers)) tab.setHorizontalHeaderLabels(headers) self.mo_names = [] for i in range(0, len(orbe)): if i == self.H**O: name = "H**O" elif i == self.LUMO: name = "LUMO" else: name = "" self.mo_names.append(name) row = [str.rjust(str(i+1),5), \ str.rjust(name,5), str.rjust(str(f[i]),5), str.rjust("%.7f" % orbe[i],20), \ str.rjust("%.7f" % (orbe[i]*AtomicData.hartree_to_eV), 17) ] for j, r in enumerate(row): tab.setItem(i, j, QtGui.QTableWidgetItem("%s" % r)) tab.resizeColumnsToContents() self.plotDOS() # cubes with MOs self.mo_cubes = {} # select H**O self.tableMOs.setCurrentCell(self.H**O, 0)
def __init__(self, tddftb, parent=None): self.tddftb = tddftb QtGui.QWidget.__init__(self, parent) layout = QtGui.QVBoxLayout(self) # upperFrame = QtGui.QFrame() layout.addWidget(upperFrame) upperLayout = QtGui.QHBoxLayout(upperFrame) # table with excitation energies and states tableFrame = QtGui.QFrame() upperLayout.addWidget(tableFrame) tableLayout = QtGui.QVBoxLayout(tableFrame) tableLayout.addWidget(QtGui.QLabel("Excited States:")) self.tableStates = QtGui.QTableWidget() self.tableStates.setToolTip( "Select a row to highlight the respective state in the absorption spectrum" ) self.tableStates.setSelectionBehavior( QtGui.QAbstractItemView.SelectRows) self.tableStates.itemSelectionChanged.connect(self.plotSpectrum) tableLayout.addWidget(self.tableStates) cubeFrame = QtGui.QFrame() tableLayout.addWidget(cubeFrame) cubeLayout = QtGui.QHBoxLayout(cubeFrame) self.ppbGrid = QtGui.QComboBox() self.ppbGrid.addItems( ["crude: 1.0", "coarse: 2.0", "medium: 3.0", "fine: 4.0"]) self.ppbGrid.setCurrentIndex(1) self.ppbGrid.setToolTip( "The number of points per bohr determines the resolution of the grid on which the cubes are calculated" ) self.ppbGrid.currentIndexChanged.connect(self.recalculateCubes) cubeLayout.addWidget(self.ppbGrid) calcCubesButton = QtGui.QPushButton("Calc. cubes") calcCubesButton.setToolTip( "Calculate cubes with the transition densities for the selected states" ) calcCubesButton.clicked.connect(self.calculateCubes) cubeLayout.addWidget(calcCubesButton) # transition densities tdenseFrame = QtGui.QFrame() upperLayout.addWidget(tdenseFrame) tdenseLayout = QtGui.QVBoxLayout(tdenseFrame) self.tdenseLabel = QtGui.QLabel("Excitated State:") tdenseLayout.addWidget(self.tdenseLabel) tdenseTab = QtGui.QTabWidget() tdenseLayout.addWidget(tdenseTab) # 3D cubes - transition density self.bs = AtomicBasisSet(self.tddftb.dftb2.atomlist) self.transitionDensityViewer = QCubeViewerWidget() tdenseTab.addTab(self.transitionDensityViewer, "3D Transition Density") # 3D cubes - density difference self.bs_aux = AuxiliaryBasisSet(self.tddftb.dftb2.atomlist, self.tddftb.dftb2.hubbard_U) self.differenceDensityViewer = QCubeViewerWidget() tdenseTab.addTab(self.differenceDensityViewer, "3D Difference Density") # 2D occ-virt plane exvec2dFrame = QtGui.QFrame() exvec2dLayout = QtGui.QVBoxLayout(exvec2dFrame) self.figExvec = Figure() self.canvasExvec = FigureCanvas(self.figExvec) exvec2dLayout.addWidget(self.canvasExvec) tdenseTab.addTab(exvec2dFrame, "2D Excitation Vector") NavigationToolbar(self.canvasExvec, exvec2dFrame, coordinates=True) # atomic charges in the excited state self.chargesViewer = QCubeViewerWidget() tdenseTab.addTab(self.chargesViewer, "Partial Charges") atomlist = self.tddftb.dftb2.getGeometry() # partial_charges = -self.tddftb.dftb2.getPartialCharges() # self.chargesViewer.setGeometriesAndCharges([atomlist], [partial_charges]) self.chargesViewer.selectShowOptions(options=["charges"]) # transition charges between ground and excited state self.trans_chargesViewer = QCubeViewerWidget() tdenseTab.addTab(self.trans_chargesViewer, "Transition Charges") atomlist = self.tddftb.dftb2.getGeometry() # # for S0->S0 transition, the transition charges are equal to the partial charges # transition_charges = -self.tddftb.dftb2.getPartialCharges() # self.trans_chargesViewer.setGeometriesAndCharges([atomlist], [transition_charges]) self.trans_chargesViewer.selectShowOptions( options=["charges", "transition charges"]) # figure with spectrum spectrumFrame = QtGui.QFrame() layout.addWidget(spectrumFrame) spectrumLayout = QtGui.QVBoxLayout(spectrumFrame) self.figSpectrum = Figure() self.canvasSpectrum = FigureCanvas(self.figSpectrum) spectrumLayout.addWidget(self.canvasSpectrum) NavigationToolbar(self.canvasSpectrum, spectrumFrame, coordinates=True) # controls controlFrame = QtGui.QFrame() spectrumLayout.addWidget(controlFrame) controlLayout = QtGui.QHBoxLayout(controlFrame) self.energyUnits = QtGui.QComboBox() self.energyUnits.addItems(["Hartree", "eV", "nm", "cm-1"]) self.energyUnits.currentIndexChanged.connect(self.plotSpectrum) controlLayout.addWidget(QtGui.QLabel("units:")) controlLayout.addWidget(self.energyUnits) controlLayout.addWidget(QtGui.QLabel("broadening:")) self.broadening = QtGui.QLineEdit() self.broadening.setText("0.0") self.broadening.editingFinished.connect(self.plotSpectrum) self.broadening.setToolTip( "The stick spectrum is convolved with a Gaussian to simulated a temperature broadened spectrum" ) controlLayout.addWidget(self.broadening) # load spectrum nr_dominant_ex = 2 tab = self.tableStates tab.setRowCount(len(tddftb.Omega)) headers = [ "N", "Spin", "Sym", "exc. en. / hartree", "exc. en. / eV", "exc. en. / nm", "osc. strength", "Lambda diagn.", "Cube" ] tab.setColumnCount(len(headers)) tab.setHorizontalHeaderLabels(headers) for I in range(0, len(tddftb.Omega)): row = [str.rjust(str(I+1),5), \ tddftb.multiplicity, \ str.rjust(tddftb.Irreps[I], 3), \ str.rjust("%.7f" % tddftb.Omega[I],20), \ str.rjust("%.7f" % (tddftb.Omega[I]*AtomicData.hartree_to_eV), 17), \ str.rjust("%.7f" % (AtomicData.hartree_to_nm / tddftb.Omega[I]), 17), \ str.rjust("%.7f" % tddftb.oscillator_strength[I], 12), \ str.rjust("%.4f" % tddftb.Lambda2[I], 7)] for j, r in enumerate(row): tab.setItem(I, j, QtGui.QTableWidgetItem("%s" % r)) tab.resizeColumnsToContents() self.plotSpectrum() # cubes with transition densities and difference densities for each state if calculated self.tdense_cubes = {} self.difdense_cubes = {} self.partial_charges = {} # partial charges on excited states
class QMolecularOrbitalWidget(QtGui.QWidget): def __init__(self, tddftb, parent=None): self.tddftb = tddftb QtGui.QWidget.__init__(self, parent) layout = QtGui.QVBoxLayout(self) # upperFrame = QtGui.QFrame() layout.addWidget(upperFrame) upperLayout = QtGui.QHBoxLayout(upperFrame) # table with MOs and Kohn-Sham energies tableFrame = QtGui.QFrame() upperLayout.addWidget(tableFrame) tableLayout = QtGui.QVBoxLayout(tableFrame) tableLayout.addWidget(QtGui.QLabel("Molecular Orbitals:")) self.tableMOs = QtGui.QTableWidget() self.tableMOs.setToolTip( "Select a row to highlight the respective orbital in the density of states" ) self.tableMOs.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) self.tableMOs.itemSelectionChanged.connect(self.plotDOS) tableLayout.addWidget(self.tableMOs) cubeFrame = QtGui.QFrame() tableLayout.addWidget(cubeFrame) cubeLayout = QtGui.QHBoxLayout(cubeFrame) self.ppbGrid = QtGui.QComboBox() self.ppbGrid.addItems( ["crude: 1.0", "coarse: 2.0", "medium: 3.0", "fine: 4.0"]) self.ppbGrid.setCurrentIndex(1) self.ppbGrid.setToolTip( "The number of points per bohr determines the resolution of the grid on which the cubes are calculated" ) self.ppbGrid.currentIndexChanged.connect(self.recalculateCubes) cubeLayout.addWidget(self.ppbGrid) calcCubesButton = QtGui.QPushButton("Calc. cubes") calcCubesButton.setToolTip("Calculate cubes for the selected orbitals") calcCubesButton.clicked.connect(self.calculateCubes) cubeLayout.addWidget(calcCubesButton) # MOs moFrame = QtGui.QFrame() upperLayout.addWidget(moFrame) moLayout = QtGui.QVBoxLayout(moFrame) self.moLabel = QtGui.QLabel("Ground State:") moLayout.addWidget(self.moLabel) moTab = QtGui.QTabWidget() moLayout.addWidget(moTab) # 3D cubes - MOs self.bs = AtomicBasisSet(self.tddftb.dftb2.atomlist) self.molecularOrbitalViewer = QCubeViewerWidget() self.molecularOrbitalViewer.setIsoValue(0.02) moTab.addTab(self.molecularOrbitalViewer, "3D Molecular Orbital") # 3D cubes - MOs # Mulliken Charges self.chargesViewer = QCubeViewerWidget() moTab.addTab(self.chargesViewer, "Mulliken Charges") atomlist = self.tddftb.dftb2.getGeometry() partial_charges = -self.tddftb.dftb2.getPartialCharges() self.chargesViewer.setGeometriesAndCharges([atomlist], [partial_charges]) self.chargesViewer.selectShowOptions(options=["charges"]) # figure with DOS dosFrame = QtGui.QFrame() layout.addWidget(dosFrame) dosLayout = QtGui.QVBoxLayout(dosFrame) self.figDOS = Figure() self.canvasDOS = FigureCanvas(self.figDOS) dosLayout.addWidget(self.canvasDOS) NavigationToolbar(self.canvasDOS, dosFrame, coordinates=True) # controls controlFrame = QtGui.QFrame() dosLayout.addWidget(controlFrame) controlLayout = QtGui.QHBoxLayout(controlFrame) self.energyUnits = QtGui.QComboBox() self.energyUnits.addItems(["Hartree", "eV", "cm-1"]) self.energyUnits.currentIndexChanged.connect(self.plotDOS) controlLayout.addWidget(QtGui.QLabel("units:")) controlLayout.addWidget(self.energyUnits) controlLayout.addWidget(QtGui.QLabel("broadening:")) self.broadening = QtGui.QLineEdit() self.broadening.setText("0.01") self.broadening.editingFinished.connect(self.plotDOS) self.broadening.setToolTip( "The sticks are convolved with a Gaussian to simulated a temperature broadened DOS" ) controlLayout.addWidget(self.broadening) # load KS orbitals tab = self.tableMOs dftb = self.tddftb.dftb2 orbe = dftb.getKSEnergies() f = dftb.getOccupation() self.H**O, self.LUMO = dftb.getFrontierOrbitals() tab.setRowCount(len(orbe)) headers = [ "N", "name", "occ.", "orb. en. / hartree", "orb. en. / eV", "Cube" ] tab.setColumnCount(len(headers)) tab.setHorizontalHeaderLabels(headers) self.mo_names = [] for i in range(0, len(orbe)): if i == self.H**O: name = "H**O" elif i == self.LUMO: name = "LUMO" else: name = "" self.mo_names.append(name) row = [str.rjust(str(i+1),5), \ str.rjust(name,5), str.rjust(str(f[i]),5), str.rjust("%.7f" % orbe[i],20), \ str.rjust("%.7f" % (orbe[i]*AtomicData.hartree_to_eV), 17) ] for j, r in enumerate(row): tab.setItem(i, j, QtGui.QTableWidgetItem("%s" % r)) tab.resizeColumnsToContents() self.plotDOS() # cubes with MOs self.mo_cubes = {} # select H**O self.tableMOs.setCurrentCell(self.H**O, 0) def plotDOS(self): self.figDOS.clf() ax = self.figDOS.add_subplot(111) units = self.energyUnits.itemText(self.energyUnits.currentIndex()) broadening = abs(float(self.broadening.text())) ax.set_title("Density of States (DOS)") ax.set_xlabel("Kohn-Sham Energy / %s" % units, fontsize=15) ax.set_ylabel("DOS / arbitrary units", fontsize=15) ens = self.tddftb.dftb2.getKSEnergies() dos = 1.0 * np.ones(ens.shape) # stick spectrum ens_out = convert_energy(ens, "Hartree", units) ax.vlines(ens_out, np.zeros(ens_out.shape), dos, lw=2, color="black", picker=5) # selected states are highlighted selected = self.tableMOs.selectionModel().selectedRows() for s in selected: i = s.row() ax.vlines(ens_out[i], [0.0], dos[i], lw=3, color="green") ax.text(ens_out[i], dos[i], "%s %s" % (i + 1, self.mo_names[i])) def onpick(event): self.tableMOs.setCurrentCell(event.ind[0], 0) self.figDOS.canvas.mpl_connect('pick_event', onpick) if broadening > 0.0: ens_broadened, spec = broadened_spectrum(ens, dos, broadening) ens_broadened_out = convert_energy(ens_broadened, "Hartree", units) scale = spec.max() / dos.max() / 1.1 x, y = ens_broadened_out, spec / scale ax.plot(x, y, lw=1, color="blue") x_occ = x[x <= ens_out[self.H**O]] y_occ = y[x <= ens_out[self.H**O]] x_virt = x[x >= ens_out[self.LUMO]] y_virt = y[x >= ens_out[self.LUMO]] ax.fill_between(x_occ, 0, y_occ, alpha=0.5, color="blue") ax.fill_between(x_virt, 0, y_virt, alpha=0.5, color="red") ax.annotate('', xy=(ens_out[self.H**O], 1.1), xycoords='data', xytext=(ens_out[self.LUMO], 1.1), textcoords='data', arrowprops={'arrowstyle': '<->'}) ax.annotate( 'gap = %4.3f' % (ens_out[self.LUMO] - ens_out[self.H**O]), xy=((ens_out[self.H**O] + ens_out[self.LUMO]) / 2.0, 1.1), xycoords='data', xytext=(0, 5), textcoords='offset points') self.canvasDOS.draw() self.showMullikenCharges() if len(selected) > 0: i = selected[0].row() self.moLabel.setText("Molecular Orbital: %s %s" % (i + 1, self.mo_names[i])) if i in self.mo_cubes: mo_cube = self.mo_cubes[i] self.molecularOrbitalViewer.setCubes([mo_cube]) else: print("No cube for molecular orbital %d" % (i + 1)) def showMullikenCharges(self): atomlist = self.tddftb.dftb2.getGeometry() dq0 = self.tddftb.dftb2.getPartialCharges() # internally charges are measured in units of e, charges = -dq0 self.chargesViewer.setGeometriesAndCharges([atomlist], [charges]) def calculateCubes(self): selected = self.tableMOs.selectionModel().selectedRows() for s in selected: i = s.row() if not (i in self.mo_cubes): # need to calculated cube print("compute cube for molecular orbital %d ..." % (i + 1), end=' ') mo_cube = self._computeMO(i) print("...done") self.mo_cubes[i] = mo_cube self.tableMOs.setItem(i, self.tableMOs.columnCount() - 1, QtGui.QTableWidgetItem("Y")) self.tableMOs.item(i, self.tableMOs.columnCount() - 1).setBackground(QtGui.QColor(255, 0, 0)) self.plotDOS() def recalculateCubes(self): # delete cubes for i in list(self.mo_cubes.keys()): self.tableMOs.setItem(i, self.tableMOs.columnCount() - 1, QtGui.QTableWidgetItem("")) self.tableMOs.item(i, self.tableMOs.columnCount() - 1).setBackground( QtGui.QColor(255, 255, 255)) self.mo_cubes = {} delattr(Cube.orbital_amplitude, "cached_grid") # cached grid has wrong resolution self.calculateCubes() def _computeMO(self, i): cube = CubeData() atomlist = self.tddftb.dftb2.getGeometry() (xmin, xmax), (ymin, ymax), (zmin, zmax) = Cube.get_bbox(atomlist, dbuff=5.0) dx, dy, dz = xmax - xmin, ymax - ymin, zmax - zmin ppb_text = self.ppbGrid.itemText(self.ppbGrid.currentIndex()) ppb = float(ppb_text.split()[1]) # points per bohr nx, ny, nz = int(dx * ppb), int(dy * ppb), int(dz * ppb) x, y, z = np.mgrid[xmin:xmax:nx * 1j, ymin:ymax:ny * 1j, zmin:zmax:nz * 1j] grid = (x, y, z) # orbs = self.tddftb.dftb2.getKSCoefficients() moGrid = Cube.orbital_amplitude(grid, self.bs.bfs, orbs[:, i], cache=True) mo_cube = CubeData() mo_cube.data = moGrid.real mo_cube.grid = grid mo_cube.atomlist = atomlist return mo_cube
class QSpectrumWidget(QtGui.QWidget): def __init__(self, tddftb, parent=None): self.tddftb = tddftb QtGui.QWidget.__init__(self, parent) layout = QtGui.QVBoxLayout(self) # upperFrame = QtGui.QFrame() layout.addWidget(upperFrame) upperLayout = QtGui.QHBoxLayout(upperFrame) # table with excitation energies and states tableFrame = QtGui.QFrame() upperLayout.addWidget(tableFrame) tableLayout = QtGui.QVBoxLayout(tableFrame) tableLayout.addWidget(QtGui.QLabel("Excited States:")) self.tableStates = QtGui.QTableWidget() self.tableStates.setToolTip( "Select a row to highlight the respective state in the absorption spectrum" ) self.tableStates.setSelectionBehavior( QtGui.QAbstractItemView.SelectRows) self.tableStates.itemSelectionChanged.connect(self.plotSpectrum) tableLayout.addWidget(self.tableStates) cubeFrame = QtGui.QFrame() tableLayout.addWidget(cubeFrame) cubeLayout = QtGui.QHBoxLayout(cubeFrame) self.ppbGrid = QtGui.QComboBox() self.ppbGrid.addItems( ["crude: 1.0", "coarse: 2.0", "medium: 3.0", "fine: 4.0"]) self.ppbGrid.setCurrentIndex(1) self.ppbGrid.setToolTip( "The number of points per bohr determines the resolution of the grid on which the cubes are calculated" ) self.ppbGrid.currentIndexChanged.connect(self.recalculateCubes) cubeLayout.addWidget(self.ppbGrid) calcCubesButton = QtGui.QPushButton("Calc. cubes") calcCubesButton.setToolTip( "Calculate cubes with the transition densities for the selected states" ) calcCubesButton.clicked.connect(self.calculateCubes) cubeLayout.addWidget(calcCubesButton) # transition densities tdenseFrame = QtGui.QFrame() upperLayout.addWidget(tdenseFrame) tdenseLayout = QtGui.QVBoxLayout(tdenseFrame) self.tdenseLabel = QtGui.QLabel("Excitated State:") tdenseLayout.addWidget(self.tdenseLabel) tdenseTab = QtGui.QTabWidget() tdenseLayout.addWidget(tdenseTab) # 3D cubes - transition density self.bs = AtomicBasisSet(self.tddftb.dftb2.atomlist) self.transitionDensityViewer = QCubeViewerWidget() tdenseTab.addTab(self.transitionDensityViewer, "3D Transition Density") # 3D cubes - density difference self.bs_aux = AuxiliaryBasisSet(self.tddftb.dftb2.atomlist, self.tddftb.dftb2.hubbard_U) self.differenceDensityViewer = QCubeViewerWidget() tdenseTab.addTab(self.differenceDensityViewer, "3D Difference Density") # 2D occ-virt plane exvec2dFrame = QtGui.QFrame() exvec2dLayout = QtGui.QVBoxLayout(exvec2dFrame) self.figExvec = Figure() self.canvasExvec = FigureCanvas(self.figExvec) exvec2dLayout.addWidget(self.canvasExvec) tdenseTab.addTab(exvec2dFrame, "2D Excitation Vector") NavigationToolbar(self.canvasExvec, exvec2dFrame, coordinates=True) # atomic charges in the excited state self.chargesViewer = QCubeViewerWidget() tdenseTab.addTab(self.chargesViewer, "Partial Charges") atomlist = self.tddftb.dftb2.getGeometry() # partial_charges = -self.tddftb.dftb2.getPartialCharges() # self.chargesViewer.setGeometriesAndCharges([atomlist], [partial_charges]) self.chargesViewer.selectShowOptions(options=["charges"]) # transition charges between ground and excited state self.trans_chargesViewer = QCubeViewerWidget() tdenseTab.addTab(self.trans_chargesViewer, "Transition Charges") atomlist = self.tddftb.dftb2.getGeometry() # # for S0->S0 transition, the transition charges are equal to the partial charges # transition_charges = -self.tddftb.dftb2.getPartialCharges() # self.trans_chargesViewer.setGeometriesAndCharges([atomlist], [transition_charges]) self.trans_chargesViewer.selectShowOptions( options=["charges", "transition charges"]) # figure with spectrum spectrumFrame = QtGui.QFrame() layout.addWidget(spectrumFrame) spectrumLayout = QtGui.QVBoxLayout(spectrumFrame) self.figSpectrum = Figure() self.canvasSpectrum = FigureCanvas(self.figSpectrum) spectrumLayout.addWidget(self.canvasSpectrum) NavigationToolbar(self.canvasSpectrum, spectrumFrame, coordinates=True) # controls controlFrame = QtGui.QFrame() spectrumLayout.addWidget(controlFrame) controlLayout = QtGui.QHBoxLayout(controlFrame) self.energyUnits = QtGui.QComboBox() self.energyUnits.addItems(["Hartree", "eV", "nm", "cm-1"]) self.energyUnits.currentIndexChanged.connect(self.plotSpectrum) controlLayout.addWidget(QtGui.QLabel("units:")) controlLayout.addWidget(self.energyUnits) controlLayout.addWidget(QtGui.QLabel("broadening:")) self.broadening = QtGui.QLineEdit() self.broadening.setText("0.0") self.broadening.editingFinished.connect(self.plotSpectrum) self.broadening.setToolTip( "The stick spectrum is convolved with a Gaussian to simulated a temperature broadened spectrum" ) controlLayout.addWidget(self.broadening) # load spectrum nr_dominant_ex = 2 tab = self.tableStates tab.setRowCount(len(tddftb.Omega)) headers = [ "N", "Spin", "Sym", "exc. en. / hartree", "exc. en. / eV", "exc. en. / nm", "osc. strength", "Lambda diagn.", "Cube" ] tab.setColumnCount(len(headers)) tab.setHorizontalHeaderLabels(headers) for I in range(0, len(tddftb.Omega)): row = [str.rjust(str(I+1),5), \ tddftb.multiplicity, \ str.rjust(tddftb.Irreps[I], 3), \ str.rjust("%.7f" % tddftb.Omega[I],20), \ str.rjust("%.7f" % (tddftb.Omega[I]*AtomicData.hartree_to_eV), 17), \ str.rjust("%.7f" % (AtomicData.hartree_to_nm / tddftb.Omega[I]), 17), \ str.rjust("%.7f" % tddftb.oscillator_strength[I], 12), \ str.rjust("%.4f" % tddftb.Lambda2[I], 7)] for j, r in enumerate(row): tab.setItem(I, j, QtGui.QTableWidgetItem("%s" % r)) tab.resizeColumnsToContents() self.plotSpectrum() # cubes with transition densities and difference densities for each state if calculated self.tdense_cubes = {} self.difdense_cubes = {} self.partial_charges = {} # partial charges on excited states def plotSpectrum(self): self.figSpectrum.clf() ax = self.figSpectrum.add_subplot(111) units = self.energyUnits.itemText(self.energyUnits.currentIndex()) broadening = abs(float(self.broadening.text())) ax.set_title("Absorption Spectrum") ax.set_xlabel("Excitation Energy / %s" % units, fontsize=15) ax.set_ylabel("Oscillator strength", fontsize=15) ens = self.tddftb.Omega osz = self.tddftb.oscillator_strength # stick spectrum ens_out = convert_energy(ens, "Hartree", units) ax.vlines(ens_out, np.zeros(ens_out.shape), osz, lw=2, color="black", picker=5) # selected states are highlighted selected = self.tableStates.selectionModel().selectedRows() for s in selected: I = s.row() ax.vlines(ens_out[I], [0.0], osz[I], lw=3, color="green") ax.text( ens_out[I], osz[I], "%s (%s$_{%d}$)" % (self.tddftb.Irreps[I], self.tddftb.multiplicity, I + 1)) def onpick(event): self.tableStates.setCurrentCell(event.ind[0], 0) self.figSpectrum.canvas.mpl_connect('pick_event', onpick) if broadening > 0.0: ens_broadened, spec = broadened_spectrum(ens, osz, broadening) ens_broadened_out = convert_energy(ens_broadened, "Hartree", units) scale = spec.max() / osz.max() / 1.1 ax.plot(ens_broadened_out, spec / scale, lw=1, color="blue") self.canvasSpectrum.draw() if len(selected) > 0: I = selected[0].row() self.tdenseLabel.setText( "Excited State: %s (%s%d)" % (self.tddftb.Irreps[I], self.tddftb.multiplicity, I + 1)) self.showPartialCharges(I) if I in self.tdense_cubes: tdense_cube = self.tdense_cubes[I] self.transitionDensityViewer.setCubes([tdense_cube]) difdense_cube = self.difdense_cubes[I] self.differenceDensityViewer.setCubes([difdense_cube]) else: print( "No cube for transition and difference density of state %d" % (I + 1)) # transition densities in occ-virt plane self.plotExcitationCoefficients2D(I) self.canvasExvec.draw() def plotExcitationCoefficients2D(self, I): self.figExvec.clf() ax = self.figExvec.add_subplot(111) ax.set_title("$C^{%d}_{(o \\to v)}$" % (I + 1)) ax.set_xlabel("occupied orbital") ax.set_ylabel("virtual orbital") active_occupied_orbs, active_virtual_orbs = self.tddftb.getActiveOrbitals( ) xlabel_positions = np.arange(0, len(active_occupied_orbs)) xlabels = [str(o + 1) for o in active_occupied_orbs] xlabels[-1] = "H**O %s" % xlabels[-1] ylabel_positions = np.arange(0, len(active_virtual_orbs)) ylabels = [str(v + 1) for v in active_virtual_orbs] ylabels[0] = "LUMO %s" % ylabels[0] ax.set_xticks(xlabel_positions) ax.set_xticklabels(xlabels, rotation=45, fontsize=10) ax.set_yticks(ylabel_positions) ax.set_yticklabels(ylabels, fontsize=10) self.Cexvec = self.tddftb._getExcitationCoefficients()[I, :, :] image = ax.imshow(self.Cexvec.transpose(), interpolation='none', origin='lower', picker=True, vmin=-1.0, vmax=1.0) self.figExvec.colorbar(image, ax=ax) def onpick(event): x, y = event.mouseevent.xdata, event.mouseevent.ydata o, v = int(np.floor(x + 0.5)), int(np.floor(y + 0.5)) if self.Cexvec[o, v] < -0.5: color = "white" else: color = "black" print("%s -> %s %s" % (active_occupied_orbs[o] + 1, active_virtual_orbs[v] + 1, self.Cexvec[o, v])) ax.text(o - 0.4, v - 0.25, "$%d \\to %d$\n%+3.3f" % (active_occupied_orbs[o] + 1, active_virtual_orbs[v] + 1, self.Cexvec[o, v]), color=color, fontsize=12) self.canvasExvec.draw() self.figExvec.canvas.mpl_connect('pick_event', onpick) def showPartialCharges(self, I): atomlist = self.tddftb.dftb2.getGeometry() # particle-hole charges print("compute particle-hole charges for excited state %d" % (I + 1)) particle_charges, hole_charges = self.tddftb.ParticleHoleCharges(I) # partial charges on ground state dq0 = self.tddftb.dftb2.getPartialCharges() # partial charges on excited state dqI = particle_charges + hole_charges + dq0 self.partial_charges[I] = dqI # internally charges are measured in units of e, charges = -dqI self.chargesViewer.setGeometriesAndCharges([atomlist], [charges]) print( "compute transition charges between ground and excited state %d" % (I + 1)) transition_charges = self.tddftb.TransitionChargesState(I) self.trans_chargesViewer.setGeometriesAndCharges([atomlist], [transition_charges]) def calculateCubes(self): selected = self.tableStates.selectionModel().selectedRows() for s in selected: I = s.row() if not (I in self.tdense_cubes): self.showPartialCharges(I) # need to calculated cube print( "compute transition and difference density for state %d ..." % (I + 1), end=' ') tdense_cube, difdense_cube = self._computeTransitionAndDifferenceDensity( I) print("...done") self.tdense_cubes[I] = tdense_cube self.difdense_cubes[I] = difdense_cube self.tableStates.setItem(I, self.tableStates.columnCount() - 1, QtGui.QTableWidgetItem("Y")) self.tableStates.item(I, self.tableStates.columnCount() - 1).setBackground(QtGui.QColor(255, 0, 0)) self.plotSpectrum() def recalculateCubes(self): # delete cubes for I in list(self.tdense_cubes.keys()): self.tableStates.setItem(I, self.tableStates.columnCount() - 1, QtGui.QTableWidgetItem("")) self.tableStates.item(I, self.tableStates.columnCount() - 1).setBackground(QtGui.QColor(255, 255, 255)) self.tdense_cubes = {} self.difdense_cubes = {} self.calculateCubes() def _computeTransitionAndDifferenceDensity(self, I): Ptrans = self.tddftb.TransitionDensityMatrix(I) P0, PI = self.tddftb.ExcitedDensityMatrix(I) Pdif = PI - P0 cube = CubeData() atomlist = self.tddftb.dftb2.getGeometry() (xmin, xmax), (ymin, ymax), (zmin, zmax) = Cube.get_bbox(atomlist, dbuff=5.0) dx, dy, dz = xmax - xmin, ymax - ymin, zmax - zmin ppb_text = self.ppbGrid.itemText(self.ppbGrid.currentIndex()) ppb = float(ppb_text.split()[1]) # points per bohr nx, ny, nz = int(dx * ppb), int(dy * ppb), int(dz * ppb) x, y, z = np.mgrid[xmin:xmax:nx * 1j, ymin:ymax:ny * 1j, zmin:zmax:nz * 1j] grid = (x, y, z) # transition density tdenseGrid = Cube.electron_density(grid, self.bs.bfs, Ptrans) tdense_cube = CubeData() tdense_cube.data = tdenseGrid tdense_cube.grid = grid tdense_cube.atomlist = atomlist ## approximate difference density based on particle and hole charges #dqI = self.partial_charges[I] #difdenseGrid = self.bs_aux.partial_density(dqI, 0.0*self.tddftb.dftb2.ddip, x,y,z) # exact difference density difdenseGrid = Cube.electron_density(grid, self.bs.bfs, Pdif) difdense_cube = CubeData() difdense_cube.data = difdenseGrid difdense_cube.grid = grid difdense_cube.atomlist = atomlist return tdense_cube, difdense_cube
def __init__(self, ): super(Main, self).__init__() self.ui = loadUiWidget( os.path.join(os.path.dirname(os.path.abspath(__file__)), "window2.ui")) self.setCentralWidget(self.ui) self.canvases = [] self.trajdirs = [] self.dt = 0.0 self.geometries = [] self.particle_hole_charges = [] self.ui.trajSelection.itemSelectionChanged.connect( self.changeTrajectory) # load Trajectories dyndir = "./" if len(sys.argv) > 1: dyndir = sys.argv[1] self.ui.dirBrowser.setText(dyndir) self.ui.dirBrowser.returnPressed.connect(self.fetchURL) self.ui.openButton.clicked.connect(self.openDirectory) # save movie self.ui.movieButton.clicked.connect(self.renderMovie) self.stop = False # plots import matplotlib.pyplot as plt cmap = plt.get_cmap('gnuplot') self.colors = ['black', 'red', 'green', 'blue', 'orange', 'm' ] + [cmap(f) for f in np.linspace(0.4, 1.0, 10)] self.energiesAxis = self.addTab(self.ui.energiesLayout) self.coefficientsAxis = self.addTab(self.ui.coefficientsLayout) self.couplingsAxis = self.addTab(self.ui.couplingsLayout) self.currEnergyAxis = self.addTab(self.ui.currEnergyLayout) self.currStateAxis = self.addTab(self.ui.currStateLayout) self.populationsAxis = self.addTab(self.ui.populationsLayout) self.axes = [ self.energiesAxis, self.coefficientsAxis, self.couplingsAxis, self.currEnergyAxis, self.currStateAxis ] self.objects = [] # Window with geometries self.geometryViewer = QCubeViewerWidget() self.geometryViewer.setAnimation(True) #self.geometryViewer.hideIsoControls() self.geometryViewer.selectShowOptions(options=["charges"]) self.ui.viewerWidgetLayout.addWidget(self.geometryViewer) """ self.glWidget = self.newGLWidget() self.mol = Avogadro.molecules.addMolecule() self.objects.append( self.mol ) self.glWidget.molecule = self.mol self.ui.viewerWidgetLayout.addWidget(Avogadro.toPyQt(self.glWidget)) """ # self.ui.animationSlider.valueChanged.connect(self.showMolecules) self.loadTrajectories(dyndir) # self.pictureAorDiab = QtGui.QComboBox() self.pictureAorDiab.addItems(["adiabatic", "local diabatic"]) self.pictureAorDiab.setToolTip( "Choose whether energies and couplings should be shown in the adiabatic or the local diabatic picture." ) self.pictureAorDiab.currentIndexChanged.connect(self.switchPicture) self.picture = self.pictureAorDiab.currentText() self.ui.gridLayout.addWidget(self.pictureAorDiab, 1, 0)
class Main(QtGui.QMainWindow): def __init__(self, ): super(Main, self).__init__() self.ui = loadUiWidget( os.path.join(os.path.dirname(os.path.abspath(__file__)), "window2.ui")) self.setCentralWidget(self.ui) self.canvases = [] self.trajdirs = [] self.dt = 0.0 self.geometries = [] self.particle_hole_charges = [] self.ui.trajSelection.itemSelectionChanged.connect( self.changeTrajectory) # load Trajectories dyndir = "./" if len(sys.argv) > 1: dyndir = sys.argv[1] self.ui.dirBrowser.setText(dyndir) self.ui.dirBrowser.returnPressed.connect(self.fetchURL) self.ui.openButton.clicked.connect(self.openDirectory) # save movie self.ui.movieButton.clicked.connect(self.renderMovie) self.stop = False # plots import matplotlib.pyplot as plt cmap = plt.get_cmap('gnuplot') self.colors = ['black', 'red', 'green', 'blue', 'orange', 'm' ] + [cmap(f) for f in np.linspace(0.4, 1.0, 10)] self.energiesAxis = self.addTab(self.ui.energiesLayout) self.coefficientsAxis = self.addTab(self.ui.coefficientsLayout) self.couplingsAxis = self.addTab(self.ui.couplingsLayout) self.currEnergyAxis = self.addTab(self.ui.currEnergyLayout) self.currStateAxis = self.addTab(self.ui.currStateLayout) self.populationsAxis = self.addTab(self.ui.populationsLayout) self.axes = [ self.energiesAxis, self.coefficientsAxis, self.couplingsAxis, self.currEnergyAxis, self.currStateAxis ] self.objects = [] # Window with geometries self.geometryViewer = QCubeViewerWidget() self.geometryViewer.setAnimation(True) #self.geometryViewer.hideIsoControls() self.geometryViewer.selectShowOptions(options=["charges"]) self.ui.viewerWidgetLayout.addWidget(self.geometryViewer) """ self.glWidget = self.newGLWidget() self.mol = Avogadro.molecules.addMolecule() self.objects.append( self.mol ) self.glWidget.molecule = self.mol self.ui.viewerWidgetLayout.addWidget(Avogadro.toPyQt(self.glWidget)) """ # self.ui.animationSlider.valueChanged.connect(self.showMolecules) self.loadTrajectories(dyndir) # self.pictureAorDiab = QtGui.QComboBox() self.pictureAorDiab.addItems(["adiabatic", "local diabatic"]) self.pictureAorDiab.setToolTip( "Choose whether energies and couplings should be shown in the adiabatic or the local diabatic picture." ) self.pictureAorDiab.currentIndexChanged.connect(self.switchPicture) self.picture = self.pictureAorDiab.currentText() self.ui.gridLayout.addWidget(self.pictureAorDiab, 1, 0) def switchPicture(self): self.picture = self.pictureAorDiab.currentText() if self.picture == "adiabatic": self.ui.tabWidget.setTabText(0, "Adiab. Energies") self.ui.tabWidget.setTabText(2, "Nonadiab. Couplings") else: self.ui.tabWidget.setTabText(0, "Local Diab. Energies") self.ui.tabWidget.setTabText(2, "Local Diab. Coupl.") self.changeTrajectory() def openDirectory(self): dyndir = QtGui.QFileDialog.getExistingDirectory() self.ui.dirBrowser.setText(dyndir) self.fetchURL() def fetchURL(self): dyndir = str(self.ui.dirBrowser.text()) print "TOP DIRECTORY: %s" % dyndir self.loadTrajectories(dyndir) def loadTrajectories(self, dyndir): dirs = glob.glob(os.path.join(dyndir, "*TRAJ*/")) if len(dirs) == 0: # open a single trajectory if os.path.isfile(os.path.join(dyndir, "dynamics.xyz")): self.ui.trajSelection.addItem("%d" % len(self.trajdirs)) self.trajdirs.append(dyndir) else: dirs.sort() # remove previous trajectories self.trajdirs = [] for i, trajdir in enumerate(dirs): if os.path.isfile(os.path.join(trajdir, "dynamics.xyz")): print "Load trajectory form %s" % trajdir self.ui.trajSelection.addItem("%d" % i) self.trajdirs.append(trajdir) self.plotPopulations() for ax in self.axes: ax.clear() ax.set_xlim((-1.0, 1.0)) ax.set_ylim((-1.0, 1.0)) ax.text(-0.75, 0.2, "Click on a trajectory", fontsize=30) ax.text(-0.75, -0.2, " on the left", fontsize=30) for canvas in self.canvases: canvas.draw() def plotPopulations(self): tmin = 0.0 tmax = 0.0 Nst = 0 Nt = 0 data_list = [] for i, trajdir in enumerate(self.trajdirs): state_file = os.path.join(trajdir, "state.dat") try: data = np.loadtxt(state_file) except IOError as e: print e continue Nst = max(data[:, 1].max() + 1, Nst) tmin = min(data[:, 0].min(), tmin) tmax = max(data[:, 0].max(), tmax) Nt = max(len(data[:, 0]), Nt) data_list.append(data) Nst = int(Nst) Nt = int(Nt) print "%d electronic states" % Nst print "%d time steps between %s and %s" % (Nt, tmin, tmax) pop = np.zeros((Nt, Nst + 1)) pop[:, 0] = np.linspace(tmin, tmax, Nt) # time axis in fs Ntraj = [0 for t in range(0, Nt) ] # count trajectories available at each time step for i, data in enumerate(data_list): # only consider trajectories that finished nicely Nt_i = data.shape[0] if Nt_i != Nt: print "Trajectory %d has only %d time steps" % (i, Nt_i) for t in range(0, Nt_i): st = data[t, 1] pop[t, st + 1] += 1 Ntraj[t] += 1.0 print "%s trajectories" % Ntraj[0] # divide populations by the number of trajectories for t in range(0, Nt): pop[t, 1:] /= float(Ntraj[t]) self.populationsAxis.clear() self.populationsAxis.set_xlabel("Time / fs", fontsize=15) self.populationsAxis.set_ylabel("Populations", fontsize=15) for i in range(0, Nst): self.populationsAxis.plot(pop[:, 0], pop[:, 1 + i], lw=2, color=self.colors[i], label="State %d" % i) def addTab(self, layout): fig = Figure() ax = fig.add_subplot(111) ax.set_xlim((-1.0, 1.0)) ax.set_ylim((-1.0, 1.0)) ax.text(-0.75, 0.2, "Click on a trajectory", fontsize=30) ax.text(-0.75, -0.2, " on the left", fontsize=30) canvas = FigureCanvas(fig) self.canvases.append(canvas) layout.addWidget(canvas) canvas.draw() toolbar = NavigationToolbar(canvas, canvas, coordinates=True) return ax def newGLWidget(self): glWidget = Avogadro.GLWidget() glWidget.loadDefaultEngines() glWidget.quality = 4 toolGroup = Avogadro.ToolGroup() tool = Avogadro.PluginManager.instance.tool('Navigate', None) if tool: toolGroup.append(tool) self.objects.append(tool) glWidget.toolGroup = toolGroup self.objects.append(glWidget) self.objects.append(toolGroup) return glWidget def changeTrajectory(self): selected = self.ui.trajSelection.selectedItems() trajs = map(int, [s.text() for s in selected]) self.geometries = [] self.nsteps = 0 print "Selected: %s" % map(str, trajs) print "Plot trajectories %s" % trajs for ax in self.axes: ax.clear() for t in trajs: trajdir = self.trajdirs[t] print trajdir try: self.plotTrajectory(trajdir, t) if os.path.isfile( os.path.join(trajdir, "particle_hole_charges.xyz")): geoms, ph_charges = read_xyz_datafields( os.path.join(trajdir, "particle_hole_charges.xyz")) else: geoms = XYZ.read_xyz(os.path.join(trajdir, "dynamics.xyz")) ph_charges = None self.geometries.append(geoms) self.particle_hole_charges.append(ph_charges) self.nsteps = max(len(self.geometries[-1]), self.nsteps) except (ValueError, IOError) as e: print "Unable to load %s" % trajdir print "ERROR: %s" % e for canvas in self.canvases: canvas.draw() self.ui.animationSlider.setMinimum(0) self.ui.animationSlider.setMaximum(self.nsteps) self.ui.animationTime.setText("Time: %7.2f fs" % 0.0) self.geometryViewer.clear() self.showMolecules() def showMolecules(self): tstep = self.ui.animationSlider.value() self.ui.animationTime.setText("Time: %7.2f fs" % (tstep * self.dt)) molecules = [] charge_lists = [] for geoms, ph_charges in zip(self.geometries, self.particle_hole_charges): geom_time = geoms[min(tstep, len(geoms) - 1)] if type(ph_charges) == type(None): ph_charges_times = np.zeros((len(geom_time), 2)) else: ph_charges_times = ph_charges[min(tstep, len(ph_charges) - 1)] charge_lists.append(-ph_charges_times) molecules.append(geom_time) # change sign of charges, internally charges are represented in units of e-! if len(charge_lists) == 0: self.geometryViewer.setGeometries(molecules) else: self.geometryViewer.setGeometriesAndCharges( molecules, charge_lists) def abortCurrentAction(self): self.stop = True #raise AbortException() def renderMovie(self): if len(self.geometries) == 0: # no trajectories return # ask for filename and start and end frames (ok, path, start, end, step) = SaveMovieDialog.getDirnameAndFrames() if ok == False: return steps = range(0, self.nsteps) self.ui.movieButton.setText("Stop") self.ui.movieButton.clicked.disconnect() self.ui.movieButton.clicked.connect(self.abortCurrentAction) dirname = os.path.dirname(path) basename = os.path.basename(path) if basename == '': basename = "movie.png" name, suffix = os.path.splitext(basename) print "Frames will be stored in %s" % os.path.join( dirname, "%s_######.png" % name) for i in steps[start:end:step]: print "render frame %d" % i self.ui.animationSlider.setValue(i) image_file = os.path.join(dirname, "%s_%06d.png" % (name, i)) self.geometryViewer.savefig(image_file) if self.stop == True: print "stop rendering!" self.stop = False break self.ui.movieButton.setText("Movie...") self.ui.movieButton.clicked.disconnect() self.ui.movieButton.clicked.connect(self.renderMovie) def plotTrajectory(self, trajdir, trajID): # load adiabatic energies energy_files = glob.glob(os.path.join(trajdir, "energy_*.dat")) states = [] energies_list = [] for f in energy_files: states.append(int(f[-15:-4].split("_")[1])) data = np.loadtxt(f) times = data[:, 0] energies_list.append(data[:, 1]) Nst = len(states) Nt = len(times) energies = np.zeros((Nt, Nst)) indx = np.argsort(states) for i in range(0, Nst): energies[:, i] = energies_list[indx[i]] # plot levels if self.picture == "adiabatic": print "adiabatic levels ...", self.energiesAxis.set_xlabel("Time / fs", fontsize=17) self.energiesAxis.set_ylabel("Adiab. Energy / eV", fontsize=17) for i in range(0, Nst): self.energiesAxis.plot(times, energies[:, i] * 27.211, lw=2, color=self.colors[i], label="State %d" % i) print "...done" else: print "local diabatic levels ..." # local diabatic picture ld_energy_file = os.path.join(trajdir, "local_diabatic_energies.dat") self.energiesAxis.set_xlabel("Time / fs", fontsize=17) self.energiesAxis.set_ylabel("Local Diab. Energy / eV", fontsize=17) try: data = np.loadtxt(ld_energy_file) for i in range(0, Nst): self.energiesAxis.plot(data[:, 0], data[:, 1 + i] * 27.211, lw=2, color=self.colors[i], label="State %d" % i) print "...done" except IOError as e: print "No diabatic energies" print e # plot current state print "current states ...", curr_states = np.loadtxt(os.path.join(trajdir, "state.dat")) curr_energy = [] state_changes = [0] st_last = curr_states[0, 1] for i in range(0, Nt): st = curr_states[i, 1] if st_last != st: state_changes.append(i) st_last = st curr_energy.append(energies[i, st]) state_changes.append(i) curr_energy = np.array(curr_energy) # show current energy # ... as a dashed brown line self.energiesAxis.plot(times, curr_energy * 27.211, ls="-.", lw=3, color="brown", label="Curr. State") # ... or as circles #self.energiesAxis.plot(times[::15], (curr_energy*27.211)[::15], 'o', color='brown', markersize=13, mfc='none', label="Curr. State") print "...done" # time-step in fs self.dt = times[1] - times[0] # couplings if self.picture == "adiabatic": # non-adiabatic couplings print "non-adiabatic couplings...", self.couplingsAxis.set_xlabel("Time / fs", fontsize=15) self.couplingsAxis.set_ylabel("Scalar Non-adiab. Coupling", fontsize=15) for i in range(0, Nst): for j in range(i + 1, Nst): try: data = np.loadtxt( os.path.join( trajdir, "nonadiabatic" + str(i) + str(j) + ".dat")) self.couplingsAxis.plot( data[:, 0], data[:, 1], lw=2, label="Non-adiab. coupling %d-%d" % (i, j)) except IOError as e: print e print "...done" else: # local diabatic couplings print "local diabatic coupling...", ld_coupling_file = os.path.join(trajdir, "local_diabatic_couplings.dat") self.couplingsAxis.set_xlabel("Time / fs", fontsize=15) self.couplingsAxis.set_ylabel("Scalar Local Diab. Coupling", fontsize=15) try: data = np.loadtxt(ld_coupling_file) c = 1 for i in range(0, Nst): for j in range(i + 1, Nst): self.couplingsAxis.plot( data[:, 0], data[:, c], lw=2, label="Local diab. coupling %d-%d" % (i, j)) c += 1 print "...done" except IOError as e: print "No diabatic couplings" print e # coefficients print "coefficients ...", self.coefficientsAxis.set_xlabel("Time / fs", fontsize=15) self.coefficientsAxis.set_ylabel( "Electronic Coefficients $\\vert C_i \\vert^2$", fontsize=15) for i in range(0, Nst): try: data = np.loadtxt( os.path.join(trajdir, "coeff_" + str(i) + ".dat")) self.coefficientsAxis.plot(data[:, 0], data[:, 1], lw=2, color=self.colors[i], label="$\\vert C_%d \\vert^2$" % i) except IOError as e: print e print "...done" # current energy print "current energy ...", self.currEnergyAxis.set_xlabel("Time / fs", fontsize=15) self.currEnergyAxis.set_ylabel("Current Energy / eV", fontsize=15) try: data = np.loadtxt(os.path.join(trajdir, "curr_energy.dat")) self.currEnergyAxis.plot(data[:, 0], data[:, 1] * 27.211, lw=2, color="black", label="kinetic") self.currEnergyAxis.plot(data[:, 0], data[:, 2] * 27.211, lw=2, color="red", label="potential") if data.shape[1] > 3: self.currEnergyAxis.plot(data[:, 0], data[:, 3] * 27.211, lw=2, color="green", label="total") legend = self.currEnergyAxis.get_legend() if legend == None: handles, labels = self.currEnergyAxis.get_legend_handles_labels( ) self.currEnergyAxis.legend(handles, labels, loc=1) except IOError as e: print e print "...done" # current states as blocks print "current state blocks ...", self.currStateAxis.set_yticks(range(0, len(self.trajdirs))) self.currStateAxis.set_xlabel("Time / fs", fontsize=15) self.currStateAxis.set_ylabel("Trajectory", fontsize=15) state = curr_states[0, 1] for i in range(1, len(state_changes)): xi = times[state_changes[i - 1]] yi = trajID - 0.45 width = times[state_changes[i]] - times[state_changes[i - 1]] st = int(curr_states[state_changes[i - 1], 1]) self.currStateAxis.add_patch( patches.Rectangle((xi, yi), width, 0.90, color=self.colors[st])) self.currStateAxis.text(xi + 0.5 * width, yi + 0.25, "%d" % st) xmin, xmax = self.currStateAxis.get_xlim() self.currStateAxis.set_xlim((min(times[0], xmin), max(times[-1], xmax))) ymin, ymax = self.currStateAxis.get_ylim() self.currStateAxis.set_ylim((min(ymin, trajID - 1), max(ymax, trajID + 1))) print "...done"
def __init__(self, xyz_file, dyson_file=None): super(Main, self).__init__() self.settings = Settings({ "Continuum Orbital": { "Ionization transitions": [0, ["only intra-atomic", "inter-atomic"]] }, "Averaging": { "Euler angle grid points": 5, "polar angle grid points": 1000, "sphere radius Rmax": 300.0, }, "Scan": { "nr. points": 20 }, "Cube": { "extra space / bohr": 15.0, "points per bohr": 3.0 } }) # perform DFTB calculation # BOUND ORBITAL = H**O self.atomlist = XYZ.read_xyz(xyz_file)[0] # shift molecule to center of mass print "shift molecule to center of mass" pos = XYZ.atomlist2vector(self.atomlist) masses = AtomicData.atomlist2masses(self.atomlist) pos_com = MolCo.shift_to_com(pos, masses) self.atomlist = XYZ.vector2atomlist(pos_com, self.atomlist) self.tddftb = LR_TDDFTB(self.atomlist) self.tddftb.setGeometry(self.atomlist, charge=0) options = {"nstates": 1} try: self.tddftb.getEnergies(**options) except DFTB.Solver.ExcitedStatesNotConverged: pass self.valorbs, radial_val = load_pseudo_atoms(self.atomlist) if dyson_file == None: # Kohn-Sham orbitals are taken as Dyson orbitals self.H**O, self.LUMO = self.tddftb.dftb2.getFrontierOrbitals() self.bound_orbs = self.tddftb.dftb2.getKSCoefficients() self.orbe = self.tddftb.dftb2.getKSEnergies() orbital_names = [] norb = len(self.orbe) for o in range(0, norb): if o < self.H**O: name = "occup." elif o == self.H**O: name = "H**O" elif o == self.LUMO: name = "LUMO " else: name = "virtual" name = name + " " + str(o).rjust(4) + ( " %+10.3f eV" % (self.orbe[o] * 27.211)) orbital_names.append(name) initially_selected = self.H**O else: # load coefficients of Dyson orbitals from file names, ionization_energies, self.bound_orbs = load_dyson_orbitals( dyson_file) self.orbe = np.array(ionization_energies) / 27.211 orbital_names = [] norb = len(self.orbe) for o in range(0, norb): name = names[o] + " " + str(o).rjust(4) + ( " %4.2f eV" % (self.orbe[o] * 27.211)) orbital_names.append(name) initially_selected = 0 self.photo_kinetic_energy = slako_tables_scattering.energies[0] self.epol = np.array([15.0, 0.0, 0.0]) # Build Graphical User Interface main = QtGui.QWidget() mainLayout = QtGui.QHBoxLayout(main) # selectionFrame = QtGui.QFrame() selectionFrame.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Preferred) mainLayout.addWidget(selectionFrame) selectionLayout = QtGui.QVBoxLayout(selectionFrame) # label = QtGui.QLabel(selectionFrame) label.setText("Select bound MO:") selectionLayout.addWidget(label) # bound orbitals self.orbitalSelection = QtGui.QListWidget(selectionFrame) self.orbitalSelection.itemSelectionChanged.connect( self.selectBoundOrbital) norb = len(self.orbe) self.orbital_dict = {} for o in range(0, norb): name = orbital_names[o] self.orbital_dict[name] = o item = QtGui.QListWidgetItem(name, self.orbitalSelection) if o == initially_selected: selected_orbital_item = item selectionLayout.addWidget(self.orbitalSelection) ### VIEWS center = QtGui.QWidget() mainLayout.addWidget(center) centerLayout = QtGui.QGridLayout(center) # boundFrame = QtGui.QFrame() centerLayout.addWidget(boundFrame, 1, 1) boundLayout = QtGui.QVBoxLayout(boundFrame) # "Bound Orbital" label = QtGui.QLabel(boundFrame) label.setText("Bound Orbital") boundLayout.addWidget(label) # self.boundOrbitalViewer = QCubeViewerWidget(boundFrame) boundLayout.addWidget(self.boundOrbitalViewer) # continuum orbital continuumFrame = QtGui.QFrame() centerLayout.addWidget(continuumFrame, 1, 2) continuumLayout = QtGui.QVBoxLayout(continuumFrame) # "Dipole-Prepared Continuum Orbital" label = QtGui.QLabel(continuumFrame) label.setText("Dipole-Prepared Continuum Orbital") continuumLayout.addWidget(label) self.continuumOrbitalViewer = QCubeViewerWidget(continuumFrame) continuumLayout.addWidget(self.continuumOrbitalViewer) self.efield_objects = [] self.efield_actors = [] self.selected = None # picker self.picker = self.continuumOrbitalViewer.visualization.scene.mayavi_scene.on_mouse_pick( self.picker_callback) self.picker.tolerance = 0.01 # PHOTO KINETIC ENERGY sliderFrame = QtGui.QFrame(continuumFrame) continuumLayout.addWidget(sliderFrame) sliderLayout = QtGui.QHBoxLayout(sliderFrame) # label self.pke_label = QtGui.QLabel() self.pke_label.setText("PKE: %6.4f eV" % (self.photo_kinetic_energy * 27.211)) sliderLayout.addWidget(self.pke_label) # Slider for changing the PKE self.pke_slider = QtGui.QSlider(QtCore.Qt.Horizontal) self.pke_slider.setMinimum(0) self.pke_slider.setMaximum(len(slako_tables_scattering.energies) - 1) self.pke_slider.setValue(0) self.pke_slider.sliderReleased.connect(self.changePKE) self.pke_slider.valueChanged.connect(self.searchPKE) sliderLayout.addWidget(self.pke_slider) # # molecular frame photoangular distribution mfpadFrame = QtGui.QFrame() centerLayout.addWidget(mfpadFrame, 2, 1) mfpadLayout = QtGui.QVBoxLayout(mfpadFrame) mfpadLayout.addWidget(QtGui.QLabel("Molecular Frame PAD")) mfpadTabs = QtGui.QTabWidget() mfpadLayout.addWidget(mfpadTabs) # 2D map mfpadFrame2D = QtGui.QFrame() mfpadTabs.addTab(mfpadFrame2D, "2D") mfpadLayout2D = QtGui.QVBoxLayout(mfpadFrame2D) self.MFPADfig2D = Figure() self.MFPADCanvas2D = FigureCanvas(self.MFPADfig2D) mfpadLayout2D.addWidget(self.MFPADCanvas2D) self.MFPADCanvas2D.draw() NavigationToolbar(self.MFPADCanvas2D, mfpadFrame2D, coordinates=True) # 3D mfpadFrame3D = QtGui.QFrame() mfpadTabs.addTab(mfpadFrame3D, "3D") mfpadLayout3D = QtGui.QVBoxLayout(mfpadFrame3D) self.MFPADfig3D = Figure() self.MFPADCanvas3D = FigureCanvas(self.MFPADfig3D) mfpadLayout3D.addWidget(self.MFPADCanvas3D) self.MFPADCanvas3D.draw() NavigationToolbar(self.MFPADCanvas3D, mfpadFrame3D, coordinates=True) # orientation averaged photoangular distribution avgpadFrame = QtGui.QFrame() centerLayout.addWidget(avgpadFrame, 2, 2) avgpadLayout = QtGui.QVBoxLayout(avgpadFrame) self.activate_average = QtGui.QCheckBox("Orientation Averaged PAD") self.activate_average.setToolTip( "Check this box to start averaging of the molecular frame PADs over all orientations. This can take a while." ) self.activate_average.setCheckState(QtCore.Qt.Unchecked) self.activate_average.stateChanged.connect(self.activateAveragedPAD) avgpadLayout.addWidget(self.activate_average) avgpadTabs = QtGui.QTabWidget() avgpadLayout.addWidget(avgpadTabs) # 1D map avgpadFrame1D = QtGui.QFrame() avgpadTabs.addTab(avgpadFrame1D, "1D") avgpadLayout1D = QtGui.QVBoxLayout(avgpadFrame1D) self.AvgPADfig1D = Figure() self.AvgPADCanvas1D = FigureCanvas(self.AvgPADfig1D) avgpadLayout1D.addWidget(self.AvgPADCanvas1D) self.AvgPADCanvas1D.draw() NavigationToolbar(self.AvgPADCanvas1D, avgpadFrame1D, coordinates=True) # 2D map avgpadFrame2D = QtGui.QFrame() avgpadFrame2D.setToolTip( "The averaged PAD should have no phi-dependence anymore. A phi-dependence is a sign of incomplete averaging." ) avgpadTabs.addTab(avgpadFrame2D, "2D") avgpadLayout2D = QtGui.QVBoxLayout(avgpadFrame2D) self.AvgPADfig2D = Figure() self.AvgPADCanvas2D = FigureCanvas(self.AvgPADfig2D) avgpadLayout2D.addWidget(self.AvgPADCanvas2D) self.AvgPADCanvas2D.draw() NavigationToolbar(self.AvgPADCanvas2D, avgpadFrame2D, coordinates=True) # Table avgpadFrameTable = QtGui.QFrame() avgpadTabs.addTab(avgpadFrameTable, "Table") avgpadLayoutTable = QtGui.QVBoxLayout(avgpadFrameTable) self.avgpadTable = QtGui.QTableWidget(0, 6) self.avgpadTable.setToolTip( "Activate averaging and move the PKE slider above to add a new row with beta values. After collecting betas for different energies you can save the table or plot a curve beta(PKE) for the selected orbital." ) self.avgpadTable.setHorizontalHeaderLabels( ["PKE / eV", "sigma", "beta1", "beta2", "beta3", "beta4"]) avgpadLayoutTable.addWidget(self.avgpadTable) # Buttons buttonFrame = QtGui.QFrame() avgpadLayoutTable.addWidget(buttonFrame) buttonLayout = QtGui.QHBoxLayout(buttonFrame) deleteButton = QtGui.QPushButton("Delete") deleteButton.setToolTip("clear table") deleteButton.clicked.connect(self.deletePADTable) buttonLayout.addWidget(deleteButton) buttonLayout.addSpacing(3) scanButton = QtGui.QPushButton("Scan") scanButton.setToolTip( "fill table by scanning automatically through all PKE values") scanButton.clicked.connect(self.scanPADTable) buttonLayout.addWidget(scanButton) saveButton = QtGui.QPushButton("Save") saveButton.setToolTip("save table as a text file") saveButton.clicked.connect(self.savePADTable) buttonLayout.addWidget(saveButton) plotButton = QtGui.QPushButton("Plot") plotButton.setToolTip("plot beta2 column as a function of PKE") plotButton.clicked.connect(self.plotPADTable) buttonLayout.addWidget(plotButton) """ # DOCKS self.setDockOptions(QtGui.QMainWindow.AnimatedDocks | QtGui.QMainWindow.AllowNestedDocks) # selectionDock = QtGui.QDockWidget(self) selectionDock.setWidget(selectionFrame) selectionDock.setFeatures(QtGui.QDockWidget.DockWidgetFloatable | QtGui.QDockWidget.DockWidgetMovable) self.addDockWidget(QtCore.Qt.DockWidgetArea(1), selectionDock) # boundDock = QtGui.QDockWidget(self) boundDock.setWidget(boundFrame) boundDock.setFeatures(QtGui.QDockWidget.DockWidgetFloatable | QtGui.QDockWidget.DockWidgetMovable) boundDock.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) self.addDockWidget(QtCore.Qt.DockWidgetArea(2), boundDock) # continuumDock = QtGui.QDockWidget(self) continuumDock.setWidget(continuumFrame) continuumDock.setFeatures(QtGui.QDockWidget.DockWidgetFloatable | QtGui.QDockWidget.DockWidgetMovable) continuumDock.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) self.addDockWidget(QtCore.Qt.DockWidgetArea(2), continuumDock) """ self.setCentralWidget(main) self.status_bar = QtGui.QStatusBar(main) self.setStatusBar(self.status_bar) self.default_message = "Click on the tip of the green arrow in the top right figure to change the orientation of the E-field" self.statusBar().showMessage(self.default_message) # Menu bar menubar = self.menuBar() exitAction = QtGui.QAction('&Exit', self) exitAction.setShortcut('Ctrl+Q') exitAction.setStatusTip('Exit program') exitAction.triggered.connect(exit) fileMenu = menubar.addMenu('&File') fileMenu.addAction(exitAction) settingsMenu = menubar.addMenu('&Edit') settingsAction = QtGui.QAction('&Settings...', self) settingsAction.setStatusTip('Edit settings') settingsAction.triggered.connect(self.editSettings) settingsMenu.addAction(settingsAction) self.loadContinuum() # select H**O selected_orbital_item.setSelected(True)
class Main(QtGui.QMainWindow): def __init__(self, xyz_file, dyson_file=None): super(Main, self).__init__() self.settings = Settings({ "Continuum Orbital": { "Ionization transitions": [0, ["only intra-atomic", "inter-atomic"]] }, "Averaging": { "Euler angle grid points": 5, "polar angle grid points": 1000, "sphere radius Rmax": 300.0, }, "Scan": { "nr. points": 20 }, "Cube": { "extra space / bohr": 15.0, "points per bohr": 3.0 } }) # perform DFTB calculation # BOUND ORBITAL = H**O self.atomlist = XYZ.read_xyz(xyz_file)[0] # shift molecule to center of mass print "shift molecule to center of mass" pos = XYZ.atomlist2vector(self.atomlist) masses = AtomicData.atomlist2masses(self.atomlist) pos_com = MolCo.shift_to_com(pos, masses) self.atomlist = XYZ.vector2atomlist(pos_com, self.atomlist) self.tddftb = LR_TDDFTB(self.atomlist) self.tddftb.setGeometry(self.atomlist, charge=0) options = {"nstates": 1} try: self.tddftb.getEnergies(**options) except DFTB.Solver.ExcitedStatesNotConverged: pass self.valorbs, radial_val = load_pseudo_atoms(self.atomlist) if dyson_file == None: # Kohn-Sham orbitals are taken as Dyson orbitals self.H**O, self.LUMO = self.tddftb.dftb2.getFrontierOrbitals() self.bound_orbs = self.tddftb.dftb2.getKSCoefficients() self.orbe = self.tddftb.dftb2.getKSEnergies() orbital_names = [] norb = len(self.orbe) for o in range(0, norb): if o < self.H**O: name = "occup." elif o == self.H**O: name = "H**O" elif o == self.LUMO: name = "LUMO " else: name = "virtual" name = name + " " + str(o).rjust(4) + ( " %+10.3f eV" % (self.orbe[o] * 27.211)) orbital_names.append(name) initially_selected = self.H**O else: # load coefficients of Dyson orbitals from file names, ionization_energies, self.bound_orbs = load_dyson_orbitals( dyson_file) self.orbe = np.array(ionization_energies) / 27.211 orbital_names = [] norb = len(self.orbe) for o in range(0, norb): name = names[o] + " " + str(o).rjust(4) + ( " %4.2f eV" % (self.orbe[o] * 27.211)) orbital_names.append(name) initially_selected = 0 self.photo_kinetic_energy = slako_tables_scattering.energies[0] self.epol = np.array([15.0, 0.0, 0.0]) # Build Graphical User Interface main = QtGui.QWidget() mainLayout = QtGui.QHBoxLayout(main) # selectionFrame = QtGui.QFrame() selectionFrame.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Preferred) mainLayout.addWidget(selectionFrame) selectionLayout = QtGui.QVBoxLayout(selectionFrame) # label = QtGui.QLabel(selectionFrame) label.setText("Select bound MO:") selectionLayout.addWidget(label) # bound orbitals self.orbitalSelection = QtGui.QListWidget(selectionFrame) self.orbitalSelection.itemSelectionChanged.connect( self.selectBoundOrbital) norb = len(self.orbe) self.orbital_dict = {} for o in range(0, norb): name = orbital_names[o] self.orbital_dict[name] = o item = QtGui.QListWidgetItem(name, self.orbitalSelection) if o == initially_selected: selected_orbital_item = item selectionLayout.addWidget(self.orbitalSelection) ### VIEWS center = QtGui.QWidget() mainLayout.addWidget(center) centerLayout = QtGui.QGridLayout(center) # boundFrame = QtGui.QFrame() centerLayout.addWidget(boundFrame, 1, 1) boundLayout = QtGui.QVBoxLayout(boundFrame) # "Bound Orbital" label = QtGui.QLabel(boundFrame) label.setText("Bound Orbital") boundLayout.addWidget(label) # self.boundOrbitalViewer = QCubeViewerWidget(boundFrame) boundLayout.addWidget(self.boundOrbitalViewer) # continuum orbital continuumFrame = QtGui.QFrame() centerLayout.addWidget(continuumFrame, 1, 2) continuumLayout = QtGui.QVBoxLayout(continuumFrame) # "Dipole-Prepared Continuum Orbital" label = QtGui.QLabel(continuumFrame) label.setText("Dipole-Prepared Continuum Orbital") continuumLayout.addWidget(label) self.continuumOrbitalViewer = QCubeViewerWidget(continuumFrame) continuumLayout.addWidget(self.continuumOrbitalViewer) self.efield_objects = [] self.efield_actors = [] self.selected = None # picker self.picker = self.continuumOrbitalViewer.visualization.scene.mayavi_scene.on_mouse_pick( self.picker_callback) self.picker.tolerance = 0.01 # PHOTO KINETIC ENERGY sliderFrame = QtGui.QFrame(continuumFrame) continuumLayout.addWidget(sliderFrame) sliderLayout = QtGui.QHBoxLayout(sliderFrame) # label self.pke_label = QtGui.QLabel() self.pke_label.setText("PKE: %6.4f eV" % (self.photo_kinetic_energy * 27.211)) sliderLayout.addWidget(self.pke_label) # Slider for changing the PKE self.pke_slider = QtGui.QSlider(QtCore.Qt.Horizontal) self.pke_slider.setMinimum(0) self.pke_slider.setMaximum(len(slako_tables_scattering.energies) - 1) self.pke_slider.setValue(0) self.pke_slider.sliderReleased.connect(self.changePKE) self.pke_slider.valueChanged.connect(self.searchPKE) sliderLayout.addWidget(self.pke_slider) # # molecular frame photoangular distribution mfpadFrame = QtGui.QFrame() centerLayout.addWidget(mfpadFrame, 2, 1) mfpadLayout = QtGui.QVBoxLayout(mfpadFrame) mfpadLayout.addWidget(QtGui.QLabel("Molecular Frame PAD")) mfpadTabs = QtGui.QTabWidget() mfpadLayout.addWidget(mfpadTabs) # 2D map mfpadFrame2D = QtGui.QFrame() mfpadTabs.addTab(mfpadFrame2D, "2D") mfpadLayout2D = QtGui.QVBoxLayout(mfpadFrame2D) self.MFPADfig2D = Figure() self.MFPADCanvas2D = FigureCanvas(self.MFPADfig2D) mfpadLayout2D.addWidget(self.MFPADCanvas2D) self.MFPADCanvas2D.draw() NavigationToolbar(self.MFPADCanvas2D, mfpadFrame2D, coordinates=True) # 3D mfpadFrame3D = QtGui.QFrame() mfpadTabs.addTab(mfpadFrame3D, "3D") mfpadLayout3D = QtGui.QVBoxLayout(mfpadFrame3D) self.MFPADfig3D = Figure() self.MFPADCanvas3D = FigureCanvas(self.MFPADfig3D) mfpadLayout3D.addWidget(self.MFPADCanvas3D) self.MFPADCanvas3D.draw() NavigationToolbar(self.MFPADCanvas3D, mfpadFrame3D, coordinates=True) # orientation averaged photoangular distribution avgpadFrame = QtGui.QFrame() centerLayout.addWidget(avgpadFrame, 2, 2) avgpadLayout = QtGui.QVBoxLayout(avgpadFrame) self.activate_average = QtGui.QCheckBox("Orientation Averaged PAD") self.activate_average.setToolTip( "Check this box to start averaging of the molecular frame PADs over all orientations. This can take a while." ) self.activate_average.setCheckState(QtCore.Qt.Unchecked) self.activate_average.stateChanged.connect(self.activateAveragedPAD) avgpadLayout.addWidget(self.activate_average) avgpadTabs = QtGui.QTabWidget() avgpadLayout.addWidget(avgpadTabs) # 1D map avgpadFrame1D = QtGui.QFrame() avgpadTabs.addTab(avgpadFrame1D, "1D") avgpadLayout1D = QtGui.QVBoxLayout(avgpadFrame1D) self.AvgPADfig1D = Figure() self.AvgPADCanvas1D = FigureCanvas(self.AvgPADfig1D) avgpadLayout1D.addWidget(self.AvgPADCanvas1D) self.AvgPADCanvas1D.draw() NavigationToolbar(self.AvgPADCanvas1D, avgpadFrame1D, coordinates=True) # 2D map avgpadFrame2D = QtGui.QFrame() avgpadFrame2D.setToolTip( "The averaged PAD should have no phi-dependence anymore. A phi-dependence is a sign of incomplete averaging." ) avgpadTabs.addTab(avgpadFrame2D, "2D") avgpadLayout2D = QtGui.QVBoxLayout(avgpadFrame2D) self.AvgPADfig2D = Figure() self.AvgPADCanvas2D = FigureCanvas(self.AvgPADfig2D) avgpadLayout2D.addWidget(self.AvgPADCanvas2D) self.AvgPADCanvas2D.draw() NavigationToolbar(self.AvgPADCanvas2D, avgpadFrame2D, coordinates=True) # Table avgpadFrameTable = QtGui.QFrame() avgpadTabs.addTab(avgpadFrameTable, "Table") avgpadLayoutTable = QtGui.QVBoxLayout(avgpadFrameTable) self.avgpadTable = QtGui.QTableWidget(0, 6) self.avgpadTable.setToolTip( "Activate averaging and move the PKE slider above to add a new row with beta values. After collecting betas for different energies you can save the table or plot a curve beta(PKE) for the selected orbital." ) self.avgpadTable.setHorizontalHeaderLabels( ["PKE / eV", "sigma", "beta1", "beta2", "beta3", "beta4"]) avgpadLayoutTable.addWidget(self.avgpadTable) # Buttons buttonFrame = QtGui.QFrame() avgpadLayoutTable.addWidget(buttonFrame) buttonLayout = QtGui.QHBoxLayout(buttonFrame) deleteButton = QtGui.QPushButton("Delete") deleteButton.setToolTip("clear table") deleteButton.clicked.connect(self.deletePADTable) buttonLayout.addWidget(deleteButton) buttonLayout.addSpacing(3) scanButton = QtGui.QPushButton("Scan") scanButton.setToolTip( "fill table by scanning automatically through all PKE values") scanButton.clicked.connect(self.scanPADTable) buttonLayout.addWidget(scanButton) saveButton = QtGui.QPushButton("Save") saveButton.setToolTip("save table as a text file") saveButton.clicked.connect(self.savePADTable) buttonLayout.addWidget(saveButton) plotButton = QtGui.QPushButton("Plot") plotButton.setToolTip("plot beta2 column as a function of PKE") plotButton.clicked.connect(self.plotPADTable) buttonLayout.addWidget(plotButton) """ # DOCKS self.setDockOptions(QtGui.QMainWindow.AnimatedDocks | QtGui.QMainWindow.AllowNestedDocks) # selectionDock = QtGui.QDockWidget(self) selectionDock.setWidget(selectionFrame) selectionDock.setFeatures(QtGui.QDockWidget.DockWidgetFloatable | QtGui.QDockWidget.DockWidgetMovable) self.addDockWidget(QtCore.Qt.DockWidgetArea(1), selectionDock) # boundDock = QtGui.QDockWidget(self) boundDock.setWidget(boundFrame) boundDock.setFeatures(QtGui.QDockWidget.DockWidgetFloatable | QtGui.QDockWidget.DockWidgetMovable) boundDock.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) self.addDockWidget(QtCore.Qt.DockWidgetArea(2), boundDock) # continuumDock = QtGui.QDockWidget(self) continuumDock.setWidget(continuumFrame) continuumDock.setFeatures(QtGui.QDockWidget.DockWidgetFloatable | QtGui.QDockWidget.DockWidgetMovable) continuumDock.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) self.addDockWidget(QtCore.Qt.DockWidgetArea(2), continuumDock) """ self.setCentralWidget(main) self.status_bar = QtGui.QStatusBar(main) self.setStatusBar(self.status_bar) self.default_message = "Click on the tip of the green arrow in the top right figure to change the orientation of the E-field" self.statusBar().showMessage(self.default_message) # Menu bar menubar = self.menuBar() exitAction = QtGui.QAction('&Exit', self) exitAction.setShortcut('Ctrl+Q') exitAction.setStatusTip('Exit program') exitAction.triggered.connect(exit) fileMenu = menubar.addMenu('&File') fileMenu.addAction(exitAction) settingsMenu = menubar.addMenu('&Edit') settingsAction = QtGui.QAction('&Settings...', self) settingsAction.setStatusTip('Edit settings') settingsAction.triggered.connect(self.editSettings) settingsMenu.addAction(settingsAction) self.loadContinuum() # select H**O selected_orbital_item.setSelected(True) def loadContinuum(self): E = self.photo_kinetic_energy k = np.sqrt(2 * E) wavelength = 2.0 * np.pi / k # determine the radius of the sphere where the angular distribution is calculated. It should be # much larger than the extent of the molecule (xmin, xmax), (ymin, ymax), (zmin, zmax) = Cube.get_bbox(self.atomlist, dbuff=0.0) dx, dy, dz = xmax - xmin, ymax - ymin, zmax - zmin Rmax0 = self.settings.getOption("Averaging", "sphere radius Rmax") Rmax = max([dx, dy, dz]) + Rmax0 Npts = max(int(Rmax), 1) * 50 print "Radius of sphere around molecule, Rmax = %s bohr" % Rmax print "Points on radial grid, Npts = %d" % Npts self.bs_free = AtomicScatteringBasisSet(self.atomlist, E, rmin=0.0, rmax=Rmax + 2 * wavelength, Npts=Npts) self.SKT_bf, SKT_ff = load_slako_scattering(self.atomlist, E) if self.settings.getOption("Continuum Orbital", "Ionization transitions") == "inter-atomic": inter_atomic = True else: inter_atomic = False print "inter-atomic transitions: %s" % inter_atomic self.Dipole = ScatteringDipoleMatrix(self.atomlist, self.valorbs, self.SKT_bf, inter_atomic=inter_atomic).real # if self.activate_average.isChecked(): print "ORIENTATION AVERAGING" npts_euler = self.settings.getOption("Averaging", "Euler angle grid points") npts_theta = self.settings.getOption("Averaging", "polar angle grid points") self.orientation_averaging = PAD.OrientationAveraging_small_memory( self.Dipole, self.bs_free, Rmax, E, npts_euler=npts_euler, npts_theta=npts_theta) else: print "NO AVERAGING" def searchPKE(self): self.photo_kinetic_energy = slako_tables_scattering.energies[ self.pke_slider.value()] self.pke_label.setText("PKE: %6.4f eV" % (self.photo_kinetic_energy * 27.211)) #self.pke_label.update() @busy def changePKE(self): self.photo_kinetic_energy = slako_tables_scattering.energies[ self.pke_slider.value()] self.pke_label.setText("PKE: %6.4f eV" % (self.photo_kinetic_energy * 27.211)) self.loadContinuum() self.plotContinuumOrbital() self.plotMFPAD() self.plotAveragedPAD() @busy def selectBoundOrbital(self): self.plotBoundOrbital() self.plotContinuumOrbital() self.plotMFPAD() self.deletePADTable() self.plotAveragedPAD() def plotBoundOrbital(self): selected = self.orbitalSelection.selectedItems() assert len(selected) == 1 selected_orbital = self.orbital_dict[str(selected[0].text())] self.mo_bound = self.bound_orbs[:, selected_orbital] # shift geometry so that the expectation value of the dipole operator vanishes # dipole matrix dipole = np.tensordot(self.mo_bound, np.tensordot(self.tddftb.dftb2.D, self.mo_bound, axes=(1, 0)), axes=(0, 0)) print "expectation value of dipole: %s" % dipole # shift molecule R -> R - dipole self.atomlist = MolCo.transform_molecule( self.tddftb.dftb2.getGeometry(), (0, 0, 0), -dipole) # load bound basis functions self.bs_bound = AtomicBasisSet(self.atomlist) # plot selected orbital (xmin, xmax), (ymin, ymax), (zmin, zmax) = Cube.get_bbox(self.atomlist, dbuff=5.0) dx, dy, dz = xmax - xmin, ymax - ymin, zmax - zmin ppb = 3.0 # Points per bohr nx, ny, nz = int(dx * ppb), int(dy * ppb), int(dz * ppb) x, y, z = np.mgrid[xmin:xmax:nx * 1j, ymin:ymax:ny * 1j, zmin:zmax:nz * 1j] grid = (x, y, z) amplitude_bound = Cube.orbital_amplitude(grid, self.bs_bound.bfs, self.mo_bound, cache=False) bound_cube = CubeData() bound_cube.data = amplitude_bound.real bound_cube.grid = grid bound_cube.atomlist = self.atomlist self.boundOrbitalViewer.setCubes([bound_cube]) def plotContinuumOrbital(self): dbuff = self.settings["Cube"]["extra space / bohr"] ppb = self.settings["Cube"]["points per bohr"] (xmin, xmax), (ymin, ymax), (zmin, zmax) = Cube.get_bbox(self.atomlist, dbuff=dbuff) dx, dy, dz = xmax - xmin, ymax - ymin, zmax - zmin nx, ny, nz = int(dx * ppb), int(dy * ppb), int(dz * ppb) x, y, z = np.mgrid[xmin:xmax:nx * 1j, ymin:ymax:ny * 1j, zmin:zmax:nz * 1j] grid = (x, y, z) # plot continuum orbital # projection of dipoles onto polarization direction Dipole_projected = np.zeros( (self.Dipole.shape[0], self.Dipole.shape[1])) # normalize polarization epol_unit = self.epol / np.sqrt(np.dot(self.epol, self.epol)) print "Polarization direction of E-field:" print "E (normalized) = %s" % epol_unit for xyz in [0, 1, 2]: Dipole_projected += self.Dipole[:, :, xyz] * epol_unit[xyz] #print "Dipole projected" #print Dipole_projected # unnormalized coefficients of dipole-prepared continuum orbitals self.mo_free = np.dot(self.mo_bound, Dipole_projected) nrm2 = np.dot(self.mo_free.conjugate(), self.mo_free) #print "nrm2 = %s" % nrm2 # normalized coefficients self.mo_free /= np.sqrt(nrm2) amplitude_continuum = Cube.orbital_amplitude(grid, self.bs_free.bfs, self.mo_free, cache=False) continuum_cube = CubeData() continuum_cube.data = amplitude_continuum.real continuum_cube.grid = grid continuum_cube.atomlist = self.atomlist self.continuumOrbitalViewer.setCubes([continuum_cube]) # plot E-field for o in self.efield_objects: o.remove() mlab = self.continuumOrbitalViewer.visualization.scene.mlab # self.efield_arrow = mlab.quiver3d(0,0,0, self.epol[0], self.epol[1], self.epol[2], self.efield_arrow = mlab.quiver3d(0, 0, 0, float(self.epol[0]), float(self.epol[1]), float(self.epol[2]), color=(0.0, 1.0, 0.0), scale_factor=1.0, mode='arrow', resolution=20, figure=self.continuumOrbitalViewer. visualization.scene.mayavi_scene) self.efield_text = mlab.text(self.epol[0], self.epol[1], "E-field", z=self.epol[2], figure=self.continuumOrbitalViewer. visualization.scene.mayavi_scene) self.efield_text.actor.set(text_scale_mode='none', width=0.05, height=0.1) self.efield_text.property.set(justification='centered', vertical_justification='centered') self.efield_head = mlab.points3d([self.epol[0]], [self.epol[1]], [self.epol[2]], scale_factor=0.5, mode='cube', resolution=20, color=(0.0, 1.0, 0.0), figure=self.continuumOrbitalViewer. visualization.scene.mayavi_scene) self.efield_head.glyph.glyph_source.glyph_source.center = [0, 0, 0] self.efield_outline = mlab.outline(line_width=3, figure=self.continuumOrbitalViewer. visualization.scene.mayavi_scene) self.efield_outline.outline_mode = 'cornered' w = 0.1 self.efield_outline.bounds = (self.epol[0] - w, self.epol[0] + w, self.epol[1] - w, self.epol[1] + w, self.epol[2] - w, self.epol[2] + w) self.efield_objects = [ self.efield_arrow, self.efield_text, self.efield_head, self.efield_outline ] self.efield_actors = [self.efield_head.actor.actors] def picker_callback(self, picker): for actors in self.efield_actors: if picker.actor in actors: mlab = self.continuumOrbitalViewer.visualization.scene.mlab self.selected = "arrow" w = 1.0 self.efield_outline.bounds = (self.epol[0] - w, self.epol[0] + w, self.epol[1] - w, self.epol[1] + w, self.epol[2] - w, self.epol[2] + w) break else: # if self.selected != None: w = 0.1 self.efield_outline.bounds = (self.epol[0] - w, self.epol[0] + w, self.epol[1] - w, self.epol[1] + w, self.epol[2] - w, self.epol[2] + w) self.epol = np.array(picker.pick_position) self.plotContinuumOrbital() self.plotMFPAD() self.selected = None def plotMFPAD(self): Rmax = 80.0 npts = 30 E = self.photo_kinetic_energy k = np.sqrt(2 * E) wavelength = 2.0 * np.pi / k # spherical grid rs, thetas, phis = np.mgrid[Rmax:(Rmax + wavelength):30j, 0.0:np.pi:npts * 1j, 0.0:2 * np.pi:npts * 1j] # transformed into cartesian coordinates xs = rs * np.sin(thetas) * np.cos(phis) ys = rs * np.sin(thetas) * np.sin(phis) zs = rs * np.cos(thetas) grid = (xs, ys, zs) amplitude_continuum = Cube.orbital_amplitude(grid, self.bs_free.bfs, self.mo_free, cache=False) # integrate continuum orbital along radial-direction for 1 wavelength wfn2 = abs(amplitude_continuum)**2 # dr = wavelength / 30.0 wfn2_angular = np.sum(wfn2 * dr, axis=0) # SPHERICAL PLOT self.MFPADfig3D.clf() xs = wfn2_angular * np.sin(thetas[0, :, :]) * np.cos(phis[0, :, :]) ys = wfn2_angular * np.sin(thetas[0, :, :]) * np.sin(phis[0, :, :]) zs = wfn2_angular * np.cos(thetas[0, :, :]) ax = self.MFPADfig3D.add_subplot(111, projection='3d') rmax = wfn2_angular.max() * 1.5 ax.set_xlim((-rmax, rmax)) ax.set_ylim((-rmax, rmax)) ax.set_zlim((-rmax, rmax)) ax.set_xlabel("x") ax.set_ylabel("y") ax.set_zlabel("z") # draw efield arrow_vec = wfn2_angular.max() * 1.2 * self.epol / la.norm(self.epol) arrow = Arrow3D([0.0, arrow_vec[0]], [0.0, arrow_vec[1]], [0.0, arrow_vec[2]], color=(0.0, 1.0, 0.0), mutation_scale=20, lw=2, arrowstyle="-|>") ax.add_artist(arrow) ax.text(arrow_vec[0], arrow_vec[1], arrow_vec[2], "E-field", color=(0.0, 1.0, 0.0)) ax.plot_surface(xs, ys, zs, rstride=1, cstride=1) ax.scatter(xs, ys, zs, color="k", s=20) ax.get_xaxis().set_ticks([]) ax.get_yaxis().set_ticks([]) ax.w_zaxis.set_ticks([]) self.MFPADCanvas3D.draw() # 2D PLOT self.MFPADfig2D.clf() ax = self.MFPADfig2D.add_subplot(111) image = ax.imshow(np.fliplr(wfn2_angular.transpose()), extent=[0.0, np.pi, 0.0, 2 * np.pi], aspect=0.5, origin='lower') ax.set_xlim((0.0, np.pi)) ax.set_ylim((0.0, 2 * np.pi)) ax.set_xlabel("$\\theta$") ax.set_ylabel("$\phi$") self.MFPADfig2D.colorbar(image) # show piercing points of E-field vector # ... coming out of the plane r = la.norm(self.epol) th_efield = np.arccos(self.epol[2] / r) phi_efield = np.arctan2(self.epol[1], self.epol[0]) + np.pi print "th, phi = %s %s" % (th_efield, phi_efield) ax.plot([th_efield], [phi_efield], "o", markersize=10, color=(0.0, 1.0, 0.0)) ax.text(th_efield, phi_efield, "E-field", color=(0.0, 1.0, 0.0), ha="center", va="top") # ... going into the plane th_efield = np.arccos(-self.epol[2] / r) phi_efield = np.arctan2(-self.epol[1], -self.epol[0]) + np.pi print "- th, phi = %s %s" % (th_efield, phi_efield) ax.plot([th_efield], [phi_efield], "x", markersize=10, color=(0.0, 1.0, 0.0)) self.MFPADCanvas2D.draw() @busy def activateAveragedPAD(self, s): print "s = %s" % s self.loadContinuum() self.plotAveragedPAD() @busy def plotAveragedPAD(self): if self.activate_average.isChecked() == False: for fig in [self.AvgPADfig1D, self.AvgPADfig2D]: fig.clf() ax = fig.add_subplot(111) ax.set_xlim((0.0, 1.0)) ax.set_ylim((0.0, 1.0)) ax.text(0.2, 0.6, "click checkbox to", fontsize=20) ax.text(0.2, 0.3, "activate averaging", fontsize=20) #self.AvgPADfig1D.draw() else: pad, betas = self.orientation_averaging.averaged_pad(self.mo_bound) # 1D plot # cut along any phi self.AvgPADfig1D.clf() ax1d = self.AvgPADfig1D.add_subplot(111, projection='polar') nx, ny = pad.shape thetas = np.linspace(0.0, np.pi, nx) for i in range(0, ny): ax1d.plot(thetas, pad[:, i], color="black") ax1d.plot(thetas + np.pi, pad[::-1, i], color="black") # plot PAD = sigma/4pi * (1 + beta2 * P2(cos(theta))) in read pad2 = betas[0] / (4.0 * np.pi) * (1.0 + betas[2] * 0.5 * (3 * np.cos(thetas)**2 - 1.0)) print "max(pad) / max(pad2) = %s" % (pad.max() / pad2.max()) ax1d.plot(thetas, pad2, color="red", ls="-.") ax1d.plot(thetas + np.pi, pad2[::-1], color="red", ls="-.") self.AvgPADCanvas1D.draw() # 2D plot self.AvgPADfig2D.clf() ax2d = self.AvgPADfig2D.add_subplot(111) image = ax2d.imshow(np.fliplr(pad.transpose()), extent=[0.0, np.pi, 0.0, 2 * np.pi], aspect=0.5, origin='lower') ax2d.set_xlim((0.0, np.pi)) ax2d.set_ylim((0.0, 2 * np.pi)) ax2d.set_xlabel("$\\theta$") ax2d.set_ylabel("$\phi$") self.AvgPADfig2D.colorbar(image) self.AvgPADCanvas2D.draw() # Table n = self.avgpadTable.rowCount() self.avgpadTable.insertRow(n) self.avgpadTable.setItem( n, 0, QtGui.QTableWidgetItem("%6.4f" % (self.photo_kinetic_energy * 27.211))) for i, b in enumerate(betas): if i == 0: # sigma self.avgpadTable.setItem( n, i + 1, QtGui.QTableWidgetItem("%e" % betas[i])) else: self.avgpadTable.setItem( n, i + 1, QtGui.QTableWidgetItem("%6.4f" % betas[i])) def deletePADTable(self): n = self.avgpadTable.rowCount() for i in range(0, n): self.avgpadTable.removeRow(0) def scanPADTable(self): if self.activate_average.isChecked() == False: self.statusBar().showMessage( "You have to activate averaging first before performing a scan!" ) return self.deletePADTable() # scan through all PKE's and fill table print "SCAN" nskip = len(slako_tables_scattering.energies) / int( self.settings.getOption("Scan", "nr. points")) nskip = max(1, nskip) print "nskip = %s" % nskip for i, pke in enumerate(slako_tables_scattering.energies): if i % nskip != 0: continue print "*** PKE = %s Hartree ***" % pke self.pke_slider.setValue(i) self.changePKE() def getPADTable(self): m, n = self.avgpadTable.rowCount(), self.avgpadTable.columnCount() pad_data = np.zeros((m, n)) for i in range(0, m): for j in range(0, n): item = self.avgpadTable.item(i, j) if item is not None: pad_data[i, j] = float(item.text()) return pad_data def savePADTable(self): data_file = QtGui.QFileDialog.getSaveFileName(self, 'Save Table', '', '*')[0] pad_data = self.getPADTable() if str(data_file) != "": fh = open(data_file, "w") print >> fh, "# PKE/eV sigma beta1 beta2 beta3 beta4" np.savetxt(fh, pad_data) fh.close() print "Wrote table with betas to %s" % data_file def plotPADTable(self): pad_data = self.getPADTable() self.plot_window = FigurePopup(pad_data[:, 0], pad_data[:, 1], pad_data[:, 3]) def editSettings(self): self.settings_window = SettingsPopup(self.settings)