Ejemplo n.º 1
0
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
Ejemplo n.º 5
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)
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 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
Ejemplo n.º 9
0
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)
Ejemplo n.º 10
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)
Ejemplo n.º 11
0
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)
Ejemplo n.º 12
0
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