Пример #1
1
    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)
Пример #2
0
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")