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)
class BaseScheme(object): prefix = None usage = None @classmethod def new_from_args(cls, context, args): raise NotImplementedError def __init__(self, context, rgrid, extra_tag_attributes): # create angular grid object agrid = ALebedevIntGrid(context.options.lebedev, context.options.do_random) # check arguments extra_tag_attributes["rgrid"] = rgrid.get_description() extra_tag_attributes["agrid"] = agrid.get_description() context.check_tag(extra_tag_attributes) # assign attributes self.context = context self.rgrid = rgrid self.agrid = agrid self._done = set([]) # clone attributes from context self.work = context.work self.output = context.output self.wavefn = context.wavefn self.molecule = context.wavefn.molecule def _spherint(self, integrand): radfun = self.agrid.integrate(integrand) rs = self.rgrid.rs[:len(integrand)] return self.rgrid.integrate(radfun*rs*rs) @OnlyOnce("Atomic grids") 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() @OnlyOnce("Molecular density on atomic grids") def do_atgrids_moldens(self): self.do_atgrids() pb = log.pb("Computing/Loading densities", self.molecule.size) for i in xrange(self.molecule.size): pb() self.wavefn.compute_density(self.atgrids[i]) pb() @OnlyOnce("Molecular spin density on atomic grids") 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() @OnlyOnce("Estimating noble gas core radii") def do_noble_radii(self): self.noble_radii = self.work.load("noble_radii") if self.noble_radii is None: self.do_atgrids_moldens() self.noble_radii = numpy.zeros(self.molecule.size, float) for i, number_i in enumerate(self.molecule.numbers): if number_i < 3: self.noble_radii[i] = 0.2 else: densities = self.atgrids[i].moldens radfun = self.agrid.integrate(densities) rs = self.rgrid.rs[:len(radfun)] charge_int = self.rgrid.integrate_cumul(radfun*rs*rs) j = charge_int.searchsorted([core_sizes[number_i]])[0] self.noble_radii[i] = self.rgrid.rs[j] self.work.dump("noble_radii", self.noble_radii) @OnlyOnce("Computing the ESP cost function") def do_esp_costfunction(self): # TODO: the ESP cost function should be upgraded to a more reliable # implementation. We should consider the cost function as an integral # over the volume where the density is not too high and the distance # from the molecule is not too far. This can be achieved by a # combination of Becke's integration scheme # (http://dx.doi.org/10.1063/1.454033) and Hu's ESP method # (http://dx.doi.org/10.1021/ct600295n). Then there is no need to # construct a molecular grid. The atomic grids are sufficient. # TODO: output ESP charges in the same way as the stockholder charges. self.do_molgrid_moldens() self.do_molgrid_molpot() self.mol_esp_cost = ESPCostFunction( self.molecule.coordinates, self.molgrid.points, self.molgrid.weights, self.molgrid.moldens, self.molgrid.molpot, self.wavefn.charge, ) self.output.dump_esp_cost("mol_esp_cost.txt", self.mol_esp_cost) @OnlyOnce("Molecular grid") 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) @OnlyOnce("Molecular density on the molecular grid") def do_molgrid_moldens(self): self.do_molgrid() self.wavefn.compute_density(self.molgrid) @OnlyOnce("Molecular potential on the molecular grid") def do_molgrid_molpot(self): self.do_molgrid() log("This may take a minute. Hang on.") self.wavefn.compute_potential(self.molgrid) def _prepare_atweights(self): pass @OnlyOnce("Defining atomic weight functions (own atomic grid)") def do_atgrids_atweights(self): self.do_atgrids() log("Trying to load weight functions") success = self._load_atgrid_atweights() if not success: log("Could not load all weight functions from workdir. Computing them.") self._prepare_atweights() self._compute_atgrid_atweights() log("Writing results to workdir") self._dump_atgrid_atweights() def _load_atgrid_atweights(self): ws = [] for i in xrange(self.molecule.size): w = self.atgrids[i].load("%s_atweights" % self.prefix) if w is None: return False else: ws.append(w) for i in xrange(self.molecule.size): self.atgrids[i].atweights = ws[i] return True def _compute_atgrid_atweights(self): raise NotImplementedError def _dump_atgrid_atweights(self): for i in xrange(self.molecule.size): self.atgrids[i].dump("%s_atweights" % self.prefix, self.atgrids[i].atweights, ignore=True) @OnlyOnce("Atomic charges") 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") @OnlyOnce("Atomic spin charges") 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") @OnlyOnce("Atomic dipoles") 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") @OnlyOnce("Atomic multipoles (up to hexadecapols)") 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") @OnlyOnce("Testing charges and dipoles on ESP grid.") def do_esp_test(self): self.do_charges() self.do_dipoles() self.do_esp_costfunction() dipole_q = numpy.dot(self.charges, self.molecule.coordinates) dipole_p = self.dipoles.sum(axis=0) dipole_qp = dipole_q + dipole_p dipole_qm = self.wavefn.dipole self.output.dump_esp_test( "%s_esp_test.txt" % self.prefix, dipole_q, dipole_p, dipole_qp, dipole_qm, self.mol_esp_cost, self.charges, self.dipoles ) @OnlyOnce("Evaluating orbitals on atomic grids") 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() @OnlyOnce("Atomic overlap matrices (orbitals)") def do_atgrids_overlap_matrix_orb(self): # Note that the overlap matrices are computed in the basis of the # orbitals. Each kind of overlap matrix is thus computed in the basis # of its corresponding kind of orbitals. self.do_atgrids() 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) do_one_kind("alpha") do_one_kind("beta") do_one_kind("natural") @OnlyOnce("Atomic overlap matrices (contracted Gaussians)") 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) @OnlyOnce("Bond orders and valences") 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") @OnlyOnce("Atomic weights on other atoms' grids.") 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 _compute_atweights(self, grid, atom_index): raise NotImplementedError @OnlyOnce("Net and overlap populations") 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")