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'])
Example #3
0
def rdf(fn, atoms_i, atoms_j, dr, rmax):
    """
    A function that computes the radial distribution function between a pair of atoms
    """
    # Read the geometries from the MD file
    geometries = split_file_geometries(fn)
    molecs = [parse_string_xyz(gs) for gs in geometries]

    # Get the number of atoms and number of frames in trajectory file
    n_frames = len(molecs)
    n_atoms = len(molecs[0])

    # Create grid of r values to compute the pair correlation function g_ij
    r_grid = np.arange(0, rmax, dr)
    # Used for the histogram
    n_bins = r_grid.size

    # Find the indexes in the bond_matrix of the atomic types involved in the calculation of g_ij
    atoms = np.asarray([molecs[0][i].symbol for i in range(n_atoms)])
    index_i = np.where(atoms == atoms_i)
    index_j = np.where(atoms == atoms_j)

    rdf_tot = np.zeros(n_bins)
    for iframe in range(n_frames):
        # Read coordinates from iframe
        coords = np.asarray([molecs[iframe][i].xyz for i in range(n_atoms)])
        # Compute bond distance matrix for iframe
        bond_mtx = cdist(coords, coords)
        # Slice the bond_matrix with only the atom types
        sliced_mtx = np.triu(bond_mtx[np.ix_(index_i[0], index_j[0])])
        # Count the number of atoms within r and r+dr
        rdf, r_grid = np.histogram(sliced_mtx, bins=n_bins, range=(0, rmax))
        # Sum over all rdf
        rdf_tot += rdf

    # Center radii
    r = 0.5 * (r_grid[1:] + r_grid[:-1])
    # Compute the volume in a concentric sphere of size dr at a distance r from the origin
    # elemental volume dV = 4*pi*r^2*dr
    volume = (4 / 3) * np.pi * (np.power(r_grid[1:], 3) -
                                np.power(r_grid[:-1], 3))
    vol_tot = np.sum(volume)
    # Compute the rho_ab(r)
    # This is the pair density distribution for each frame
    rho_ab = rdf_tot / (volume * n_frames)
    # Compute the bulk density
    # skip first element otherwise it counts an atom with itself
    n_tot = np.sum(rdf_tot[1:]) / n_frames
    rho_bulk = n_tot / vol_tot
    # Density = counts / volume[1:] # skip the first element dV which is 0
    g_ab = rho_ab / rho_bulk
    # Compute also the potential of mean force
    w_ab = -np.log(g_ab)

    # we skip the first element at r=0, which in the histogram is counted many times:
    # counts an atom with itself
    return r[1:], g_ab[1:], w_ab[1:]
Example #4
0
def test_calc_sphericals():
    """Test the calculation of spherical functions."""
    with open(PATH_TEST / 'Cd33Se33.xyz', 'r') as f:
        mol = parse_string_xyz(f.read())
    path_hdf5 = PATH_TEST / "Cd33Se33.hdf5"
    xs = number_spherical_functions_per_atom(mol, "cp2k", "DZVP-MOLOPT-SR-GTH",
                                             path_hdf5)

    expected = np.concatenate((np.repeat(25, 33), np.repeat(13, 33)))

    assert np.array_equal(xs, expected)
Example #5
0
def initialize(config: DictConfig) -> DictConfig:
    """Initialize all the data required to schedule the workflows.

    Returns
    -------
    DictConfig
        Input to run the workflow.

    """
    log_config(config)

    # Scratch folder
    scratch_path = create_path_option(config["scratch_path"])
    if scratch_path is None:
        scratch_path = Path(tempfile.gettempdir()) / \
            getpass.getuser() / config.project_name
        logger.warning(
            f"path to scratch was not defined, using: {scratch_path}")
    config['workdir'] = scratch_path

    # If the directory does not exist create it
    if not scratch_path.exists():
        scratch_path.mkdir(parents=True)

    # Touch HDF5 if it doesn't exists
    if not os.path.exists(config.path_hdf5):
        Path(config.path_hdf5).touch()

    # all_geometries type :: [String]
    geometries = split_file_geometries(config["path_traj_xyz"])
    config['geometries'] = geometries

    # Create a folder for each point the the dynamics
    enumerate_from = config["enumerate_from"]
    len_geometries = len(geometries)
    config["folders"] = create_point_folder(scratch_path, len_geometries,
                                            enumerate_from)

    config['calc_new_wf_guess_on_points'] = guesses_to_compute(
        config['calculate_guesses'], enumerate_from, len_geometries)

    # Generate a list of tuples containing the atomic label
    # and the coordinates to generate the primitive CGFs
    atoms = parse_string_xyz(geometries[0])
    if 'angstrom' in config["geometry_units"].lower():
        atoms = change_mol_units(atoms)

    # Save Basis to HDF5
    save_basis_to_hdf5(config)

    return config
