def do_proatomfns(self): self.do_atgrids_moldens() counter = 0 old_populations = self.wavefn.nuclear_charges.astype(float) log("Iteration Max change Total charge") while True: # construct the pro-atom density functions, using the densities # from the previous iteration. self.proatomfns = [] for i, number_i in enumerate(self.molecule.numbers): self.proatomfns.append(self.atom_table.records[number_i].get_atom_fn(old_populations[i])) populations = numpy.zeros(self.molecule.size, float) for i in xrange(self.molecule.size): integrand = self.atgrids[i].moldens*self._compute_atweights(self.atgrids[i], i) population = self._spherint(integrand) populations[i] = population # ordinary blablabla ... max_change = abs(populations-old_populations).max() log("%5i % 10.5e % 10.5e" % ( counter, max_change, self.wavefn.nuclear_charges.sum() - populations.sum() )) if max_change < self.context.options.threshold: break counter += 1 if counter > self.context.options.max_iter: raise RuntimeError("Iterative Hirshfeld failed to converge.") old_populations = populations
def dump_esp_test(self, filename, dipole_q, dipole_p, dipole_qp, dipole_qm, mol_esp_cost, charges, dipoles): if self.active: filename = os.path.join(self.directory, filename) dump_esp_test(filename, dipole_q, dipole_p, dipole_qp, dipole_qm, mol_esp_cost, charges, dipoles) log("Written %s" % filename)
def get_atom_fn(self, population=None): if population is None: population = float(self.number) else: population = float(population) if population < 0: raise ValueError("A negative number of electrons is not physical") if population < self.min_population: if self.number > 1 and population < self.min_population: log("Warning: unsafe extrapolation (below), number=%i, population=%f" % (self.number, population)) ratio = population/self.min_population rhos = self.records[self.min_population]*ratio elif population > self.max_population: log("Warning: unsafe extrapolation (above), number=%i, population=%f" % (self.number, population)) ratio = population/self.max_population rhos = self.records[self.max_population]*ratio else: high_population = int(numpy.ceil(population)) low_population = int(numpy.floor(population)) if low_population == high_population: rhos = self.records[low_population] else: low_ref = self.records[low_population] high_ref = self.records[high_population] if len(high_ref) > len(low_ref): rhos = high_ref*(population - low_population) rhos[:len(low_ref)] += low_ref*(high_population - population) else: rhos = low_ref*(high_population - population) rhos[:len(high_ref)] += high_ref*(population - low_population) result = CubicSpline(self.rgrid.rs[:len(rhos)], rhos) return result
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_natural(dmat, label): orbitals_name = "%s_orbitals" % label occupations_name = "%s_occupations" % label orbitals = work.load(orbitals_name, (self.num_orbitals, self.num_orbitals)) occupations = work.load(occupations_name) if orbitals is None or occupations is None: log("Computing the %s orbitals and occupation numbers ..." % label) occupations, orbitals = compute_naturals(dmat, self.num_orbitals) work.dump(orbitals_name, orbitals) work.dump(occupations_name, occupations) num = get_num_filled(occupations) return num, occupations, orbitals
def select_ground_states(program, max_ion): log("Selecting ground states.") all_energies = program.get_energies() if 1 in all_energies: # Computation on a proton without electrons: solve manually. # crunch crunch ... all_energies[1][1] = {1: 0.0} f_au = file("chieta_au.txt", "w") f_ev = file("chieta_ev.txt", "w") print >> f_au, "All values below are in atomic units." print >> f_au, " A I chi eta mult(neg,neut,pos)" print >> f_ev, "All values below are in electron volts." print >> f_ev, " A I chi eta mult(neg,neut,pos)" for number, atom_energies in sorted(all_energies.iteritems()): energies = {} mult = {} symbol = periodic[number].symbol for charge in xrange(-max_ion, max_ion + 1): charge_label = charge_to_label(charge) if charge not in atom_energies: continue energies[charge] = min(atom_energies[charge].itervalues()) mult[charge] = min( (energy, mult) for mult, energy in atom_energies[charge].iteritems())[1] newlink = os.path.join("%03i%s" % (number, symbol), charge_label, "gs") if os.path.isdir(os.path.dirname(newlink)): if os.path.exists(newlink): os.remove(newlink) os.symlink("mult%i" % mult[charge], newlink) if -1 in energies and 0 in energies and 1 in energies: chi = (energies[+1] - energies[-1]) / 2 eta = (energies[+1] + energies[-1] - 2 * energies[0]) values = [ energies[0] - energies[-1], energies[+1] - energies[0], chi, eta ] print >> f_au, "% 3s" % symbol, "%2i" % number, " ".join( "% 10.5f" % v for v in values), " ", mult[-1], mult[0], mult[1] print >> f_ev, "% 3s" % symbol, "%2i" % number, " ".join( "% 10.5f" % (v / electronvolt) for v in values), " ", mult[-1], mult[0], mult[1] f_au.close() f_ev.close()
def do_natural(dmat, label): orbitals_name = "%s_orbitals" % label occupations_name = "%s_occupations" % label orbitals = work.load(orbitals_name, (self.num_orbitals, self.num_orbitals)) occupations = work.load(occupations_name) if orbitals is None or occupations is None: log("Computing the %s orbitals and occupation numbers ..." % label) occupations, orbitals = compute_naturals( dmat, self.num_orbitals) work.dump(orbitals_name, orbitals) work.dump(occupations_name, occupations) num = get_num_filled(occupations) return num, occupations, orbitals
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 estimate_errors(atom_tables, fns_fchk): from hipart.context import Context, Options from hipart.schemes import HirshfeldScheme from hipart.log import log import numpy, time configs = [] ref_config = None # The loops below one jobs for each grid configuration, i.e. combination # of radial and angular grid. for size, atom_table in sorted(atom_tables.iteritems()): for lebedev in 26, 50, 110, 230, 434, 770, 1454, 2702: all_charges = [] start = time.clock() for fn_fchk in fns_fchk: log.begin("Config size=%i lebedev=%i fchk=%s" % (size, lebedev, fn_fchk)) # Make a new working environment for every sample options = Options(lebedev=lebedev, do_work=False, do_output=False, do_random=True) context = Context(fn_fchk, options) scheme = HirshfeldScheme(context, atom_table) scheme.do_charges() all_charges.append(scheme.charges) del scheme log.end() cost = time.clock() - start log("cost=%.2e" % cost) config = Config(size, lebedev, numpy.concatenate(all_charges), cost) configs.append(config) if ref_config is None or (ref_config.size <= config.size and ref_config.lebedev <= config.lebedev): ref_config = config # Compute the errors ref_charges = configs[-1].charges for config in configs: config.error = (ref_charges - config.charges).std() # Log all log.begin("All configs") for config in configs: log(str(config)) log.end() return configs
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 estimate_errors(atom_tables, fn_fchk): from hipart.context import Context, Options from hipart.schemes import HirshfeldScheme from hipart.log import log import numpy, time configs = [] # The loops below run 40 jobs for each grid configuration, i.e. combination # of radial and angular grid. Due to the random rotations of the angular # integration grids, the resulting charges will differ in each run. The # standard devation on each charge over the 40 runs is computed, and then # the error over all atoms is averaged. This error is used as 'the' error on # the charges. The cost is the cpu time consumed for computing this error. for size, atom_table in sorted(atom_tables.iteritems()): for lebedev in 26, 50, 110, 170, 266, 434: log.begin("Config size=%i lebedev=%i" % (size, lebedev)) start = time.clock() all_charges = [] for counter in xrange(40): log.begin("Sample counter=%i" % counter) # Make a new working environment for every sample options = Options(lebedev=lebedev, do_work=False, do_output=False) context = Context(fn_fchk, options) scheme = HirshfeldScheme(context, atom_table) scheme.do_charges() all_charges.append(scheme.charges) del scheme log.end() all_charges = numpy.array(all_charges) error = all_charges.std(axis=0).mean() cost = time.clock() - start log("error=%.2e cost=%.2e" % (error, cost)) configs.append(Config(size, lebedev, error, cost)) log.end() log.begin("All configs") for config in configs: log(str(config)) log.end() return configs
def select_ground_states(program, max_ion): log("Selecting ground states.") all_energies = program.get_energies() if 1 in all_energies: # Computation on a proton without electrons: solve manually. # crunch crunch ... all_energies[1][1] = {1: 0.0} f_au = file("chieta_au.txt", "w") f_ev = file("chieta_ev.txt", "w") print >> f_au, "All values below are in atomic units." print >> f_au, " A I chi eta mult(neg,neut,pos)" print >> f_ev, "All values below are in electron volts." print >> f_ev, " A I chi eta mult(neg,neut,pos)" for number, atom_energies in sorted(all_energies.iteritems()): energies = {} mult = {} symbol = periodic[number].symbol for charge in xrange(-max_ion, max_ion+1): charge_label = charge_to_label(charge) if charge not in atom_energies: continue energies[charge] = min(atom_energies[charge].itervalues()) mult[charge] = min((energy, mult) for mult, energy in atom_energies[charge].iteritems())[1] newlink = os.path.join("%03i%s" % (number, symbol), charge_label, "gs") if os.path.isdir(os.path.dirname(newlink)): if os.path.exists(newlink): os.remove(newlink) os.symlink("mult%i" % mult[charge], newlink) if -1 in energies and 0 in energies and 1 in energies: chi = (energies[+1] - energies[-1])/2 eta = (energies[+1] + energies[-1] - 2*energies[0]) values = [energies[0] - energies[-1], energies[+1] - energies[0], chi, eta] print >> f_au, "% 3s" % symbol, "%2i" % number, " ".join("% 10.5f" % v for v in values), " ", mult[-1],mult[0],mult[1] print >> f_ev, "% 3s" % symbol, "%2i" % number, " ".join("% 10.5f" % (v/electronvolt) for v in values), " ", mult[-1],mult[0],mult[1] f_au.close() f_ev.close()
def pareto_front(configs): from hipart.log import log # take a copy of the list, so that we can modify locally. configs = list(configs) # eliminate all configs that are not pareto-optimal configs.sort(key=(lambda c: c.error)) i = 0 while i < len(configs): ref_cost = configs[i].cost j = len(configs) -1 while j > i: if configs[j].cost >= configs[i].cost: del configs[j] j -= 1 i += 1 # print the pareto front. log.begin("Pareto front") for config in configs: log(str(config)) config.optimal = True log.end()
def pareto_front(configs): from hipart.log import log # take a copy of the list, so that we can modify locally. configs = list(configs) # eliminate all configs that are not pareto-optimal configs.sort(key=(lambda c: c.error)) i = 0 while i < len(configs): ref_cost = configs[i].cost j = len(configs) - 1 while j > i: if configs[j].cost >= configs[i].cost: del configs[j] j -= 1 i += 1 # print the pareto front. log.begin("Pareto front") for config in configs: log(str(config)) config.optimal = True log.end()
def get_atom_fn(self, population=None): if population is None: population = float(self.number) else: population = float(population) if population < 0: raise ValueError("A negative number of electrons is not physical") if population < self.min_population: if self.number > 1 and population < self.min_population: log("Warning: unsafe extrapolation (below), number=%i, population=%f" % (self.number, population)) ratio = population / self.min_population rhos = self.records[self.min_population] * ratio elif population > self.max_population: log("Warning: unsafe extrapolation (above), number=%i, population=%f" % (self.number, population)) ratio = population / self.max_population rhos = self.records[self.max_population] * ratio else: high_population = int(numpy.ceil(population)) low_population = int(numpy.floor(population)) if low_population == high_population: rhos = self.records[low_population] else: low_ref = self.records[low_population] high_ref = self.records[high_population] if len(high_ref) > len(low_ref): rhos = high_ref * (population - low_population) rhos[:len(low_ref)] += low_ref * (high_population - population) else: rhos = low_ref * (high_population - population) rhos[:len(high_ref)] += high_ref * (population - low_population) result = CubicSpline(self.rgrid.rs[:len(rhos)], rhos) return result
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 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_proatomfns(self): self.do_atgrids_moldens() log("Generating initial guess for the pro-atoms") self.proatomfns = [] for i in xrange(self.molecule.size): densities = self.atgrids[i].moldens profile = self.agrid.minimum(densities) profile[profile < 1e-6] = 1e-6 self.proatomfns.append(CubicSpline(self.rgrid.rs, profile)) counter = 0 old_populations = self.wavefn.nuclear_charges.copy() log("Iteration Max change Total charge") while True: new_proatomfns = [] populations = numpy.zeros(self.molecule.size, float) for i in xrange(self.molecule.size): integrand = self.atgrids[i].moldens*self._compute_atweights(self.atgrids[i], i) radfun = self.agrid.integrate(integrand) rs = self.rgrid.rs[:len(radfun)] populations[i] = self.rgrid.integrate(radfun*rs*rs) # add negligible tails to maintain a complete partitioning radfun[radfun < 1e-40] = 1e-40 new_proatomfn = CubicSpline(self.rgrid.rs, radfun/4*numpy.pi) new_proatomfns.append(new_proatomfn) # ordinary blablabla ... max_change = abs(populations-old_populations).max() log("%5i % 10.5e % 10.5e" % ( counter, max_change, self.wavefn.nuclear_charges.sum() - populations.sum() )) if max_change < self.context.options.threshold: break counter += 1 if counter > self.context.options.max_iter: raise RuntimeError("Iterative Stockholder Analysis failed to converge.") old_populations = populations self.proatomfns = new_proatomfns
def dump_atom_matrix(self, filename, matrix, name): if self.active: filename = os.path.join(self.directory, filename) dump_atom_matrix(filename, matrix, name, self.numbers) log("Written %s" % filename)
def dump_atom_fields(self, filename, table, labels, name): if self.active: filename = os.path.join(self.directory, filename) dump_atom_fields(filename, table, labels, name, self.numbers) log("Written %s" % filename)
def dump_overlap_matrices(self, filename, overlap_matrices): if self.active: filename = os.path.join(self.directory, filename) dump_overlap_matrices(filename, overlap_matrices, self.numbers) log("Written %s" % filename)
def __init__(self, executable, options): self.executable = executable self.qc = options.qc log("Computing atomic database with program Gaussian (%s,qc=%s)" % (self.executable, self.qc))
def log(self): log("Data read from: %s (%s)" % (self.filename, ",".join(self.options))) log("Restricted: %s" % self.restricted) log("Orbitals present: %s" % (self.alpha_orbital_energies is not None)) log("Spin density present: %s" % (self.spin_density_matrix is not None)) log("Number of alpha electrons: %i" % self.num_alpha) log("Number of beta electrons: %i" % self.num_beta) log("Number of electrons: %i" % self.num_electrons) log("Total charge: %i" % self.charge) log("Number of atoms: %i" % self.molecule.size) log("Chemical formula: %s" % self.molecule.chemical_formula)
def dump_atom_scalars(self, filename, scalars, name): if self.active: filename = os.path.join(self.directory, filename) dump_atom_scalars(filename, scalars, name, self.numbers) log("Written %s" % filename)
def __init__(self, executable, options): self.executable = executable self.qc = options.qc log("Computing atomic database with program Gaussian (%s,qc=%s)" % (self.executable,self.qc))
def dump_esp_cost(self, filename, esp_cost): if self.active: filename = os.path.join(self.directory, filename) esp_cost.write_to_file(filename) log("Written %s" % filename)
def do_molgrid_molpot(self): self.do_molgrid() log("This may take a minute. Hang on.") self.wavefn.compute_potential(self.molgrid)