def workflow_derivative_couplings(workflow_settings: Dict): """ Compute the derivative couplings from an MD trajectory. :param workflow_settings: Arguments to compute the oscillators see: `data/schemas/derivative_couplings.json :returns: None """ # Arguments to compute the orbitals and configure the workflow. see: # `data/schemas/general_settings.json config = workflow_settings['general_settings'] # Dictionary containing the general configuration config.update(initialize(**config)) # compute the molecular orbitals mo_paths_hdf5 = calculate_mos(**config) # Overlap matrix at two different times promised_overlaps = calculate_overlap( config['project_name'], config['path_hdf5'], config['dictCGFs'], config['geometries'], mo_paths_hdf5, config['hdf5_trans_mtx'], config['enumerate_from'], workflow_settings['overlaps_deph'], nHOMO=workflow_settings['nHOMO'], couplings_range=workflow_settings['couplings_range']) # Create a function that returns a proxime array of couplings schedule_couplings = schedule(lazy_couplings) # Calculate Non-Adiabatic Coupling promised_crossing_and_couplings = schedule_couplings( promised_overlaps, config['path_hdf5'], config['project_name'], config['enumerate_from'], workflow_settings['nHOMO'], workflow_settings['dt'], workflow_settings['tracking'], workflow_settings['write_overlaps'], algorithm=workflow_settings['algorithm']) # Write the results in PYXAID format work_dir = config['work_dir'] path_hamiltonians = join(work_dir, 'hamiltonians') if not os.path.exists(path_hamiltonians): os.makedirs(path_hamiltonians) # Inplace scheduling of write_hamiltonians function. # Equivalent to add @schedule on top of the function schedule_write_ham = schedule(write_hamiltonians) # Number of matrix computed nPoints = len(config['geometries']) - 2 # Write Hamilotians in PYXAID format promise_files = schedule_write_ham( config['path_hdf5'], mo_paths_hdf5, promised_crossing_and_couplings, nPoints, path_dir_results=path_hamiltonians, enumerate_from=config['enumerate_from'], nHOMO=workflow_settings['nHOMO'], couplings_range=workflow_settings['couplings_range']) run(promise_files, folder=work_dir) remove_folders(config['folders'])
def workflow_stddft(config: dict) -> None: """ Compute the excited states using simplified TDDFT :param workflow_settings: Arguments to compute the oscillators see: `data/schemas/absorption_spectrum.json :returns: None """ # Dictionary containing the general configuration config.update(initialize(config)) # Single Point calculations settings using CP2K mo_paths_hdf5, energy_paths_hdf5 = unpack(calculate_mos(config), 2) # Read structures molecules_au = [ change_mol_units(parse_string_xyz(gs)) for i, gs in enumerate(config.geometries) if (i % config.stride) == 0 ] # Noodles promised call scheduleTDDFT = schedule(compute_excited_states_tddft) results = gather(*[ scheduleTDDFT(config, mo_paths_hdf5[i], DictConfig({ 'i': i * config.stride, 'mol': mol })) for i, mol in enumerate(molecules_au) ]) return run(gather(results, energy_paths_hdf5), folder=config['workdir'])
def workflow_stddft(config: dict) -> None: """ Compute the excited states using simplified TDDFT :param workflow_settings: Arguments to compute the oscillators see: `data/schemas/absorption_spectrum.json :returns: None """ # Dictionary containing the general configuration config.update(initialize(config)) # Single Point calculations settings using CP2K mo_paths_hdf5, energy_paths_hdf5 = unpack(calculate_mos(config), 2) # Read structures molecules_au = [change_mol_units(parse_string_xyz(gs)) for i, gs in enumerate(config.geometries) if (i % config.stride) == 0] # Noodles promised call scheduleTDDFT = schedule(compute_excited_states_tddft) results = gather( *[scheduleTDDFT(config, mo_paths_hdf5[i], DictConfig( {'i': i * config.stride, 'mol': mol})) for i, mol in enumerate(molecules_au)]) return run(gather(results, energy_paths_hdf5), folder=config['workdir'])
def workflow_derivative_couplings(config: dict) -> list: """ Compute the derivative couplings from an MD trajectory. :param workflow_settings: Arguments to compute the oscillators see: `nac/workflows/schemas.py :returns: None """ # Dictionary containing the general configuration config.update(initialize(config)) logger.info("starting couplings calculation!") # compute the molecular orbitals mo_paths_hdf5, energy_paths_hdf5 = unpack(calculate_mos(config), 2) # mo_paths_hdf5 = run(calculate_mos(config), folder=config.workdir) # Overlap matrix at two different times promised_overlaps = calculate_overlap(config, mo_paths_hdf5) # Calculate Non-Adiabatic Coupling promised_crossing_and_couplings = lazy_couplings(config, promised_overlaps) # Write the results in PYXAID format config.path_hamiltonians = create_path_hamiltonians(config.workdir) # Inplace scheduling of write_hamiltonians function. # Equivalent to add @schedule on top of the function schedule_write_ham = schedule(write_hamiltonians) # Number of matrix computed config["nPoints"] = len(config.geometries) - 2 # Write Hamilotians in PYXAID format promise_files = schedule_write_ham( config, promised_crossing_and_couplings, mo_paths_hdf5) results = run( gather(promise_files, energy_paths_hdf5), folder=config.workdir, always_cache=False) remove_folders(config.folders) return results
def workflow_oscillator_strength(workflow_settings: Dict): """ Compute the oscillator strength. :param workflow_settings: Arguments to compute the oscillators see: `data/schemas/absorption_spectrum.json :returns: None """ # Arguments to compute the orbitals and configure the workflow. see: # `data/schemas/general_settings.json config = workflow_settings['general_settings'] # Dictionary containing the general configuration config.update(initialize(**config)) # Point calculations Using CP2K mo_paths_hdf5 = calculate_mos(**config) # geometries in atomic units molecules_au = [change_mol_units(parse_string_xyz(gs)) for gs in config['geometries']] # Construct initial and final states ranges transition_args = [workflow_settings[key] for key in ['initial_states', 'final_states', 'nHOMO']] initial_states, final_states = build_transitions(*transition_args) # Make a promise object the function the compute the Oscillator Strenghts scheduleOscillator = schedule(calc_oscillator_strenghts) oscillators = gather( *[scheduleOscillator( i, mol, mo_paths_hdf5, initial_states, final_states, config) for i, mol in enumerate(molecules_au) if i % workflow_settings['calculate_oscillator_every'] == 0]) energies, promised_cross_section = create_promised_cross_section( oscillators, workflow_settings['broadening'], workflow_settings['energy_range'], workflow_settings['convolution'], workflow_settings['calculate_oscillator_every']) cross_section, data = run( gather(promised_cross_section, oscillators), folder=config['work_dir']) return store_data(data, energies, cross_section)
def workflow_derivative_couplings(config: dict) -> list: """ Compute the derivative couplings from an MD trajectory. :param workflow_settings: Arguments to compute the oscillators see: `nac/workflows/schemas.py :returns: None """ # Dictionary containing the general configuration config.update(initialize(config)) logger.info("starting!") # compute the molecular orbitals mo_paths_hdf5, energy_paths_hdf5 = unpack(calculate_mos(config), 2) # mo_paths_hdf5 = run(calculate_mos(config), folder=config.workdir) # Overlap matrix at two different times promised_overlaps = calculate_overlap(config, mo_paths_hdf5) # Calculate Non-Adiabatic Coupling promised_crossing_and_couplings = lazy_couplings(config, promised_overlaps) # Write the results in PYXAID format config.path_hamiltonians = create_path_hamiltonians(config.workdir) # Inplace scheduling of write_hamiltonians function. # Equivalent to add @schedule on top of the function schedule_write_ham = schedule(write_hamiltonians) # Number of matrix computed config["nPoints"] = len(config.geometries) - 2 # Write Hamilotians in PYXAID format promise_files = schedule_write_ham( config, promised_crossing_and_couplings, mo_paths_hdf5) results = run(gather(promise_files, energy_paths_hdf5), folder=config.workdir) remove_folders(config.folders) return results
def workflow_single_points(config: dict) -> list: """ Single point calculations for a given trajectory :param workflow_settings: Arguments to run the single points calculations see: `nac/workflows/schemas.py """ # Dictionary containing the general configuration config.update(initialize(config)) logger.info("starting!") # compute the molecular orbitals # Unpack mo_paths_hdf5 = calculate_mos(config) # Pack results = run(mo_paths_hdf5, folder=config.workdir) return results
def calculate_ETR( package_name: str, project_name: str, package_args: Dict, path_time_coeffs: str=None, geometries: List=None, initial_conditions: List=None, path_hdf5: str=None, basis_name: str=None, enumerate_from: int=0, package_config: Dict=None, calc_new_wf_guess_on_points: str=None, guess_args: Dict=None, work_dir: str=None, traj_folders: List=None, dictCGFs: Dict=None, orbitals_range: Tuple=None, pyxaid_HOMO: int=None, pyxaid_Nmin: int=None, pyxaid_Nmax: int=None, fragment_indices: None=List, dt: float=1, **kwargs): """ Use a md trajectory to calculate the Electron transfer rate. :param package_name: Name of the package to run the QM simulations. :param project_name: Folder name where the computations are going to be stored. :param package_args: Specific settings for the package. :param path_hdf5: Path to the HDF5 file that contains the numerical results. :param geometries: List of string cotaining the molecular geometries. :paramter dictCGFS: Dictionary from Atomic Label to basis set :type dictCGFS: Dict String [CGF], CGF = ([Primitives], AngularMomentum), Primitive = (Coefficient, Exponent) :param calc_new_wf_guess_on_points: number of Computations that used a previous calculation as guess for the wave function. :param nHOMO: index of the H**O orbital. :param couplings_range: Range of MO use to compute the nonadiabatic :param pyxaid_range: range of HOMOs and LUMOs used by pyxaid. :param enumerate_from: Number from where to start enumerating the folders create for each point in the MD. :param traj_folders: List of paths to where the CP2K MOs are printed. :param package_config: Parameters required by the Package. :param fragment_indices: indices of atoms belonging to a fragment. :param dt: integration time used in the molecular dynamics. :returns: None """ # Start logging event file_log = '{}.log'.format(project_name) logging.basicConfig(filename=file_log, level=logging.DEBUG, format='%(levelname)s:%(message)s %(asctime)s\n', datefmt='%m/%d/%Y %I:%M:%S %p') # prepare Cp2k Job point calculations Using CP2K mo_paths_hdf5 = calculate_mos( package_name, geometries, project_name, path_hdf5, traj_folders, package_args, guess_args, calc_new_wf_guess_on_points, enumerate_from, package_config=package_config) # geometries in atomic units molecules_au = [change_mol_units(parse_string_xyz(gs)) for gs in geometries] # Time-dependent coefficients time_depend_coeffs = read_time_dependent_coeffs(path_time_coeffs) msg = "Reading time_dependent coefficients from: {}".format(path_time_coeffs) logger.info(msg) # compute_overlaps_ET scheduled_overlaps = schedule(compute_overlaps_ET) fragment_overlaps = scheduled_overlaps( project_name, molecules_au, basis_name, path_hdf5, dictCGFs, mo_paths_hdf5, fragment_indices, enumerate_from, package_name) # Delta time in a.u. dt_au = dt * femtosec2au # Indices relation between the PYXAID active space and the orbitals # stored in the HDF5 map_index_pyxaid_hdf5 = create_map_index_pyxaid( orbitals_range, pyxaid_HOMO, pyxaid_Nmin, pyxaid_Nmax) # Number of ETR points calculated with the MD trajectory n_points = len(geometries) - 2 # Read the swap between Molecular orbitals obtained from a previous # Coupling calculation swaps = read_swaps(path_hdf5, project_name) # Electron transfer rate for each frame of the Molecular dynamics scheduled_photoexcitation = schedule(compute_photoexcitation) etrs = scheduled_photoexcitation( path_hdf5, time_depend_coeffs, fragment_overlaps, map_index_pyxaid_hdf5, swaps, n_points, dt_au) # Execute the workflow electronTransferRates, path_overlaps = run( gather(etrs, fragment_overlaps), folder=work_dir) for i, mtx in enumerate(electronTransferRates): write_ETR(mtx, dt, i) write_overlap_densities(path_hdf5, path_overlaps, dt)
def workflow_electron_transfer(workflow_settings: Dict): """ Use a MD trajectory to calculate the Electron transfer rate. :param workflow_settings: Arguments to compute the oscillators see: `data/schemas/electron_transfer.json :returns: None """ # Arguments to compute the orbitals and configure the workflow. see: # `data/schemas/general_settings.json config = workflow_settings['general_settings'] # Dictionary containing the general configuration config.update(initialize(**config)) # Point calculations Using CP2K mo_paths_hdf5 = calculate_mos(**config) # geometries in atomic units molecules_au = [ change_mol_units(parse_string_xyz(gs)) for gs in config['geometries'] ] # Time-dependent coefficients path_time_coeffs = workflow_settings['path_time_coeffs'] time_depend_coeffs = read_time_dependent_coeffs(path_time_coeffs) msg = "Reading time_dependent coefficients from: {}".format( path_time_coeffs) logger.info(msg) # compute_overlaps_ET scheduled_overlaps = schedule(compute_overlaps_ET) fragment_overlaps = scheduled_overlaps( config['project_name'], molecules_au, config['basis_name'], config['path_hdf5'], config['dictCGFs'], mo_paths_hdf5, workflow_settings['fragment_indices'], config['enumerate_from'], config['package_name']) # Delta time in a.u. dt = workflow_settings['dt'] dt_au = dt * femtosec2au # Indices relation between the PYXAID active space and the orbitals # stored in the HDF5 args_map_index = [ workflow_settings[key] for key in ['orbitals_range', 'pyxaid_HOMO', 'pyxaid_Nmin', 'pyxaid_Nmax'] ] map_index_pyxaid_hdf5 = create_map_index_pyxaid(*args_map_index) # Number of points in the pyxaid trajectory: # shape: (initial_conditions, n_points, n_states) n_points = len(config['geometries']) - 2 # Read the swap between Molecular orbitals obtained from a previous # Coupling calculation swaps = read_swaps(['path_hdf5'], ['project_name']) # Electron transfer rate for each frame of the Molecular dynamics scheduled_photoexcitation = schedule(compute_photoexcitation) etrs = scheduled_photoexcitation(config['path_hdf5'], time_depend_coeffs, fragment_overlaps, map_index_pyxaid_hdf5, swaps, n_points, ['pyxaid_iconds'], dt_au) # Execute the workflow electronTransferRates, path_overlaps = run(gather(etrs, fragment_overlaps), folder=config['work_dir']) for i, mtx in enumerate(electronTransferRates): write_ETR(mtx, dt, i) write_overlap_densities(config['path_hdf5'], path_overlaps, swaps, dt)
def generate_pyxaid_hamiltonians(package_name: str, project_name: str, package_args: Dict, guess_args: Dict = None, geometries: List = None, dictCGFs: Dict = None, calc_new_wf_guess_on_points: str = None, path_hdf5: str = None, enumerate_from: int = 0, package_config: Dict = None, dt: float = 1, traj_folders: List = None, work_dir: str = None, basisname: str = None, hdf5_trans_mtx: str = None, nHOMO: int = None, couplings_range: Tuple = None, algorithm='levine', ignore_warnings=False, tracking=True) -> None: """ Use a md trajectory to generate the hamiltonian components to run PYXAID nonadiabatic molecular dynamics. :param package_name: Name of the package to run the QM simulations. :param project_name: Folder name where the computations are going to be stored. :param package_args: Specific Settings for the package that will compute the MOs. :param geometries: List of string cotaining the molecular geometries numerical results. :paramter dictCGFS: Dictionary from Atomic Label to basis set :param calc_new_wf_guess_on_points: Calculate a guess wave function either in the first point or on each point of the trajectory. :param path_hdf5: path to the HDF5 file were the data is going to be stored. :param enumerate_from: Number from where to start enumerating the folders create for each point in the MD. :param hdf5_trans_mtx: Path into the HDF5 file where the transformation matrix (from Cartesian to sphericals) is stored. :param dt: Time used in the dynamics (femtoseconds) :param package_config: Parameters required by the Package. :type package_config: Dict :param nHOMO: index of the H**O orbital. :param couplings_range: Range of MO use to compute the nonadiabatic coupling matrix. :returns: None """ # Start logging event file_log = '{}.log'.format(project_name) logging.basicConfig(filename=file_log, level=logging.DEBUG, format='%(levelname)s:%(message)s %(asctime)s\n', datefmt='%m/%d/%Y %I:%M:%S %p') # Log initial config information log_config(work_dir, path_hdf5, algorithm) # prepare Cp2k Jobs # Point calculations Using CP2K mo_paths_hdf5 = calculate_mos(package_name, geometries, project_name, path_hdf5, traj_folders, package_args, guess_args, calc_new_wf_guess_on_points, enumerate_from, package_config=package_config, ignore_warnings=ignore_warnings) # Overlap matrix at two different times promised_overlaps = calculate_overlap(project_name, path_hdf5, dictCGFs, geometries, mo_paths_hdf5, hdf5_trans_mtx, enumerate_from, nHOMO=nHOMO, couplings_range=couplings_range) # Calculate Non-Adiabatic Coupling schedule_couplings = schedule(lazy_couplings) promised_crossing_and_couplings = schedule_couplings(promised_overlaps, path_hdf5, project_name, enumerate_from, nHOMO, dt, tracking, algorithm=algorithm) # Write the results in PYXAID format path_hamiltonians = join(work_dir, 'hamiltonians') if not os.path.exists(path_hamiltonians): os.makedirs(path_hamiltonians) # Inplace scheduling of write_hamiltonians function. # Equivalent to add @schedule on top of the function schedule_write_ham = schedule(write_hamiltonians) # Number of matrix computed nPoints = len(geometries) - 2 # Write Hamilotians in PYXAID format promise_files = schedule_write_ham(path_hdf5, mo_paths_hdf5, promised_crossing_and_couplings, nPoints, path_dir_results=path_hamiltonians, enumerate_from=enumerate_from, nHOMO=nHOMO, couplings_range=couplings_range) run(promise_files, folder=work_dir) remove_folders(traj_folders)
def workflow_oscillator_strength( package_name: str, project_name: str, package_args: Dict, guess_args: Dict = None, geometries: List = None, dictCGFs: Dict = None, enumerate_from: int = 0, calc_new_wf_guess_on_points: str = None, path_hdf5: str = None, package_config: Dict = None, work_dir: str = None, initial_states: Any = None, final_states: Any = None, traj_folders: List = None, hdf5_trans_mtx: str = None, nHOMO: int = None, couplings_range: Tuple = None, calculate_oscillator_every: int = 50, convolution: str = 'gaussian', broadening: float = 0.1, # eV energy_range: Tuple = None, # eV geometry_units='angstrom', **kwargs): """ Compute the oscillator strength :param package_name: Name of the package to run the QM simulations. :param project_name: Folder name where the computations are going to be stored. :param geometry:string containing the molecular geometry. :param package_args: Specific settings for the package :param guess_args: Specific settings for guess calculate with `package`. :type package_args: dict :param initial_states: List of the initial Electronic states. :type initial_states: [Int] :param final_states: List containing the sets of possible electronic states. :type final_states: [[Int]] :param calc_new_wf_guess_on_points: Points where the guess wave functions are calculated. :param package_config: Parameters required by the Package. :param convolution: gaussian | lorentzian :param calculate_oscillator_every: step to compute the oscillator strengths :returns: None """ # Start logging event file_log = '{}.log'.format(project_name) logging.basicConfig(filename=file_log, level=logging.DEBUG, format='%(levelname)s:%(message)s %(asctime)s\n', datefmt='%m/%d/%Y %I:%M:%S %p') # Point calculations Using CP2K mo_paths_hdf5 = calculate_mos(package_name, geometries, project_name, path_hdf5, traj_folders, package_args, guess_args, calc_new_wf_guess_on_points, enumerate_from, package_config=package_config) # geometries in atomic units molecules_au = [ change_mol_units(parse_string_xyz(gs)) for gs in geometries ] # Construct initial and final states ranges initial_states, final_states = build_transitions(initial_states, final_states, nHOMO) # Schedule the function the compute the Oscillator Strenghts scheduleOscillator = schedule(calcOscillatorStrenghts) oscillators = gather(*[ scheduleOscillator(i, project_name, mo_paths_hdf5, dictCGFs, mol, path_hdf5, hdf5_trans_mtx=hdf5_trans_mtx, initial_states=initial_states, final_states=final_states) for i, mol in enumerate(molecules_au) if i % calculate_oscillator_every == 0 ]) energies, promised_cross_section = create_promised_cross_section( oscillators, broadening, energy_range, convolution, calculate_oscillator_every) cross_section, data = run(gather(promised_cross_section, oscillators), folder=work_dir) # Transform the energy to nm^-1 energies_nm = energies * 1240 # Save cross section np.savetxt( 'cross_section_cm.txt', np.stack((energies, energies_nm, cross_section, cross_section * 1e16), axis=1), header= 'Energy[eV] Energy[nm^-1] photoabsorption_cross_section[cm^2] photoabsorption_cross_section[^2]' ) # molar extinction coefficients (e in M-1 cm-1) nA = physical_constants['Avogadro constant'][0] cte = np.log(10) * 1e3 / nA extinction_coefficients = cross_section / cte np.savetxt( 'molar_extinction_coefficients.txt', np.stack((energies, energies_nm, extinction_coefficients), axis=1), header='Energy[eV] Energy[nm^-1] Extinction_coefficients[M^-1 cm^-1]') print("Data: ", data) print("Calculation Done") # Write data in human readable format write_information(data) return data