def initialize(config: dict) -> dict:
    """
    Initialize all the data required to schedule the workflows associated with
    the nonadaibatic coupling
    """
    log_config(config)

    # Scratch folder
    scratch_path = config["scratch_path"]
    if scratch_path is None:
        scratch_path = join(tempfile.gettempdir(), getpass.getuser(),
                            config.project_name)
        logger.warning(
            f"path to scratch was not defined, using: {scratch_path}")
    config['workdir'] = scratch_path

    # If the directory does not exist create it
    if not os.path.exists(scratch_path):
        os.makedirs(scratch_path)

    # HDF5 path
    path_hdf5 = config["path_hdf5"]
    if path_hdf5 is None:
        path_hdf5 = join(scratch_path, 'quantum.hdf5')
        logger.warning(f"path to the HDF5 was not defined, using: {path_hdf5}")

    # all_geometries type :: [String]
    geometries = split_file_geometries(config["path_traj_xyz"])
    config['geometries'] = geometries

    # Create a folder for each point the the dynamics
    enumerate_from = config["enumerate_from"]
    len_geometries = len(geometries)
    config["folders"] = create_point_folder(scratch_path, len_geometries,
                                            enumerate_from)

    config['calc_new_wf_guess_on_points'] = guesses_to_compute(
        config['calculate_guesses'], enumerate_from, len_geometries)

    # Generate a list of tuples containing the atomic label
    # and the coordinates to generate
    # the primitive CGFs
    atoms = parse_string_xyz(geometries[0])
    if 'angstrom' in config["geometry_units"].lower():
        atoms = change_mol_units(atoms)

    # Save Basis to HDF5
    save_basis_to_hdf5(config)

    return config
def initialize(config: dict) -> dict:
    """
    Initialize all the data required to schedule the workflows associated with
    the nonadaibatic coupling
    """
    log_config(config)

    # Scratch folder
    scratch_path = config["scratch_path"]
    if scratch_path is None:
        scratch_path = join(tempfile.gettempdir(), getpass.getuser(), config.project_name)
        logger.warning("path to scratch was not defined, using: {}".format(scratch_path))
    config['workdir'] = scratch_path

    # If the directory does not exist create it
    if not os.path.exists(scratch_path):
        os.makedirs(scratch_path)

    # HDF5 path
    path_hdf5 = config["path_hdf5"]
    if path_hdf5 is None:
        path_hdf5 = join(scratch_path, 'quantum.hdf5')
        logger.warning("path to the HDF5 was not defined, using: {}".format(path_hdf5))

    # all_geometries type :: [String]
    geometries = split_file_geometries(config["path_traj_xyz"])
    config['geometries'] = geometries

    # Create a folder for each point the the dynamics
    enumerate_from = config["enumerate_from"]
    len_geometries = len(geometries)
    config["folders"] = create_point_folder(
        scratch_path, len_geometries, enumerate_from)

    config['calc_new_wf_guess_on_points'] = guesses_to_compute(
        config['calculate_guesses'], enumerate_from, len_geometries)

    # Generate a list of tuples containing the atomic label
    # and the coordinates to generate
    # the primitive CGFs
    atoms = parse_string_xyz(geometries[0])
    if 'angstrom' in config["geometry_units"].lower():
        atoms = change_mol_units(atoms)

    # Save Basis to HDF5
    save_basis_to_hdf5(config)

    return config
