def grad(x): thomson_cap = XYZ.vector2atomlist(x, thomson_cap_ref) thomson_pts = thomson_tube + thomson_cap gr = XYZ.atomlist2vector(gradient_energy(thomson_pts)) return gr
parser = OptionParser(usage) parser.add_option( "--convert", dest="convert", help= "Type of conversion: 'b2a' - from bohr to Angstrom, 'a2b' - from Angstrom to bohr [default: %default]", default="b2a") (opts, args) = parser.parse_args() if len(args) < 2: print usage exit(-1) xyz_in = args[0] xyz_out = args[1] if xyz_in[-2:] == "in": # initial conditions file ###.in coords, vels = XYZ.read_initial_conditions(xyz_in, units="") structures = [coords] else: structures = XYZ.read_xyz(xyz_in, units="") unit_fac, units = unit_conversion[opts.convert] for i, atomlist_in in enumerate(structures): vec_in = XYZ.atomlist2vector(atomlist_in) vec_out = unit_fac * vec_in atomlist_out = XYZ.vector2atomlist(vec_out, atomlist_in) if i == 0: mode = "w" else: mode = "a" XYZ.write_xyz(xyz_out, [atomlist_out], units="", mode=mode)
def solve_constrained_Thomson(thomson_tube, thomson_cap, L, R): def potential_energy(thomson_pts): en = 0.0 for i, (Zi, posi) in enumerate(thomson_pts): for j, (Zj, posj) in enumerate(thomson_pts): if i == j: continue rij = la.norm(posi - posj) if rij == 0.0: raise ValueError("rij = 0") en += 1.0 / rij en *= 0.5 return en def gradient_energy(thomson_pts): grad = [] for k, (Zk, posk) in enumerate(thomson_pts): grad_k = np.zeros(3) for j, (Zj, posj) in enumerate(thomson_pts): if j == k: continue rjk = posk - posj grad_k += rjk / pow(la.norm(rjk), 3) grad.append((Zk, grad_k)) return grad def enforce_constaints(thomson_cap): thomson_cap_rescaled = [] for i, (Zi, posi) in enumerate(thomson_cap): x, y, z = posi z -= L if x >= 0: # keep point glued to a sphere of radius R r = np.sqrt(x * x + y * y + z * z) sc = R / r x, y, z = sc * x, sc * y, sc * z + L else: # point has moved into the tube # keep it stuck to a cylinder of radius R r = np.sqrt(x * x + y * y) sc = R / r x, y = sc * x, sc * y # z is left as it is thomson_cap_rescaled.append((Zi, np.array([x, y, z]))) return thomson_cap_rescaled # function to minimize thomson_cap_ref = thomson_cap XYZ.write_xyz("/tmp/thomson_minimization.xyz", [thomson_cap_ref], mode="w") def f(x): thomson_cap = XYZ.vector2atomlist(x, thomson_cap_ref) # thomson_cap = enforce_constaints(thomson_cap) print "thomson_cap" print thomson_cap thomson_pts = thomson_tube + thomson_cap XYZ.write_xyz("/tmp/thomson_minimization.xyz", [thomson_cap], mode="a") en = potential_energy(thomson_pts) # gradient of potential energy grad = XYZ.atomlist2vector(gradient_energy(thomson_pts)) print "en = %s" % en return en #, grad def grad(x): thomson_cap = XYZ.vector2atomlist(x, thomson_cap_ref) thomson_pts = thomson_tube + thomson_cap gr = XYZ.atomlist2vector(gradient_energy(thomson_pts)) return gr x0 = XYZ.atomlist2vector(thomson_cap) err = optimize.check_grad(f, grad, x0) assert err < 1.0e-10, "err = %s" % err xmin, fmin, d = optimize.fmin_l_bfgs_b(f, x0) thomson_cap_min = XYZ.vector2atomlist(xmin, thomson_cap_ref) return thomson_cap_min
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 f(x): atomlist = XYZ.vector2atomlist(x, atomlist0) cavity.constructSAS(atomlist) gamma_solvent = cavity.constructCOSMO() return gamma_solvent[i, j]
def save_xyz(x, mode="a"): atomlist_opt = XYZ.vector2atomlist(x, atomlist) XYZ.write_xyz(xyz_trace, [atomlist_opt], title="charge=%s" % kwds.get("charge", 0), mode=mode)
coord_name = "DIHEDRAL(%d-%d-%d-%d)" % (I, J, K, L) else: raise ValueError("Format of scan '%s' not understood!" % scan) # shift indices to programmer's style (starting at 0) IJKL = map(lambda I: I - 1, IJKL) IC = InternalValenceCoords(atomlist0, freeze=freeze, verbose=opts.verbose) # cartesian coordinates along the scan scan_geometries = [atomlist0] # value of internal coordinate IJKL along the scan val0 = IC.coordinate_value(x0, IJKL) scan_coords = [val0] x1 = x0 for i in range(0, nsteps): # cartesian coordinates at displaced geometry x1 = IC.internal_step(x1, IJKL, incr) # new value of internal coordinate, should be approximately # val0 + incr val1 = IC.coordinate_value(x1, IJKL) atomlist1 = XYZ.vector2atomlist(x1, atomlist0) scan_geometries.append(atomlist1) scan_coords.append(val1) print "step %d %s = %10.6f" % (i + 1, coord_name, val1) XYZ.write_xyz(xyz_out, scan_geometries, mode="w") print "displaced geometries were saved to '%s'" % xyz_out
def save_xyz(x, mode="a", info=""): atomlist_opt = XYZ.vector2atomlist(x, atomlist) XYZ.write_xyz(xyz_opt, [atomlist_opt], \ title="charge=%s %s" % (kwds.get("charge",0), info),\ mode=mode)
def capped_uff_nanotube(cnt, NcapN=0, NcapS=0, optimize_uff=1, out_xyz="/tmp/cnt.xyz"): """ build capped nanotube and optimize it with UFF """ nC, nT = cnt.NrCapAtoms() print("Expected number of Thomson points nT = %s" % nT) print( "If the caps have holes or very close atoms, you should try increasing" ) print( "or reducing the number of Thomson points (by changing the options NcapN or NcapS)" ) if NcapN == 0: NcapN = nT if NcapS == 0: NcapS = nT """ Different number of Thomson points are tested to find a pair (NcapN,NcapS) """ dcap = [] dnmin = 0 dnmax = 1 ntrial = 5 for dnN in range(dnmin, dnmax): for dnS in range(dnmin, dnmax): # try for 5 times for n in range(0, ntrial): dcap.append((dnN, dnS)) dcap = sorted(dcap, key=lambda a_b: abs(a_b[0]) + abs(a_b[1])) for (dnN, dnS) in dcap: print("north cap: %s points, south cap: %s points" % (NcapN + dnN, NcapS + dnS)) atomlist_carbons = capped_nanotube(cnt, NcapN + dnN, NcapS + dnS) if optimize_uff == 1: print( "CNT will be optimized further with the Universal Force Field of G09" ) tmp_dir = "/tmp" com_file = join(tmp_dir, "cnt_uff_opt.com") chk_file = join(tmp_dir, "cnt_uff_opt.chk") fchk_file = chk_file.replace(".chk", ".fchk") Gaussian.write_input(com_file, atomlist_carbons, \ route="# UFF Opt", \ title="Optimize (%s,%s)-Nanotube with UFF" % (cnt.n1,cnt.n2), chk=chk_file,mem=1000) try: Gaussian.run(com_file) # format checkpoint file Gaussian.formchk(chk_file, fchk_file) Data = Checkpoint.parseCheckpointFile(fchk_file) pos = Data["_Current_cartesian_coordinates"] atomlist_uff = XYZ.vector2atomlist(pos, atomlist_carbons) atomlist_carbons = atomlist_uff except Gaussian.GaussianError: print("UFF-optimization failed!") continue if check_connectivity(atomlist_carbons) == True: # no holes, correct connectivity break XYZ.write_xyz("/tmp/trials.xyz", [atomlist_carbons], mode="a") else: print("") XYZ.write_xyz(expandvars(expanduser(out_xyz)), [atomlist_carbons]) print("Geometry of capped CNT written to %s" % out_xyz)
def relaxed_scan(self, IJKL, nsteps, incr): """ perform a relaxed scan along the internal coordinate IJKL. The coordinate is incremented from its initial value by `nsteps` steps of size `incr`. In each step the value of the scan coordinate is kept constant while all other degrees of freedom are relaxed. Parameters ---------- IJKL : tuple of 2, 3 or 4 atom indices (starting at 0) (I,J) - bond between atoms I and J (I,J,K) - valence angle I-J-K (I,J,K,L) - dihedral angle between the bonds I-J, J-K and K-L nsteps : number of steps incr : increment in each step, in bohr for bond lengths, in radians for angles """ # length of tuple IJKL determines type of internal coordinate typ = len(IJKL) coord_type = {2: "bond", 3: "angle", 4: "dihedral"} conv_facs = { 2: AtomicData.bohr_to_angs, 3: 180.0 / np.pi, 4: 180.0 / np.pi } units = {2: "Angs", 3: "degs", 4: "degs"} # assert self.coord_system == "internal" # freeze scan coordinate at its current value self.IC.freeze(IJKL) print(" ============ ") print(" RELAXED SCAN ") print(" ============ ") print(" The internal coordinate defined by the atom indices") print(" IJKL = %s " % [I + 1 for I in IJKL]) print(" is scanned in %d steps of size %8.5f %s." % (nsteps, incr * conv_facs[typ], units[typ])) def save_step(): """function is called after each minimization""" scan_coord = self.IC.coordinate_value(xi, IJKL) print("current value of scan coordinate : %s" % scan_coord) if i == 0: mode = "w" else: mode = "a" # save relaxed geometry of step i XYZ.write_xyz(self.xyz_scan, [atomlist], title="charge=%s energy=%s" % (self.geom_kwds.get("charge", 0), self.enI), mode=mode) # save table with energies along scan fh = open(self.dat_scan, mode) if i == 0: # write header print("# Relaxed scan along %s defined by atoms %s" % (coord_type[typ], [I + 1 for I in IJKL]), file=fh) print("# state of interest: %d" % self.state, file=fh) print("# ", file=fh) print("# Scan coordinate Energies ", file=fh) print("# %s Hartree " % units[typ], file=fh) print(" %8.5f " % scan_coord, end=' ', file=fh) for en in self.energies: print(" %e " % en, end=' ', file=fh) print("", file=fh) fh.close() for i in range(0, nsteps): print("Step %d of relaxed scan" % i) # relax all other coordinates self.minimize() # optimized geometry of i-th step atomlist = self.getGeometry() xi = XYZ.atomlist2vector(atomlist) # save geometry save_step() # take a step of size `incr` along the scan coordinate xip1 = self.IC.internal_step(xi, IJKL, incr) # update geometry atomlist = XYZ.vector2atomlist(xip1, atomlist) self.setGeometry(atomlist, geom_kwds=self.geom_kwds) print("Scan geometries were written to %s" % self.xyz_scan) print("Table with scan energies was written to %s" % self.dat_scan)
return gradI print "Computing Hessian" hess = HarmonicApproximation.numerical_hessian_G(grad, xopt) np.savetxt("hessian.dat", hess) masses = AtomicData.atomlist2masses(atomlist) vib_freq, vib_modes = HarmonicApproximation.vibrational_analysis(xopt, hess, masses, \ zero_threshold=1.0e-9, is_molecule=True) # compute thermodynamic quantities and write summary thermo = Thermochemistry.Thermochemistry( atomlist, Eopt, vib_freq, pes.tddftb.dftb2.getSymmetryGroup()) thermo.calculate() # write vibrational modes to molden file molden = MoldenExporterSectioned(pes.tddftb.dftb2) atomlist_opt = XYZ.vector2atomlist(xopt, atomlist) molden.addVibrations(atomlist_opt, vib_freq.real, vib_modes.transpose()) molden.export("vib.molden") ## It's better to use the script initial_conditions.py for sampling from the Wigner ## distribution """ # SAMPLE INITIAL CONDITIONS FROM WIGNER DISTRIBUTION qs,ps = HarmonicApproximation.initial_conditions_wigner(xopt, hess, masses, Nsample=200) HarmonicApproximation.save_initial_conditions(atomlist, qs, ps, ".", "dynamics") """ # timing print T
def save_xyz(x, mode="a"): self.atomlist = XYZ.vector2atomlist(x, self.atomlist) XYZ.write_xyz(self.xyz_opt, [self.atomlist], \ title="charge=%s energy= %s" % (self.geom_kwds.get("charge",0), self.enI),\ mode=mode) return x
def minimize(self): I = self.state # convert geometry to a vector x0 = XYZ.atomlist2vector(self.atomlist) # This member variable holds the last energy of the state # of interest. self.enI = 0.0 # last available energies of all electronic states that were # calculated self.energies = None # FIND ENERGY MINIMUM # f is the objective function that should be minimized # it returns (f(x), f'(x)) def f_cart(x): # if I == 0 and type(self.pes.tddftb.XmY) != type(None): # Only ground state is needed. However, at the start # a single TD-DFT calculation is performed to initialize # all variables (e.g. X-Y), so that the program does not # complain about non-existing variables. enI, gradI = self.pes.getEnergyAndGradient_S0(x) energies = np.array([enI]) else: energies, gradI = self.pes.getEnergiesAndGradient(x, I) enI = energies[I] self.enI = enI self.energies = energies print("E = %2.7f |grad| = %2.7f" % (enI, la.norm(gradI))) # # also save geometries from line searches save_xyz(x) return enI, gradI print("Intermediate geometries will be written to %s" % self.xyz_opt) # This is a callback function that is executed for each optimization step. # It appends the current geometry to an xyz-file. def save_xyz(x, mode="a"): self.atomlist = XYZ.vector2atomlist(x, self.atomlist) XYZ.write_xyz(self.xyz_opt, [self.atomlist], \ title="charge=%s energy= %s" % (self.geom_kwds.get("charge",0), self.enI),\ mode=mode) return x Nat = len(self.atomlist) if self.coord_system == "cartesian": print( "optimization is performed directly in cartesian coordinates") q0 = x0 objective_func = f_cart save_geometry = save_xyz max_steplen = None elif self.coord_system == "internal": print( "optimization is performed in redundant internal coordinates") # transform cartesian to internal coordinates, x0 ~ q0 q0 = self.IC.cartesian2internal(x0) # define functions that wrap the cartesian<->internal transformations def objective_func(q): # transform back from internal to cartesian coordinates x = self.IC.internal2cartesian(q) self.IC.cartesian2internal(x) # compute energy and gradient in cartesian coordinates en, grad_cart = f_cart(x) # transform gradient to internal coordinates grad = self.IC.transform_gradient(x, grad_cart) return en, grad def save_geometry(q, **kwds): # transform back from internal to cartesian coordinates x = self.IC.internal2cartesian(q) # save cartesian coordinates save_xyz(x, **kwds) return x def max_steplen(q0, v): """ find a step size `a` such that the internal->cartesian transformation converges for the point q = q0+a*v """ a = 1.0 for i in range(0, 7): q = q0 + a * v try: x = self.IC.internal2cartesian(q) except NotConvergedError as e: # reduce step size by factor of 1/2 a /= 2.0 continue break else: raise RuntimeError( "Could not find a step size for which the transformation from internal to cartesian coordinates would work for q=q0+a*v! Last step size a= %e |v|= %e |a*v|= %e" % (a, la.norm(v), la.norm(a * v))) return a else: raise ValueError("Unknown coordinate system '%s'!" % self.coord_system) # save initial energy and geometry objective_func(q0) save_geometry(q0, mode="w") options = { 'gtol': self.grad_tol, 'maxiter': self.maxiter, 'gtol': self.grad_tol, 'norm': 2 } if self.method == 'CG': # The "BFGS" method is probably better than "CG", but the line search in BFGS is expensive. res = optimize.minimize(objective_func, q0, method="CG", jac=True, callback=save_geometry, options=options) #res = optimize.minimize(objective_func, q0, method="BFGS", jac=True, callback=save_geometry, options=options) elif self.method in ['Steepest Descent', 'Newton', 'BFGS']: # My own implementation of optimization algorithms res = minimize( objective_func, q0, method=self.method, #line_search_method="largest", callback=save_geometry, max_steplen=max_steplen, maxiter=self.maxiter, gtol=self.grad_tol, ftol=self.func_tol) else: raise ValueError("Unknown optimization algorithm '%s'!" % self.method) # save optimized geometry qopt = res.x Eopt = res.fun xopt = save_geometry(qopt) print("Optimized geometry written to %s" % self.xyz_opt) if self.calc_hessian == 1: # COMPUTE HESSIAN AND VIBRATIONAL MODES # The hessian is calculated by numerical differentiation of the # analytical cartesian gradients def grad(x): en, grad_cart = f_cart(x) return grad_cart print("Computing Hessian") hess = HarmonicApproximation.numerical_hessian_G(grad, xopt) np.savetxt("hessian.dat", hess) masses = AtomicData.atomlist2masses(atomlist) vib_freq, vib_modes = HarmonicApproximation.vibrational_analysis(xopt, hess, masses, \ zero_threshold=1.0e-9, is_molecule=True) # compute thermodynamic quantities and write summary thermo = Thermochemistry.Thermochemistry( atomlist, Eopt, vib_freq, self.pes.tddftb.dftb2.getSymmetryGroup()) thermo.calculate() # write vibrational modes to molden file molden = MoldenExporterSectioned(self.pes.tddftb.dftb2) atomlist_opt = XYZ.vector2atomlist(xopt, atomlist) molden.addVibrations(atomlist_opt, vib_freq.real, vib_modes.transpose()) molden.export("vib.molden") ## It's better to use the script initial_conditions.py for sampling from the Wigner ## distribution """
x = XYZ.atomlist2vector(atomlist) pes.getEnergies(x) for i, atomlist in enumerate(XYZ.read_xyz(geom_file)): # compute electronic ground state forces with DFTB x = XYZ.atomlist2vector(atomlist) en = pes.getEnergy_S0(x) # total ground state energy including repulsive potential en_tot = en[0] print "Structure %d enTot= %s Hartree" % (i, en_tot) # electronic energy without repulsive potential en_elec = pes.tddftb.dftb2.getEnergies()[2] gradVrep, gradE0, gradExc = pes.grads.gradient(I=0) # exclude repulsive potential from forces grad = gradE0 + gradExc forces = XYZ.vector2atomlist(-grad, atomlist) if i == 0: mode = 'w' else: mode = 'a' print >> fh_en, "%2.7f" % en_elec XYZ.write_xyz( force_file, [forces], title="forces with DFTB, total_energy=%2.7f elec_energy=%2.7f" % (en_tot, en_elec), units="hartree/bohr", mode=mode) fh_en.close()
def opt(self, max_iter=5000, step_size=1.0, gtol=0.005): """ optimize MECI by following the gradient downhill Optional -------- max_iter : maximum number of steps step_size : The geometry is updated by making a step x -> x - step_size * grad gtol : tolerance for the gradient norm """ print("optimize MECI by following the gradient") print(" lower state : %d" % state1) print(" upper state : %d" % state2) print("Intermediate geometries are written to 'meci_path.xyz'") print("and a table with energies is written to 'meci_energies.dat'") # initial geometry x = XYZ.atomlist2vector(self.atomlist0) # overwrite geometries from previous run mode = "w" en_fh = open("meci_energies.dat", "w") print("# optimization of MECI", file=en_fh) print( "# STEP ENERGY(1)/Hartree ENERGY(2)/Hartree ENERGY(3)-EPS/Hartree", file=en_fh) for i in range(0, max_iter): e1, e2, grad = self.getGradient(x) # energy gap en_gap = e2 - e1 self.adjust_shift(en_gap) # save intermediate steps and energies atomlist = XYZ.vector2atomlist(x, self.atomlist0) XYZ.write_xyz("meci_path.xyz", [atomlist], title="ENERGY= %e GAP= %e" % (e2, en_gap), mode=mode) print(" %4.1d %+15.10f %+15.10f %+15.10f" % (i, e1, e2, e2 - self.epsilon), file=en_fh) en_fh.flush() # append to trajectory file mode = "a" # gnorm = la.norm(grad) print(" %4.1d e2= %e e2-e1= %e |grad|= %e (tolerance= %e)" % (i, e2, en_gap, gnorm, gtol)) if gnorm < gtol: break if self.coord_system == "cartesian": # descend along gradient directly in cartesian coordinates x -= step_size * grad elif self.coord_system == "internal": # use internal redundant coordinates # 1) transform cartesian to internal coordinates x -> q q = self.ic.cartesian2internal(x) # 2) transform cartesian gradient to internal coordinates # dE/dx -> dE/dq grad_intern = self.ic.transform_gradient(x, grad) # 3) take step along gradient in internal coordinates q = q - step_size * grad_intern # 4) deduce new cartesian coordinates from new internal coordinates q x = self.ic.internal2cartesian(q) else: print("exceeded maximum number of steps") en_fh.close()
def cartesian2internal(masses, pos, ref_atomlist, debug=0): """ convert cartesian coordinates to internal coordinates + translation + rotation. Because global rotation and translation are included the number of internal coordinates equals the number of cartesian coordinates. Parameters: =========== masses: list with masses of each atom in atomic units pos: pos[3*i:3*i+3] are the cartesian positions of atom i ref_atomlist: list of tuples (Zi, (xi,yi,zi), positions do not have to be the same as in pos, only the atomic numbers are needed Optional: ========= debug: if > 0, Returns: ======== numpy array with zmat, center of mass and Euler angles """ atomlist = XYZ.vector2atomlist(pos, ref_atomlist) Nat = len(atomlist) print("Original positions") print("==================") for i in range(0, Nat): print("%s " % i, end=' ') print("%.4f %.4f %.4f" % tuple(pos[3*i:3*i+3])) cm2 = center_of_mass(masses, pos) pos1 = zeros(pos.shape) for i in range(0, Nat): pos1[3*i:3*i+3] = pos[3*i:3*i+3] - cm2 (a2,b2,g2) = euler_angles_inertia(masses, pos1) # test transformation pos_std1 = molpro_standard_orientation(masses, pos) pos = copy(pos) zmat = cartesian2Zmatrix(atomlist) pos2 = Zmatrix2cartesian(zmat) pos_std2 = molpro_standard_orientation(masses, pos2) # XYZ.write_xyz("std1.xyz", [XYZ.vector2atomlist(pos_std1, ref_atomlist)]) # XYZ.write_xyz("std2.xyz", [XYZ.vector2atomlist(pos_std2, ref_atomlist)]) pos2 = pos_std2 # R2 = inv(EulerAngles2Rotation(a2,b2,g2)) R2 = inv(EulerAngles2Rotation(a2,b2,g2)) posrecovered = zeros(pos.shape) for i in range(0, Nat): posrecovered[3*i:3*i+3] = dot(R2, pos2[3*i:3*i+3]) + cm2 # XYZ.write_xyz("recovered.xyz", [XYZ.vector2atomlist(posrecovered, ref_atomlist)]) cm3 = center_of_mass(masses, posrecovered) (a3,b3,g3) = euler_angles_inertia(masses, posrecovered) err = sum(abs(posrecovered - pos)) assert(err < 1.0e-5) # cm = list(center_of_mass(masses, pos)) # shift center of mass to origin for i in range(0, len(ref_atomlist)): pos[3*i:3*i+3] -= cm (a,b,g) = euler_angles_inertia(masses, pos) internal = zmat + cm + [a,b,g] return array(internal)
def opt(xyzfile, optionfile): """performs an optimization""" outputfile = open("output_dftb.txt", "a") # redirect output to file sys.stdout = outputfile try: I = 0 # index of electronic state (ground state) atomlist = XYZ.read_xyz(xyzfile)[0] # read atomlist kwds = XYZ.extract_keywords_xyz( xyzfile) # read keywords from xyz-file (charge) options = read_options(optionfile) # read options scf_options = extract_options(options, SCF_OPTIONLIST) # get scf-options # optimization (taken from optimize.py) pes = MyPES(atomlist, options, Nst=max(I + 1, 2), **kwds) x0 = XYZ.atomlist2vector(atomlist) #convert geometry to a vector def f(x): save_xyz(x) # also save geometries from line searches if I == 0 and type(pes.tddftb.XmY) != type(None): # only ground state is needed. However, at the start # a single TD-DFT calculation is performed to initialize # all variables (e.g. X-Y), so that the program does not # complain about non-existing variables. enI, gradI = pes.getEnergyAndGradient_S0(x) else: energies, gradI = pes.getEnergiesAndGradient(x, I) enI = energies[I] print "E = %2.7f" % (enI) return enI, gradI xyz_trace = xyzfile.replace(".xyz", "_trace.xyz") # This is a callback function that is executed by numpy for each optimization step. # It appends the current geometry to an xyz-file. def save_xyz(x, mode="a"): atomlist_opt = XYZ.vector2atomlist(x, atomlist) XYZ.write_xyz(xyz_trace, [atomlist_opt], title="charge=%s" % kwds.get("charge", 0), mode=mode) save_xyz(x0, mode="w") # write original geometry Nat = len(atomlist) min_options = {'gtol': 1.0e-7, 'norm': 2} # The "BFGS" method is probably better than "CG", but the line search in BFGS is expensive. res = optimize.minimize(f, x0, method="CG", jac=True, callback=save_xyz, options=min_options) # res = optimize.minimize(f, x0, method="BFGS", jac=True, callback=save_xyz, options=options) xopt = res.x save_xyz(xopt) print "Intermediate geometries written into file {}".format(xyz_trace) # write optimized geometry into file atomlist_opt = XYZ.vector2atomlist(xopt, atomlist) xyz_opt = xyzfile.replace(".xyz", "_opt.xyz") XYZ.write_xyz(xyz_opt, [atomlist_opt], title="charge=%s" % kwds.get("charge", 0), mode="w") # calculate energy for optimized geometry dftb2 = DFTB2(atomlist_opt, **options) # create dftb object dftb2.setGeometry(atomlist_opt, charge=kwds.get("charge", 0.0)) dftb2.getEnergy(**scf_options) energies = list(dftb2.getEnergies()) # get partial energies if dftb2.long_range_correction == 1: # add long range correction to partial energies energies.append(dftb2.E_HF_x) return str(energies) except: print sys.exc_info() return "error"
def setInternalCoordinates(self, internal): pos = internal2cartesian(self.masses, internal) self.atomlist = XYZ.vector2atomlist(pos, self.atomlist)
import sys atomlist0 = XYZ.read_xyz(sys.argv[1])[-1] x0 = XYZ.atomlist2vector(atomlist0) IC = InternalValenceCoords(atomlist0, verbose=3) test_wilson_bmatrix(IC.force_field, x0) # 1) transform cartesian to internal coordinates, x0 ~ q0 q0 = IC.cartesian2internal(x0) # 2) Take a step along the last internal coordinate, # this will only work if the coordinate is not coupled # to any other. dq = np.zeros(len(q0)) dq[-1] = 0.1 q = q0 + dq # 3) and transform back x ~ q x = IC.internal2cartesian(q) # veriy that really x corresponds to internal coordinate q q_test = IC.cartesian2internal(x) err = la.norm(q - q_test) # assert err < 1.0e-10, "|q(x(q)) - q|= %e" % err # 4) save initial and displaced geometries atomlist = XYZ.vector2atomlist(x, atomlist0) XYZ.write_xyz("/tmp/test_internal_coords.xyz", [atomlist0, atomlist])
parser = optparse.OptionParser(usage) parser.add_option("--n1", dest="n1", help="Chiral index n1 [default: %default]", default=6, type=int) parser.add_option("--n2", dest="n2", help="Chiral index n2 [default: %default]", default=5, type=int) parser.add_option("--L", dest="L", help="Length of CNT in bohr [default: %default]", default=100.0, type=int) parser.add_option("--NcapN", dest="NcapN", help="Number of Thomson points on the northern cap, if set to 0 the number is estimated from the density of carbon atoms in graphene [default: %default]", default=0, type=int) parser.add_option("--NcapS", dest="NcapS", help="Number of Thomson points on the southern cap, if set to 0 the number is estimated from the density of carbon atoms in graphene [default: %default]", default=0, type=int) parser.add_option("--out_xyz", dest="out_xyz", help="Save the CNT geometry to this xyz-file [default: %default]", default="/tmp/cnt.xyz") parser.add_option("--optimize_uff", dest="optimize_uff", help="Optimize the built CNT with the UFF force field. This requires that the program g09 is installed. [default: %default]", default=1, type=int) (opts,args) = parser.parse_args() atomlist_carbons = capped_nanotube(opts.n1,opts.n2,opts.L,NcapN=opts.NcapN, NcapS=opts.NcapS) if opts.optimize_uff == 1: print("CNT will be optimized further with the Universal Force Field of G09") tmp_dir="/tmp" com_file = join(tmp_dir, "cnt_uff_opt.com") chk_file = join(tmp_dir, "cnt_uff_opt.chk") fchk_file = chk_file.replace(".chk", ".fchk") Gaussian.write_input(com_file, atomlist_carbons, \ route="# UFF Opt", title="Optimize (%s,%s)-Nanotube with UFF" % (opts.n1,opts.n2), chk=chk_file,mem=1000) Gaussian.run(com_file) # format checkpoint file Gaussian.formchk(chk_file, fchk_file) Data = Checkpoint.parseCheckpointFile(fchk_file) pos = Data["_Current_cartesian_coordinates"] atomlist_uff = XYZ.vector2atomlist(pos, atomlist_carbons) atomlist_carbons = atomlist_uff XYZ.write_xyz(opts.out_xyz, [atomlist_carbons]) print("Geometry of capped CNT written to %s" % opts.out_xyz)
ps[3 * A:3 * (A + 1), i] *= 0.001 # # STATISTICS # kinetic energy Ekin = np.zeros(opts.Nsample) for i in range(0, opts.Nsample): Ekin[i] = np.sum(ps[:, i]**2 / (2 * np.array(masses))) # temperature (3*N-6)/2 k T = Ekin f = len(masses) - 6 # degrees of freedom minus rotation and translation T = Ekin * 2.0 / (f * AtomicData.kBoltzmann) print "Initial Condition Statistics" print "============================" print "averages are over %d trajectories" % opts.Nsample print "kinetic energy = (%2.7f +- %2.7f) Hartree" % (np.mean(Ekin), np.std(Ekin)) print "temperature = (%2.7f +- %2.7f) Kelvin" % (np.mean(T), np.std(T)) print "" # geometries only wigner_xyz = join(opts.outdir, "wigner.xyz") geometries = [ XYZ.vector2atomlist(qs[:, i], atomlist) for i in range(0, opts.Nsample) ] XYZ.write_xyz(wigner_xyz, geometries) print "Wigner ensemble written to file %s" % wigner_xyz # initial conditions HarmonicApproximation.save_initial_conditions(atomlist, qs, ps, opts.outdir, "dynamics")
atomlist0 = XYZ.read_xyz(xyz0)[0] x0 = XYZ.atomlist2vector(atomlist0) # read final geometry atomlist1 = XYZ.read_xyz(xyz1)[0] x1 = XYZ.atomlist2vector(atomlist1) # interpolation parameter rs = np.linspace(0.0, 1.0, N) geometries_interp = [] if opts.coord_system == "cartesian": for r in rs: xr = x0 + r * (x1 - x0) geometries_interp.append(XYZ.vector2atomlist(xr, atomlist0)) elif opts.coord_system == "internal": IC = InternalValenceCoords(atomlist0) # initial and final geometry in internal coordinates q1 = IC.cartesian2internal(x1) q0 = IC.cartesian2internal(x0) for r in rs: qr = q0 + r * (q1 - q0) xr = IC.internal2cartesian(qr) geometries_interp.append(XYZ.vector2atomlist(xr, atomlist0)) else: raise ValueError( "Coordinate system '%s' not understood, valid options are 'internal' and 'cartesian'" % opts.coord_system) XYZ.write_xyz(xyz_interp, geometries_interp)