def do_molgrid(self): self.molgrid = Grid.from_prefix("molecule", self.work) if self.molgrid is not None: self.molgrid.weights = self.molgrid.load("weights") else: # we have to generate a new grid. The grid is constructed taking # into account the following considerations: # 1) Grid points within the cusp region are discarded # 2) The rest of the molecular and surrounding volume is sampled # with spherical grids centered on the atoms. Around each atom, # 'scale_steps' of shells are placed with lebedev grid points # (num_lebedev). The lebedev weights are used in the fit to # avoid preferential directions within one shell. # 3) The radii of the shells start from scale_min*(cusp_radius+0.2) # and go up to scale_max*(cusp_radius+0.2). # 4) Each shell will be randomly rotated around the atom to avoid # global preferential directions in the grid. # 5) The default parameters for the grid should be sufficient for # sane ESP fitting. The ESP cost function should discard points # with a density larger than a threshold, i.e. 1e-5 a.u. A # gradual transition between included and discarded points around # this threshold will improve the quality of the fit. lebedev_xyz, lebedev_weights = get_lebedev_grid(50) self.do_noble_radii() scale_min = 1.5 scale_max = 30.0 scale_steps = 30 scale_factor = (scale_max/scale_min)**(1.0/(scale_steps-1)) scales = scale_min*scale_factor**numpy.arange(scale_steps) points = [] weights = [] pb = log.pb("Constructing molecular grid", scale_steps) for scale in scales: pb() radii = scale*self.noble_radii for i in xrange(self.molecule.size): rot = Rotation.random() for j in xrange(len(lebedev_xyz)): my_point = radii[i]*numpy.dot(rot.r, lebedev_xyz[j]) + self.molecule.coordinates[i] distances = numpy.sqrt(((self.molecule.coordinates - my_point)**2).sum(axis=1)) if (distances < scales[0]*self.noble_radii).any(): continue points.append(my_point) weights.append(lebedev_weights[j]) pb() points = numpy.array(points) weights = numpy.array(weights) self.molgrid = Grid("molecule", self.work, points) self.molgrid.weights = weights self.molgrid.dump("weights", weights)
def do_atgrids_overlap_matrix(self): self.do_atgrids() self.do_atgrids_atweights() num_orbitals = self.wavefn.num_orbitals pb = log.pb("Computing matrices", self.molecule.size) for i in xrange(self.molecule.size): pb() atgrid = self.atgrids[i] suffix = "%s_overlap_matrix" % self.prefix overlap = atgrid.load(suffix) if overlap is None: rw = self.rgrid.get_weights().copy() rw *= 4*numpy.pi rw *= self.rgrid.rs rw *= self.rgrid.rs weights = numpy.outer(rw, self.agrid.lebedev_weights).ravel() weights *= atgrid.atweights overlap = self.wavefn.compute_atomic_overlap(atgrid, weights) atgrid.dump(suffix, overlap) else: overlap = overlap.reshape((num_orbitals, num_orbitals)) atgrid.overlap_matrix = overlap pb() filename = "%s_overlap_matrices.txt" % self.prefix overlap_matrices = [atgrid.overlap_matrix for atgrid in self.atgrids] self.output.dump_overlap_matrices(filename, overlap_matrices)
def do_charges(self): charges_name = "%s_charges" % self.prefix populations_name = "%s_populations" % self.prefix self.charges = self.work.load(charges_name) self.populations = self.work.load(populations_name) if self.charges is None or self.populations is None: self.do_atgrids() self.do_atgrids_moldens() self.do_atgrids_atweights() pb = log.pb("Computing charges", self.molecule.size) self.populations = numpy.zeros(self.molecule.size, float) self.charges = numpy.zeros(self.molecule.size, float) for i in xrange(self.molecule.size): pb() w = self.atgrids[i].atweights d = self.atgrids[i].moldens center = self.molecule.coordinates[i] self.populations[i] = self._spherint(d*w) self.charges[i] = self.wavefn.nuclear_charges[i] - self.populations[i] pb() if self.context.options.fix_total_charge: self.charges -= (self.charges.sum() - self.wavefn.charge)/self.molecule.size self.work.dump(charges_name, self.charges) self.work.dump(populations_name, self.populations) self.output.dump_atom_scalars("%s_charges.txt" % self.prefix, self.charges, "Charge")
def do_atgrids_molspindens(self): self.do_atgrids() pb = log.pb("Computing/Loading spin densities", self.molecule.size) for i in xrange(self.molecule.size): pb() self.wavefn.compute_spin_density(self.atgrids[i]) pb()
def do_dipoles(self): dipoles_name = "%s_dipoles" % self.prefix self.dipoles = self.work.load(dipoles_name, (-1,3)) if self.dipoles is None: self.do_atgrids() self.do_atgrids_moldens() self.do_atgrids_atweights() pb = log.pb("Computing dipoles", self.molecule.size) self.dipoles = numpy.zeros((self.molecule.size,3), float) for i in xrange(self.molecule.size): pb() atgrid = self.atgrids[i] w = atgrid.atweights d = atgrid.moldens center = self.molecule.coordinates[i] for j in 0,1,2: integrand = -(atgrid.points[:,j] - center[j])*d*w self.dipoles[i,j] = self._spherint(integrand) pb() self.work.dump(dipoles_name, self.dipoles) self.output.dump_atom_vectors("%s_dipoles.txt" % self.prefix, self.dipoles, "Dipoles")
def do_atgrids_orbitals(self): self.do_atgrids() self.wavefn.init_naturals(self.work) pb = log.pb("Computing/Loading orbitals", self.molecule.size) for i in xrange(self.molecule.size): pb() self.wavefn.compute_orbitals(self.atgrids[i]) pb()
def make_density_profiles(program, num_lebedev, r_low, r_high, steps, atom_numbers, max_ion, do_work, do_random): # generate lebedev grid lebedev_xyz, lebedev_weights = get_grid(num_lebedev) # define radii rgrid = RLogIntGrid(r_low, r_high, steps) agrid = ALebedevIntGrid(num_lebedev, do_random) f_pro = file("densities.txt", "w") print >> f_pro, rgrid.get_description() charges = [] # run over all directories, run cubegen, load cube data pb = log.pb("Density profiles", len(atom_numbers) * (2 * max_ion + 1)) for number in atom_numbers: symbol = periodic[number].symbol for charge in xrange(-max_ion, max_ion + 1): charge_label = charge_to_label(charge) pb() dirname = os.path.join("%03i%s" % (number, symbol), charge_label, "gs") # get the grid if not os.path.isdir(dirname): continue if do_work: work = Work(dirname) else: work = Work() grid = AtomicGrid.from_prefix("grid", work) if grid is None: center = numpy.zeros(3, float) grid = AtomicGrid.from_parameters("grid", work, center, rgrid, agrid) # compute densities program.compute_density(grid, dirname) # this is spherical averaging, i.e. integral/(4*pi) radrhos = agrid.integrate(grid.moldens) / (4 * numpy.pi) print >> f_pro, "%3i %+2i" % (number, charge), # leave out near zeros to save space and time print >> f_pro, " ".join("%22.16e" % rho for rho in radrhos if rho > 1e-100) check = rgrid.integrate(4 * numpy.pi * rgrid.rs * rgrid.rs * radrhos) charges.append((number, symbol, charge, check)) pb() f_pro.close() counter = 0 for number, symbol, charge, real_charge in charges: log("Total charge error: %3i %2s %+2i % 10.5e" % (number, symbol, charge, -real_charge + number - charge)) counter += 1
def make_inputs(program, lot, atom_numbers, max_ion): states = list(iter_states(atom_numbers, max_ion)) pb = log.pb("Creating input files", len(states)) dirnames = [] for atom, charge, mult in states: pb() charge_label = charge_to_label(charge) dirname = os.path.join("%03i%s" % (atom.number, atom.symbol), charge_label, "mult%i" % mult) program.make_atom_input(dirname, atom.number, charge, mult, lot) dirnames.append(dirname) pb() return dirnames
def do_atgrids_od_atweights(self): # od stands for off-diagonal self.do_atgrids_atweights() self._prepare_atweights() pb = log.pb("Computing off-diagonal atom weights", self.molecule.size**2) for i in xrange(self.molecule.size): atgrid = self.atgrids[i] atgrid.od_atweights = [] for j in xrange(self.molecule.size): pb() w = self._compute_atweights(atgrid, j) atgrid.od_atweights.append(w) pb()
def make_density_profiles(program, num_lebedev, r_low, r_high, steps, atom_numbers, max_ion, do_work, do_random): # generate lebedev grid lebedev_xyz, lebedev_weights = get_grid(num_lebedev) # define radii rgrid = RLogIntGrid(r_low, r_high, steps) agrid = ALebedevIntGrid(num_lebedev, do_random) f_pro = file("densities.txt", "w") print >> f_pro, rgrid.get_description() charges = [] # run over all directories, run cubegen, load cube data pb = log.pb("Density profiles", len(atom_numbers)*(2*max_ion+1)) for number in atom_numbers: symbol = periodic[number].symbol for charge in xrange(-max_ion, max_ion+1): charge_label = charge_to_label(charge) pb() dirname = os.path.join("%03i%s" % (number, symbol), charge_label, "gs") # get the grid if not os.path.isdir(dirname): continue if do_work: work = Work(dirname) else: work = Work() grid = AtomicGrid.from_prefix("grid", work) if grid is None: center = numpy.zeros(3,float) grid = AtomicGrid.from_parameters("grid", work, center, rgrid, agrid) # compute densities program.compute_density(grid, dirname) # this is spherical averaging, i.e. integral/(4*pi) radrhos = agrid.integrate(grid.moldens)/(4*numpy.pi) print >> f_pro, "%3i %+2i" % (number, charge), # leave out near zeros to save space and time print >> f_pro, " ".join("%22.16e" % rho for rho in radrhos if rho > 1e-100) check = rgrid.integrate(4*numpy.pi*rgrid.rs*rgrid.rs*radrhos) charges.append((number, symbol, charge, check)) pb() f_pro.close() counter = 0 for number, symbol, charge, real_charge in charges: log("Total charge error: %3i %2s %+2i % 10.5e" % (number, symbol, charge, -real_charge+number-charge)) counter += 1
def do_one_kind(kind): # first check for restricted orbitals = getattr(self.wavefn, "%s_orbitals" % kind) if kind!="alpha" and self.wavefn.alpha_orbitals is orbitals: # simply make references to alpha data and return log("Cloning alpha results (%s)" % kind) for i in xrange(self.molecule.size): setattr(self.atgrids[i], "%s_overlap_matrix_orb" % kind, self.atgrids[i].alpha_overlap_matrix_orb) return # then try to load the matrices some_failed = False num_orbitals = self.wavefn.num_orbitals for i in xrange(self.molecule.size): matrix = self.atgrids[i].load("%s_%s_overlap_matrix_orb" % (self.prefix, kind)) if matrix is None: some_failed = True else: matrix = matrix.reshape((num_orbitals, num_orbitals)) setattr(self.atgrids[i], "%s_overlap_matrix_orb" % kind, matrix) if some_failed: self.do_atgrids_orbitals() self.do_atgrids_atweights() pb = log.pb("Computing atomic overlap matrices (%s)" % kind, self.molecule.size) for i in xrange(self.molecule.size): pb() if getattr(self.atgrids[i], "%s_overlap_matrix_orb" % kind) is None: orbitals = getattr(self.atgrids[i], "%s_orbitals" % kind) w = self.atgrids[i].atweights matrix = numpy.zeros((num_orbitals,num_orbitals), float) for j1 in xrange(num_orbitals): for j2 in xrange(j1+1): integrand = orbitals[j1]*orbitals[j2]*w value = self._spherint(integrand) matrix[j1,j2] = value matrix[j2,j1] = value setattr(self.atgrids[i], "%s_overlap_matrix_orb" % kind, matrix) self.atgrids[i].dump("%s_%s_overlap_matrix_orb" % (self.prefix, kind), matrix) pb() filename = "%s_%s_overlap_matrices_orb.txt" % (self.prefix, kind) overlap_matrices = [ getattr(grid, "%s_overlap_matrix_orb" % kind) for grid in self.atgrids ] self.output.dump_overlap_matrices(filename, overlap_matrices)
def run_jobs(program, dirnames): pb = log.pb("Atomic computations", len(dirnames)) failed = [] for dirname in dirnames: pb() succes = program.run(dirname) if not succes: failed.append(dirname) pb() if len(failed) == len(dirnames): log("Could not execute any job. Is %s in the PATH?" % program.executable) sys.exit(-1) if len(failed) > 0: log("Some jobs failed:") for dirname in failed: log(" %s" % dirname)
def do_atgrids(self): self.atgrids = [] pb = log.pb("Computing/Loading atomic grids and distances", self.molecule.size) for i in xrange(self.molecule.size): pb() name = "atom%05i" % i atgrid = AtomicGrid.from_prefix(name, self.work) if atgrid is None: center = self.molecule.coordinates[i] atgrid = AtomicGrid.from_parameters(name, self.work, center, self.rgrid, self.agrid) self.atgrids.append(atgrid) # Compute and store all the distances from these grid points to the # nuclei. atgrid.distances = LazyDistances(self.molecule.coordinates, atgrid, self.context.options.save_mem) pb()
def do_net_overlap(self): net_overlap_name = "%s_net_overlap.bin" % self.prefix self.net_overlap = self.work.load(net_overlap_name, (self.molecule.size,self.molecule.size)) if self.net_overlap is None: self.do_atgrids() self.do_atgrids_moldens() self.do_charges() self.do_atgrids_od_atweights() self.net_overlap = numpy.zeros((self.molecule.size, self.molecule.size)) pb = log.pb("Integrating over products of stockholder weights", (self.molecule.size*(self.molecule.size+1))/2) for i in xrange(self.molecule.size): for j in xrange(i+1): pb() if i != j: # Use Becke's integration scheme to split the integral # over two atomic grids. # 1) first part of the integral, using the grid on atom i delta = (self.atgrids[i].distances[j].reshape((len(self.rgrid.rs),-1)) - self.rgrid.rs.reshape((-1,1))).ravel() switch = delta/self.molecule.distance_matrix[i,j] for k in xrange(3): switch = (3 - switch**2)*switch/2 switch += 1 switch /= 2 integrand = switch*self.atgrids[i].od_atweights[j]*self.atgrids[i].atweights*self.atgrids[i].moldens part1 = self._spherint(integrand) # 2) second part of the integral delta = (self.atgrids[j].distances[i].reshape((len(self.rgrid.rs),-1)) - self.rgrid.rs.reshape((-1,1))).ravel() switch = delta/self.molecule.distance_matrix[i,j] for k in xrange(3): switch = (3 - switch**2)*switch/2 switch += 1 switch /= 2 integrand = switch*self.atgrids[j].od_atweights[i]*self.atgrids[j].atweights*self.atgrids[j].moldens part2 = self._spherint(integrand) # Add up and store self.net_overlap[i,j] = part1 + part2 self.net_overlap[j,i] = part1 + part2 else: integrand = self.atgrids[i].atweights**2*self.atgrids[i].moldens self.net_overlap[i,i] = self._spherint(integrand) pb() self.work.dump(net_overlap_name, self.net_overlap) self.output.dump_atom_matrix("%s_net_overlap.txt" % self.prefix, self.net_overlap, "Net/Overlap")
def _prepare_atweights(self): # Compute the cell functions on all grids self.do_atgrids() radii = numpy.array([periodic[n].covalent_radius for n in self.molecule.numbers]) N = self.molecule.size pb = log.pb("Computing/Loading cell functions", N) for i in xrange(N): pb() # working in the grid of atom i grid = self.atgrids[i] # first try to load. if it fails then compute. grid.cell_functions = grid.load("cell_functions") if grid.cell_functions is None: # load failed, so compute grid.cell_functions = numpy.ones((N, grid.size), float) for j0 in xrange(N): for j1 in xrange(j0): # working on the contribution from atom pair j0,j1 # determine the displacement of the cell boundary with # respect to the center based on covalent radii d = self.molecule.distance_matrix[j0,j1] u = (radii[j0]-radii[j1])/(radii[j1]+radii[j0]) a = u/(u**2-1) if a < -0.45: a = -0.45 elif a > 0.45: a = 0.45 # construct the switching function switch = grid.distances[j0].copy() switch -= grid.distances[j1] switch /= d switch = switch + a*(1-switch**2) # hetero for k in xrange(self.k): switch = 0.5*(3.0 - switch**2)*switch switch += 1.0 switch /= 2.0 grid.cell_functions[j0] *= 1-switch grid.cell_functions[j1] *= switch # dump cell functions grid.dump("cell_functions", grid.cell_functions) else: grid.cell_functions = grid.cell_functions.reshape((N, -1)) grid.cell_sum = sum(grid.cell_functions) pb()
def do_spin_charges(self): spin_charges_name = "%s_spin_charges" % self.prefix self.spin_charges = self.work.load(spin_charges_name) if self.spin_charges is None: self.do_atgrids() self.do_atgrids_molspindens() self.do_atgrids_atweights() pb = log.pb("Computing spin charges", self.molecule.size) self.spin_charges = numpy.zeros(self.molecule.size, float) for i in xrange(self.molecule.size): pb() w = self.atgrids[i].atweights d = self.atgrids[i].molspindens center = self.molecule.coordinates[i] self.spin_charges[i] = self._spherint(d*w) pb() self.work.dump(spin_charges_name, self.spin_charges) self.output.dump_atom_scalars("%s_spin_charges.txt" % self.prefix, self.spin_charges, "Spin charge")
def do_bond_orders(self): # first try to load the results from the work dir bond_orders_name = "%s_bond_orders" % self.prefix valences_name = "%s_valences" % self.prefix self.bond_orders = self.work.load(bond_orders_name, (self.molecule.size, self.molecule.size)) self.valences = self.work.load(valences_name) if self.bond_orders is None or self.valences is None: self.do_charges() self.do_atgrids_overlap_matrix() self.bond_orders = numpy.zeros((self.molecule.size, self.molecule.size)) self.valences = numpy.zeros(self.molecule.size) num_dof = self.wavefn.num_orbitals full = numpy.zeros((num_dof, num_dof), float) dmat_to_full(self.wavefn.density_matrix, full) if self.wavefn.spin_density_matrix is None: full_alpha = 0.5*full full_beta = full_alpha else: full_alpha = numpy.zeros((num_dof, num_dof), float) full_beta = numpy.zeros((num_dof, num_dof), float) dmat_to_full( 0.5*(self.wavefn.density_matrix + self.wavefn.spin_density_matrix), full_alpha ) dmat_to_full( 0.5*(self.wavefn.density_matrix - self.wavefn.spin_density_matrix), full_beta ) pb = log.pb("Computing bond orders", (self.molecule.size*(self.molecule.size+1))/2) for i in xrange(self.molecule.size): for j in xrange(i+1): pb() if i==j: # compute valence tmp = numpy.dot(full, self.atgrids[i].overlap_matrix) self.valences[i] = 2*self.populations[i] - (tmp*tmp.transpose()).sum() else: # compute bond order bo = ( numpy.dot(full_alpha, self.atgrids[i].overlap_matrix)* numpy.dot(full_alpha, self.atgrids[j].overlap_matrix).transpose() ).sum() if full_alpha is full_beta: bo *= 2 else: bo += ( numpy.dot(full_beta, self.atgrids[i].overlap_matrix)* numpy.dot(full_beta, self.atgrids[j].overlap_matrix).transpose() ).sum() bo *= 2 self.bond_orders[i,j] = bo self.bond_orders[j,i] = bo pb() self.work.dump(bond_orders_name, self.bond_orders) self.work.dump(valences_name, self.valences) self.free_valences = self.valences - self.bond_orders.sum(axis=1) self.output.dump_atom_matrix("%s_bond_orders.txt" % self.prefix, self.bond_orders, "Bond order") self.output.dump_atom_scalars("%s_valences.txt" % self.prefix, self.valences, "Valences") self.output.dump_atom_scalars("%s_free_valences.txt" % self.prefix, self.free_valences, "Free valences")
def do_multipoles(self): regular_solid_harmonics = [ lambda x,y,z: 1.0, # (0,0) lambda x,y,z: z, # (1,0) lambda x,y,z: x, # (1,1+) lambda x,y,z: y, # (1,1-) lambda x,y,z: 1.0*z**2 - 0.5*x**2 - 0.5*y**2, # (2,0) lambda x,y,z: 1.7320508075688772935*x*z, # (2,1+) lambda x,y,z: 1.7320508075688772935*y*z, # (2,1-) lambda x,y,z: 0.86602540378443864676*x**2 - 0.86602540378443864676*y**2, # (2,2+) lambda x,y,z: 1.7320508075688772935*x*y, # (2,2-) lambda x,y,z: -1.5*z*x**2 - 1.5*z*y**2 + z**3, # (3,0) lambda x,y,z: 2.4494897427831780982*x*z**2 - 0.61237243569579452455*x*y**2 - 0.61237243569579452455*x**3, # (3,1+) lambda x,y,z: 2.4494897427831780982*y*z**2 - 0.61237243569579452455*y*x**2 - 0.61237243569579452455*y**3, # (3,1-) lambda x,y,z: 1.9364916731037084426*z*x**2 - 1.9364916731037084426*z*y**2, # (3,2+) lambda x,y,z: 3.8729833462074168852*x*y*z, # (3,2-) lambda x,y,z: -2.371708245126284499*x*y**2 + 0.790569415042094833*x**3, # (3,3+) lambda x,y,z: 2.371708245126284499*y*x**2 - 0.790569415042094833*y**3, # (3,3-) lambda x,y,z: 0.75*x**2*y**2 - 3.0*x**2*z**2 - 3.0*y**2*z**2 + z**4 + 0.375*x**4 + 0.375*y**4, # (4,0) lambda x,y,z: -2.371708245126284499*x*z*y**2 + 3.162277660168379332*x*z**3 - 2.371708245126284499*z*x**3, # (4,1+) lambda x,y,z: -2.371708245126284499*y*z*x**2 + 3.162277660168379332*y*z**3 - 2.371708245126284499*z*y**3, # (4,1-) lambda x,y,z: 3.3541019662496845446*x**2*z**2 - 3.3541019662496845446*y**2*z**2 + 0.5590169943749474241*y**4 - 0.5590169943749474241*x**4, # (4,2+) lambda x,y,z: 6.7082039324993690892*x*y*z**2 - 1.1180339887498948482*x*y**3 - 1.1180339887498948482*y*x**3, # (4,2-) lambda x,y,z: -6.2749501990055666098*x*z*y**2 + 2.0916500663351888699*z*x**3, # (4,3+) lambda x,y,z: 6.2749501990055666098*y*z*x**2 - 2.0916500663351888699*z*y**3, # (4,3-) lambda x,y,z: -4.4370598373247120319*x**2*y**2 + 0.73950997288745200532*x**4 + 0.73950997288745200532*y**4, # (4,4+) lambda x,y,z: 2.9580398915498080213*y*x**3 - 2.9580398915498080213*x*y**3, # (4,4-) ] labels = [ '(0,0)', '(1,0)', '(1,1+)', '(1,1-)', '(2,0)', '(2,1+)', '(2,1-)', '(2,2+)', '(2,2-)', '(3,0)', '(3,1+)', '(3,1-)', '(3,2+)', '(3,2-)', '(3,3+)', '(3,3-)', '(4,0)', '(4,1+)', '(4,1-)', '(4,2+)', '(4,2-)', '(4,3+)', '(4,3-)', '(4,4+)', '(4,4-)' ] multipoles_name = "%s_multipoles.bin" % self.prefix num_polys = len(regular_solid_harmonics) shape = (self.molecule.size,num_polys) self.multipoles = self.work.load(multipoles_name, shape) if self.multipoles is None: self.do_atgrids() self.do_atgrids_moldens() self.do_atgrids_atweights() pb = log.pb("Computing multipoles", self.molecule.size) num_polys = len(regular_solid_harmonics) self.multipoles = numpy.zeros(shape, float) for i in xrange(self.molecule.size): pb() atgrid = self.atgrids[i] w = atgrid.atweights d = atgrid.moldens center = self.molecule.coordinates[i] cx = atgrid.points[:,0] - center[0] cy = atgrid.points[:,1] - center[1] cz = atgrid.points[:,2] - center[2] for j in xrange(num_polys): poly = regular_solid_harmonics[j] self.multipoles[i,j] = self._spherint(-poly(cx,cy,cz)*d*w) self.multipoles[i,0] += self.wavefn.nuclear_charges[i] pb() self.work.dump(multipoles_name, self.multipoles) self.output.dump_atom_fields("%s_multipoles.txt" % self.prefix, self.multipoles, labels, "Multipoles")