Example #8
0
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)
Example #9
0
def run_workflow_stddft(config: DictConfig) -> PromisedObject:
    """Compute the excited states using simplified TDDFT using `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 gather(results, energy_paths_hdf5)
def select_molecules(config: dict, i: int) -> tuple:
    """
    Select the pairs of molecules to compute the couplings.
    """
    k = 0 if config.overlaps_deph else i
    return tuple(parse_string_xyz(config.geometries[idx]) for idx in (k, i + 1))
def calculate_overlap(project_name: str,
                      path_hdf5: str,
                      dictCGFs: Dict,
                      geometries: List,
                      mo_paths_hdf5: List,
                      hdf5_trans_mtx: str,
                      enumerate_from: int,
                      overlaps_deph: bool,
                      nHOMO: int = None,
                      couplings_range: Tuple = None,
                      units: str = 'angstrom') -> List:
    """
    Calculate the Overlap matrices before computing the non-adiabatic
    coupling using 3 consecutive set of MOs in a molecular dynamic.

    :param path_hdf5: Path to the HDF5 file that contains the
    numerical results.
    :type path_hdf5: String
    :paramter dictCGFS: Dictionary from Atomic Label to basis set
    :type     dictCGFS: Dict String [CGF],
              CGF = ([Primitives], AngularMomentum),
              Primitive = (Coefficient, Exponent)
    :param geometries: list of molecular geometries
    :param mo_paths: Path to the MO coefficients and energies in the
    HDF5 file.
    :param hdf5_trans_mtx: path to the transformation matrix in the HDF5 file.
    :param enumerate_from: Number from where to start enumerating the folders
    create for each point in the MD
    :type enumerate_from: Int
    :param nHOMO: index of the H**O orbital in the HDF5
    :param couplings_range: range of Molecular orbitals used to compute the
    coupling.
    :returns: paths to the Overlap matrices inside the HDF5.
    """
    nPoints = len(geometries) - 1

    # Inplace scheduling of calculate_overlap function
    # Equivalent to add @schedule on top of the function
    schedule_overlaps = schedule(lazy_overlaps)

    # Compute the Overlaps
    paths_overlaps = []
    for i in range(nPoints):

        # Extract molecules to compute couplings
        if overlaps_deph:
            molecules = tuple(
                map(lambda idx: parse_string_xyz(geometries[idx]), [0, i + 1]))
        else:
            molecules = tuple(
                map(lambda idx: parse_string_xyz(geometries[idx]), [i, i + 1]))

        # If units are Angtrom convert then to a.u.
        if 'angstrom' in units.lower():
            molecules = tuple(map(change_mol_units, molecules))

        # Compute the coupling
        overlaps = schedule_overlaps(i,
                                     project_name,
                                     path_hdf5,
                                     dictCGFs,
                                     molecules,
                                     mo_paths_hdf5,
                                     hdf5_trans_mtx=hdf5_trans_mtx,
                                     enumerate_from=enumerate_from,
                                     nHOMO=nHOMO,
                                     couplings_range=couplings_range)

        paths_overlaps.append(overlaps)

    # Gather all the promised paths
    return gather(*paths_overlaps)
def initialize(
        project_name: str=None, path_traj_xyz: str=None, basis_name: str=None,
        enumerate_from: int=0, calculate_guesses: int='first',
        path_hdf5: str=None, scratch_path: str=None, path_basis: str=None,
        path_potential: str=None, geometry_units: str='angstrom', **kwargs) -> Dict:
    """
    Initialize all the data required to schedule the workflows associated with
    the nonadaibatic coupling
    """
    # 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')
    # User variables
    username = getpass.getuser()

    # Scratch
    if scratch_path is None:
        scratch_path = join('/tmp', username, project_name)
        logger.warning("path to scratch was not defined, using: {}".format(scratch_path))

    # If the directory does not exist create it
    if not os.path.exists(scratch_path):
        os.makedirs(scratch_path)

    # Cp2k configuration files
    cp2k_config = {"basis": path_basis, "potential": path_potential}

    # HDF5 path
    if path_hdf5 is None:
        path_hdf5 = join(scratch_path, 'quantum.hdf5')
        logger.warning("path to the HDF5 was not defined, using: {}".format(path_hdf5))

    # all_geometries type :: [String]
    geometries = split_file_geometries(path_traj_xyz)

    # Create a folder for each point the the dynamics
    traj_folders = create_point_folder(scratch_path, len(geometries),
                                       enumerate_from)
    if calculate_guesses is None:
        points_guess = []
    elif calculate_guesses.lower() in 'first':
        # Calculate new Guess in the first geometry
        points_guess = [enumerate_from]
        msg = "An initial Calculation will be computed as guess for the wave function"
        logger.info(msg)
    else:
        # Calculate new Guess in each geometry
        points_guess = [enumerate_from + i for i in range(len(geometries))]
        msg = "A guess calculation will be done for each geometry"
        logger.info(msg)

    # Generate a list of tuples containing the atomic label
    # and the coordinates to generate
    # the primitive CGFs
    atoms = parse_string_xyz(geometries[0])
    if 'angstrom' in geometry_units.lower():
        atoms = change_mol_units(atoms)

    # CGFs per element
    dictCGFs = create_dict_CGFs(path_hdf5, basis_name, atoms,
                                package_config=cp2k_config)

    # Calculcate the matrix to transform from cartesian to spherical
    # representation of the overlap matrix
    hdf5_trans_mtx = store_transf_matrix(
        path_hdf5, atoms, dictCGFs, basis_name, project_name)

    d = {'package_config': cp2k_config, 'path_hdf5': path_hdf5,
         'calc_new_wf_guess_on_points': points_guess,
         'geometries': geometries, 'enumerate_from': enumerate_from,
         'dictCGFs': dictCGFs, 'work_dir': scratch_path,
         'folders': traj_folders, 'basis_name': basis_name,
         'hdf5_trans_mtx': hdf5_trans_mtx}

    return d
Example #13
0
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)
Example #14
0
def select_molecules(config: DictConfig, i: int) -> Tuple[MolXYZ, MolXYZ]:
    """Select the pairs of molecules to compute the couplings."""
    k = 0 if config.overlaps_deph else i
    return tuple(
        parse_string_xyz(config.geometries[idx]) for idx in (k, i + 1))