def print_parent_child_diff(parent, child): ps = parent.structure cs = child.structure slog("PARENT UUID :\t{}".format(parent.uuid)) slog("CHILD UUID :\t{}".format(child.uuid)) slog("lattice constants: (%.2f, %.2f, %.2f) => (%.2f, %.2f, %.2f)" % (ps.a, ps.b, ps.c, cs.a, cs.b, cs.c)) slog("number of atoms: %.2f => %.2f" % (len(parent.structure.atom_sites), len(child.structure.atom_sites))) parent_ats = ", ".join(["(%.1f, %.1f)" % (ats.epsilon, ats.sigma) for ats in ps.atom_types]) child_ats = ", ".join(["(%.1f, %.1f)" % (ats.epsilon, ats.sigma) for ats in cs.atom_types]) slog("atom types: %s => %s" % (parent_ats, child_ats))
def parse_output(output_file, material, simulation_config): """Parse output file for gas adsorption data. Args: output_file (str): path to simulation output file. Returns: results (dict): absolute and excess molar, gravimetric, and volumetric gas loadings, as well as energy of average, van der Waals, and Coulombic host-host, host-adsorbate, and adsorbate-adsorbate interactions. """ gas_loading = GasLoading() gas_loading.adsorbate = simulation_config["adsorbate"] gas_loading.pressure = simulation_config["pressure"] gas_loading.temperature = simulation_config["temperature"] atom_blocks = [] with open(output_file) as origin: lines = origin.read().split('\n') for i, line in enumerate(lines): if "absolute [cm^3 (STP)/c" in line: gas_loading.absolute_volumetric_loading = float( line.split()[6]) gas_loading.absolute_volumetric_loading_error = float( line.split()[8]) elif "Number of molecules:" in line: atom_blocks = [ float(lines[offset + i + 5].split()[2]) for offset in range(5) ] elif "Conversion factor molecules/unit cell -> cm^3 STP/cm^3:" in line: atoms_uc_to_vv = float(line.split()[7]) slog("{} LOADING : {} v/v (STP)".format( simulation_config["adsorbate"], gas_loading.absolute_volumetric_loading)) if material.parent: slog("(parent LOADING : {} v/v (STP))".format( material.parent.gas_loading[0].absolute_volumetric_loading)) return gas_loading, atom_blocks, atoms_uc_to_vv
def run_all_simulations(material, config): """Simulate helium void fraction, gas loading, and surface area. Args: material (sqlalchemy.orm.query.Query): material to be analyzed. Depending on properties specified in config, adds simulated data for helium void fraction, gas loading, heat of adsorption, surface area, and corresponding bins to row in database corresponding to the input-material. """ slog("-----------------------------------------------") for simulation_number in config["simulations"]: simulation_config = config["simulations"][simulation_number] slog('Time : {:%Y-%m-%d %H:%M:%S}'.format(datetime.now())) slog("Simulation type : {}".format(simulation_config["type"])) getattr(simulate, simulation_config["type"]).run(material, simulation_config, config) slog("--") slog('{:%Y-%m-%d %H:%M:%S}'.format(datetime.now()))
def run(material, simulation_config, config): """Runs void fraction simulation. Args: material (Material): material record. Returns: results (dict): void fraction simulation results. """ output_dir = "output_{}_{}".format(material.uuid, uuid4()) slog("Output directory : {}".format(output_dir)) os.makedirs(output_dir, exist_ok=True) write_output_files(material, simulation_config, output_dir) # Run simulations slog("Probe : {}".format(simulation_config["adsorbate"])) if "do_geo" in simulation_config: slog("Probe radius [geo]: {}".format(simulation_config["probe_radius"])) slog("Temperature : {}".format(simulation_config["temperature"])) void_fraction = VoidFraction() void_fraction.adsorbate = simulation_config["adsorbate"] void_fraction.temperature = simulation_config["temperature"] if "do_raspa" in simulation_config and simulation_config["do_raspa"]: tbegin = time.perf_counter() process = subprocess.run(["simulate", "-i", "./void_fraction.input"], check=True, cwd=output_dir, capture_output=True, text=True) data_files = glob(os.path.join(output_dir, "Output", "System_0", "*.data")) if len(data_files) != 1: raise Exception("ERROR: There should only be one data file in the output directory for %s. Check code!" % output_dir) output_file = data_files[0] # Parse output parse_output(output_file, material, void_fraction) slog("RASPA void fraction simulation time: %5.2f seconds" % (time.perf_counter() - tbegin)) slog("RASPA VOID FRACTION : {}".format(void_fraction.void_fraction)) if material.parent: slog("(parent VOID FRACTION : {})".format(material.parent.void_fraction[0].void_fraction)) # run geometric void fraction if "do_geo" in simulation_config and simulation_config["do_geo"]: tbegin = time.perf_counter() atoms = [(a.x * material.structure.a, a.y * material.structure.b, a.z * material.structure.c, a.atom_types.sigma) for a in material.structure.atom_sites] box = (material.structure.a, material.structure.b, material.structure.c) void_fraction.void_fraction_geo = calculate_void_fraction(atoms, box, probe_r=simulation_config["probe_radius"]) slog("GEOMETRIC void fraction: %f" % void_fraction.void_fraction_geo) slog("GEOMETRIC void fraction simulation time: %5.2f seconds" % (time.perf_counter() - tbegin)) if "do_zeo" in simulation_config: pass # run zeo void fraction here material.void_fraction.append(void_fraction) if not config['keep_configs']: shutil.rmtree(output_dir, ignore_errors=True) sys.stdout.flush()
def mutate_material(parent, config): child = parent.clone() cs = child.structure perturb = set(config["perturb"]) if config["perturb_type"] == "random": child.perturbation = choice(perturb) perturb = {child.perturbation} else: child.perturbation = "all" slog("Parent id: %d" % (child.parent_id)) slog("Perturbing: %s [%s]" % (child.perturbation, perturb)) ms = config["mutation_strength"] if config["number_of_atom_types"] > len(cs.atom_types): num_atom_types_to_add = config["number_of_atom_types"] - len(cs.atom_types) slog("Adding %d random atom types so we have number defined in the config" % num_atom_types_to_add) cs.atom_types += random_atom_types(num_atom_types_to_add, config) if perturb & {"num_atoms"} and random() < ms: if random() < 0.5: # remove an atoms if len(cs.atom_sites) > config['num_atoms_limits'][0]: site_to_remove = choice(cs.atom_sites) slog("Removing atom site: ", site_to_remove) removed_site = cs.atom_sites.remove(site_to_remove) else: # add an atom if len(cs.atom_sites) < config['num_atoms_limits'][1]: slog("Adding atom site...") cs.atom_sites += random_atom_sites(1, cs.atom_types) if perturb & {"atom_type_assignments"}: for i, atom in enumerate(cs.atom_sites): if random() < ms**2: new_atom_type = choice(cs.atom_types) slog("Reassigning atom type for site %d from %d to %d" % (i, cs.atom_types.index(atom.atom_types), cs.atom_types.index(new_atom_type))) atom.atom_types = new_atom_type if perturb & {"atom_types"}: sigl = config["sigma_limits"] epsl = config["epsilon_limits"] for at in cs.atom_types: at.sigma = perturb_unweighted(at.sigma, ms * (sigl[1] - sigl[0]), sigl) at.epsilon = perturb_unweighted(at.epsilon, ms * (epsl[1] - epsl[0]), epsl) if perturb & {"lattice"}: ll = config["lattice_constant_limits"] cs.a = perturb_unweighted(cs.a, ms * (ll[1] - ll[0]), ll) if config["lattice_cubic"]: cs.b = cs.a cs.c = cs.a else: cs.b = perturb_unweighted(cs.b, ms * (ll[1] - ll[0]), ll) cs.c = perturb_unweighted(cs.c, ms * (ll[1] - ll[0]), ll) child.number_density = len(cs.atom_sites) / cs.volume if perturb & {"atom_sites"}: for a in cs.atom_sites: a.x = random_position(a.x, random(), ms) a.y = random_position(a.y, random(), ms) a.z = random_position(a.z, random(), ms) print_parent_child_diff(parent, child) return child
def run(material, simulation_config, config): """Runs gas loading simulation. Args: material_id (Material): material record. Returns: results (dict): gas loading simulation results. """ adsorbate = simulation_config["adsorbate"] output_dir = "output_{}_{}".format(material.uuid, uuid4()) os.makedirs(output_dir, exist_ok=True) raspa_config = "./{}_loading.input".format(adsorbate) raspa_restart_config = "./{}_loading_restart.input".format(adsorbate) # RASPA input-files write_output_files(material, simulation_config, output_dir, restart=False, filename=os.path.join(output_dir, raspa_config)) write_output_files(material, simulation_config, output_dir, restart=True, filename=os.path.join(output_dir, raspa_restart_config)) # Run simulations slog("Adsorbate : {}".format(adsorbate)) slog("Pressure : {}".format(simulation_config["pressure"])) slog("Temperature : {}".format(simulation_config["temperature"])) unit_cells = material.structure.minimum_unit_cells( simulation_config['cutoff']) total_unit_cells = unit_cells[0] * unit_cells[1] * unit_cells[2] all_atom_blocks = [] process = subprocess.run(["simulate", "-i", raspa_config], check=True, cwd=output_dir, capture_output=True, text=True) slog(process.stdout) for i in range(simulation_config['max_restarts'] + 1): data_files = glob( os.path.join(output_dir, "Output", "System_0", "*.data")) if len(data_files) != 1: raise Exception( "ERROR: There should only be one data file in the output directory for %s. Check code!" % output_dir) output_file = data_files[0] # Parse output gas_loading, atom_blocks, atoms_uc_to_vv = parse_output( output_file, material, simulation_config) atom_blocks = [ a * atoms_uc_to_vv / total_unit_cells for a in atom_blocks ] slog("new blocks for averaging [v/v]: ", atom_blocks) slog("atoms_uc_to_vv = %f" % atoms_uc_to_vv) slog("reported V/V: %f" % gas_loading.absolute_volumetric_loading) slog("reported err: %f" % gas_loading.absolute_volumetric_loading_error) all_atom_blocks += atom_blocks slog("all blocks: ", all_atom_blocks) # assign two initialization blocks to every restart run run_blocks = all_atom_blocks[math.floor(i / 2) * 5:] slog("run blocks: ", run_blocks) slog("run blocks len: %f" % (len(run_blocks) / 5)) blocks_for_averaging = np.mean(np.array(run_blocks).reshape( -1, int(len(run_blocks) / 5)), axis=1) slog("incorporated blocks for averaging [v/v]: ", blocks_for_averaging) atoms_std = np.std(blocks_for_averaging) slog("2*std of all blocks avg %d: %f" % (i, atoms_std * 2)) slog("2*std of all blocks: %f" % (2 * np.std(run_blocks))) error_vv = 2 * atoms_std * atoms_uc_to_vv / total_unit_cells gas_loading.absolute_volumetric_loading = np.mean(blocks_for_averaging) gas_loading.absolute_volumetric_loading_error = error_vv slog("calculated V/V: %f" % gas_loading.absolute_volumetric_loading) slog("calculated error: %f" % error_vv) slog("Copying restart to RestartInitial...") # remove old RestartInitial directory and copy the current one to there shutil.rmtree(os.path.join(output_dir, "RestartInitial"), ignore_errors=True) shutil.copytree(os.path.join(output_dir, "Restart"), os.path.join(output_dir, "RestartInitial")) slog("Moving backup RASPA outputs to restart index") shutil.move(os.path.join(output_dir, "Output"), os.path.join(output_dir, "Output-%d" % i)) shutil.move(os.path.join(output_dir, "Restart"), os.path.join(output_dir, "Restart-%d" % i)) shutil.move(os.path.join(output_dir, "Movies"), os.path.join(output_dir, "Movies-%d" % i)) shutil.move(os.path.join(output_dir, "VTK"), os.path.join(output_dir, "VTK-%d" % i)) gas_loading.cycles = simulation_config['simulation_cycles'] * (i + 1) if (gas_loading.absolute_volumetric_loading_error < simulation_config['restart_err_threshold']): slog( "Exiting because v/v err < restart_err_threshold: %4.2f < %4.2f" % (gas_loading.absolute_volumetric_loading_error, simulation_config['restart_err_threshold'])) break elif i == simulation_config['max_restarts']: slog( "Exiting because we've already restarted maximum number of times." ) slog("v/v err >= restart_err_threshold: %4.2f >= %4.2f" % (gas_loading.absolute_volumetric_loading_error, simulation_config['restart_err_threshold'])) break else: slog("\n--") slog("restart # %d" % i) process = subprocess.run(["simulate", "-i", raspa_restart_config], check=True, cwd=output_dir, capture_output=True, text=True) slog(process.stdout) material.gas_loading.append(gas_loading) if not config['keep_configs']: shutil.rmtree(output_dir, ignore_errors=True) sys.stdout.flush()