def write_config(self, params): cell_params = params[:6] # Make a list of 3 item lists atom_params = [self.atoms[0].position] + zip(*[iter(params[6:])] * 3) new_atoms = self.atoms.copy() new_atoms.set_cell([[cell_params[0], 0.0, 0.0], [cell_params[1], cell_params[2], 0.0], [cell_params[3], cell_params[4], cell_params[5]]]) new_atoms.set_positions(atom_params) info("Cell volume: {0}".format(new_atoms.get_volume())) if self.calc_energy: self.potential.calc(new_atoms, energy=True, forces=True, virial=True) # Add to the xyz self.out.write(new_atoms) sp_path = '{0:03d}'.format(self.counter) write_filename = '{0}.{1:03d}'.format(self.atoms.info['name'], self.counter) os.mkdir(sp_path) os.chdir(sp_path) castep_write(new_atoms, filename=write_filename, kpoint_spacing=0.015) info("Wrote a configuration {0}.".format(write_filename)) os.chdir('..') self.counter += 1
def create_density_function(atoms, potential, pressure, temperature, e0=None): """ Create a function that calculates the density under the given conditions that requires only a set of parameters. Everything else is contained within the closure. Parameters ---------- atoms : ase.Atoms Template structure used as the basis of the density function calculation. potential : Potential or str A quippy potential or a potential_str to initialise a potential used for calculating the density function. pressure : float Pressure in eV/A^3 of the state point to calcualte the density function. temperature : float Temperature in Kelvin of the state point. e0 : float Energy of the minimised structure. This will be calculated from `atoms` if not given. """ if e0 is None: potential.calc(atoms, energy=True) e0 = atoms.energy info("Zero energy: {0}.".format(e0)) def density_function(params): cell_params = params[:6] # Make a list of 3 item lists atom_params = [atoms[0].position] + zip(*[iter(params[6:])] * 3) new_atoms = atoms.copy() new_atoms.set_cell([[cell_params[0], 0.0, 0.0], [cell_params[1], cell_params[2], 0.0], [cell_params[3], cell_params[4], cell_params[5]]]) new_atoms.set_positions(atom_params) potential.calc(new_atoms, energy=True) # Boltzmann in eV/Kelvin value = math.exp( -(new_atoms.energy - e0 + pressure * new_atoms.get_volume()) / (temperature * 0.00008617343)) return value return density_function
def relax_structure(system, potential, potential_filename=None, relax_positions=True, relax_cell=True): """ Run a geometry optimisation on the structure to find the energy minimum. Parameters ---------- system : ase.Atoms A system of atoms to run the minimisation on. The structure is altered in-place. potential : Potential or str A quippy Potential object with the desired potential, or a potential_str to initialise a new potential. Returns ------- minimised_structure : Atoms The geometry optimised structure. """ info("Inside minimiser.") qsystem = Atoms(system) if not isinstance(potential, Potential): if potential_filename: potential = Potential(potential, param_filename=potential_filename) else: potential = Potential(potential) qsystem.set_calculator(potential) minimiser = Minim(qsystem, relax_positions=relax_positions, relax_cell=relax_cell) with Capturing(debug_on_exit=True): minimiser.run() system.set_cell(qsystem.cell) system.set_positions(qsystem.positions) system.energy = qsystem.get_potential_energy() info("Minimiser done.") return system
def __init__(self, atoms, name, potential, calc_energy=True): """ Initialise the writer with the templating atoms and potential. """ self.atoms = atoms self.name = name self.potential = potential self.calc_energy = calc_energy self.counter = 0 # TODO: check naming self.base_dir = os.getcwd() run_path = '{0}'.format(atoms.info['name']) info("Putting files in {0}.".format(run_path)) os.mkdir(run_path) os.chdir(run_path) trajectory = 'traj_{0}.xyz'.format(atoms.info['name']) self.out = AtomsWriter(trajectory)
def liquid(species, min_atoms=0, supercell=None): """ Generic bcc solid with liquid volume scaling. Parameters ---------- species : str or sats.core.elements.Element Atomic species used to generate the structures. min_atoms : int, optional Minimum number of atoms to include in a supercell of the bulk. If not specified, you get a unit cell. supercell : (int, int, int), optional Request a specific supercell of bulk material. If not given, max_atoms will be used instead to create a cubic cell. Returns ------- liquid : ase.Atoms Atoms with liquid equivalent volume (scaled to 0K). """ lattice_constant = properties[species]['lattice_constant']['liquid'] atoms = ase_bulk(species, 'bcc', a=lattice_constant, orthorhombic=True) # TODO: non cubic supercells n_cells = int(ceil((min_atoms / len(atoms))**(1 / 3))) or 1 if supercell is None: info("Generated supercell of {0}x{0}x{0}.".format(n_cells)) super_atoms = atoms.repeat((n_cells, n_cells, n_cells)) else: info("Generated supercell of {0}.".format(supercell)) super_atoms = atoms.repeat(supercell) info("Structure contains {0} atoms.".format(len(super_atoms))) # This ensures that we can find the lattice constant later on super_atoms.info['lattice_constant'] = lattice_constant return super_atoms
def bulk(species, lattice, min_atoms=0, supercell=None, lattice_constant=None, covera=None): """ Helper to generate a generic bulk configuration. Parameters ---------- species : str or sats.core.elements.Element Atomic species used to generate the structures. lattice : str Bulk lattice type to generate. min_atoms : int, optional Minimum number of atoms to include in a supercell of the bulk. If not specified, you get a unit cell. supercell : (int, int, int), optional Request a specific supercell of bulk material. If not given, max_atoms will be used instead to create a cubic cell. lattice_constant : float, optional Manually set the lattice constant, rather than using a lookup to find the value. Returns ------- bulk : ase.Atoms Atoms in the desired lattice. """ if lattice_constant is None: lattice_constant = properties[species]['lattice_constant'][lattice] if lattice == 'a15': atoms = a15(lattice_constant, species) elif lattice in ['fcc']: # Special check for fcc since the cubic and orthorhombic are # different. atoms = ase_bulk(species, lattice, a=lattice_constant, cubic=True) elif lattice in ['hcp']: if covera is None: covera = properties[species]['covera'][lattice] atoms = ase_bulk(species, lattice, a=lattice_constant, covera=covera) else: atoms = ase_bulk(species, lattice, a=lattice_constant, orthorhombic=True) # TODO: non cubic supercells n_cells = int(ceil((min_atoms / len(atoms))**(1 / 3))) or 1 if supercell is None: info("Generated supercell of {0}x{0}x{0}.".format(n_cells)) super_atoms = atoms.repeat((n_cells, n_cells, n_cells)) else: info("Generated supercell of {0}.".format(supercell)) super_atoms = atoms.repeat(supercell) info("Structure contains {0} atoms.".format(len(super_atoms))) # This ensures that we can find the lattice constant later on super_atoms.info['lattice_constant'] = lattice_constant return super_atoms
def molecular_dynamics(system, potential, potential_filename=None, temperature=300, total_steps=1100000, timestep=1.0, connect_interval=200, write_interval=20000, equilibration_steps=100000, out_of_plane=None, random_seed=None): """ Run very simple molecular dynamics to generate some configurations. Writes configurations out as xyz and CASTEP files. """ info("Inside MD.") if random_seed is None: random_seed = random.SystemRandom().randint(0, 2**63) quippy.system.system_set_random_seeds(random_seed) info("Quippy Random Seed {0}.".format(random_seed)) system = Atoms(system) # Can take Potential objects, or just use a string if not isinstance(potential, Potential): if potential_filename: potential = Potential(potential, param_filename=potential_filename) else: potential = Potential(potential) system.set_calculator(potential) dynamical_system = DynamicalSystem(system) with Capturing(debug_on_exit=True): dynamical_system.rescale_velo(temperature) if out_of_plane is not None: # Stop things moving vertically in the cell dynamical_system.atoms.velo[3, :] = 0 base_dir = os.getcwd() run_path = '{0}_{1:g}/'.format(system.info['name'], temperature) info("Putting files in {0}.".format(run_path)) os.mkdir(run_path) os.chdir(run_path) trajectory = 'traj_{0}_{1:g}.xyz'.format(system.info['name'], temperature) out = AtomsWriter(trajectory) dynamical_system.atoms.set_cutoff(potential.cutoff() + 2.0) dynamical_system.atoms.calc_connect() potential.calc(dynamical_system.atoms, force=True, energy=True, virial=True) structure_count = 0 # Basic NVE molecular dynamics for step_number in range(1, total_steps + 1): dynamical_system.advance_verlet1(timestep, virial=dynamical_system.atoms.virial) potential.calc(dynamical_system.atoms, force=True, energy=True, virial=True) dynamical_system.advance_verlet2(timestep, f=dynamical_system.atoms.force, virial=dynamical_system.atoms.virial) # Maintenance of the system if not step_number % connect_interval: debug("Connect at step {0}".format(step_number)) dynamical_system.atoms.calc_connect() if step_number < equilibration_steps: with Capturing(debug_on_exit=True): dynamical_system.rescale_velo(temperature) if not step_number % write_interval: debug("Status at step {0}".format(step_number)) # Print goes to captured stdout with Capturing(debug_on_exit=True): dynamical_system.print_status( epot=dynamical_system.atoms.energy) dynamical_system.rescale_velo(temperature) if step_number > equilibration_steps: debug("Write at step {0}".format(step_number)) out.write(dynamical_system.atoms) sp_path = '{0:03d}'.format(structure_count) write_filename = '{0}_{1:g}.{2:03d}'.format( system.info['name'], temperature, structure_count) os.mkdir(sp_path) os.chdir(sp_path) castep_write(dynamical_system.atoms, filename=write_filename) espresso_write(dynamical_system.atoms, prefix=write_filename) write_extxyz("{0}.xyz".format(write_filename), dynamical_system.atoms) info("Wrote a configuration {0}.".format(write_filename)) os.chdir('..') structure_count += 1 out.close() os.chdir(base_dir) info("MD Done.")
def get(section, option=None): """ Return the value for the specified option. The value type will be the type expected by the default value. Parameters ---------- section : str The section where the option is located. option : str, optional The name of the option to retrieve. If not given, the section is split as section.option. Returns ------- value : Any The value of the section.option option. The type is the type specified by the default option. """ if option is None: section, option = section.split('.') try: signature = DEFAULTS[section][option] except KeyError: raise ValueError("Unknown option %s.%s" % (section, option)) parser_function = DEFAULTS[section][option].parser_function if args.get(section, option) is not None: value = args.get(section, option) where = 'arguments' elif sats_ini.has_section(option) and sats_ini.has_option(section, option): value = sats_ini.get(section, option) where = 'sats ini' else: value = DEFAULTS[section][option].default_value where = 'defaults' # Parse through options if signature.multiple: try: # split on spaces and commas, throw away brackets value = [x for x in re.split('[\s,\(\)\[\]]*', value) if x] except TypeError: # Already a list or single value pass try: value = [parser_function(x) for x in value] except TypeError: # Make a list of a single value value = [parser_function(value)] else: value = parser_function(value) # Only announce the first time we see an option if (section, option) not in _seen: info("Option {0}.{1} = {2} found in {3}.".format( section, option, value, where)) _seen.append((option, section)) return value
def slice_sample(bulk, potential, potential_filename, temperature, pressure, lattice_delta, atom_delta, m_max, e0=None, init_d=0, num_configs=10, write_interval=-1, random_seed=None): info("Inside Slice Sample.") # Randomise the random seed if random_seed is None: random_seed = SystemRandom().randint(0, 2**63) quippy.system.system_set_random_seeds(random_seed) seed(random_seed) info("Quippy Random Seed {0}.".format(random_seed)) info("Python Random Seed {0}.".format(random_seed)) if not isinstance(potential, Potential): if potential_filename: potential = Potential(potential, param_filename=potential_filename) else: potential = Potential(potential) bulk = Atoms(bulk) # pressure in GPa -> eV/A^3 pressure = pressure / quippy.GPA density_function = create_density_function(bulk, potential, pressure, temperature, e0) # Convert to triangle lattice representation (lower triangle in cell) scaled_positions = bulk.get_scaled_positions() lattice_params = quippy.get_lattice_params(bulk.lattice) new_cell = quippy.make_lattice(*lattice_params).T bulk.set_cell(new_cell) bulk.set_scaled_positions(scaled_positions) params = [ bulk.cell[0][0], bulk.cell[1][0], bulk.cell[1][1], bulk.cell[2][0], bulk.cell[2][1], bulk.cell[2][2] ] info("Re-orineted to cell: {0}.".format(new_cell.tolist())) for atom in bulk.positions.tolist()[1:]: params.extend(atom) # value for the first iteration df_value = density_function(params) # Floating count so that division by write_interval make it integer for # written configurations count = 0.0 idx = init_d output = ParamWriter(bulk, bulk.info['name'], potential) # Only write once everything has changed if write_interval < 1: write_interval = len(params) info("Writing configurations after {0} steps.".format(write_interval)) # Loop just iterates to the next value of x while count / write_interval < num_configs: # Determine the delta value outside the incrementer, so we can make it # do less work if idx < 6: delta = lattice_delta else: delta = atom_delta # Here's where the magic happens params, df_value = increment_params(density_function, params, idx, delta, m_max, df_0=df_value) debug("SLICE_SAMPLE: {0:g}".format(count / write_interval)) debug("Params: {0}.".format(", ".join("{0}".format(x) for x in params))) if not count % write_interval: output.write_config(params) count += 1 idx += 1 if idx >= len(params): idx = 0 output.close() info("Slice Sample Done.")