# Workflow with PLAMS

# A couple of useful function for extracting results:

@add_to_class(AMSResults)
def get_excitations(results):
    """Returns excitation energies (in eV) and oscillator strenghts (in Debye)."""
    if results.job.ok():
        exci_energies_au = results.readrkf('Excitations SS A','excenergies',file='engine')
        oscillator_str_au = results.readrkf('Excitations SS A','oscillator strengths',file='engine')
        # The results are stored in atomic units. Convert them to more convenient units:
        exci_energies = Units.convert(exci_energies_au,'au','eV')
        oscillator_str = Units.convert(oscillator_str_au,'au','Debye')
        return exci_energies, oscillator_str
    else:
        return [],[]


@add_to_class(AMSResults)
def has_good_excitations(results, min_energy, max_energy, oscillator_str_threshold=1E-4):
    """Returns True if there is at least one excitation with non-vanishing oscillator strenght
       in the energy range [min_energy, max_energy]. Unit for min_energy and max energy: eV. """
    exci_energies, oscillator_str = results.get_excitations()
    for e,o in zip(exci_energies, oscillator_str):
        if e > min_energy and e < max_energy and o > oscillator_str_threshold:
            return True
    return False


# Calculation settings:
# =====================

# Settings for geometry optimization with the AMS driver:
go_sett = Settings()
go_sett.input.ams.Task = 'GeometryOptimization'
go_sett.input.ams.GeometryOptimization.Convergence.Gradients = 1.0e-4

# Settings for single point calculation with the AMS driver
sp_sett = Settings()
sp_sett.input.ams.Task = 'SinglePoint'

# Settings for the DFTB engine (including excitations)
dftb_sett = Settings()
dftb_sett.input.dftb.Model = 'SCC-DFTB'
dftb_sett.input.dftb.ResourcesDir = 'QUASINANO2015'
dftb_sett.input.dftb.Properties.Excitations.TDDFTB.calc = 'singlet'
dftb_sett.input.dftb.Properties.Excitations.TDDFTB.lowest = 10
dftb_sett.input.dftb.Occupation.Temperature = 5.0

# Settings for the geometry optimization with the ADF engine
adf_sett = Settings()
adf_sett.input.adf.Basis.Type = 'DZP'
adf_sett.input.adf.NumericalQuality = 'Basic'

# Settings for the excitation calculation using the ADF engine
adf_exci_sett = Settings()
adf_exci_sett.input.adf.Basis.Type = 'TZP'
adf_exci_sett.input.adf.XC.GGA = 'PBE'
adf_exci_sett.input.adf.NumericalQuality = 'Basic'
adf_exci_sett.input.adf.Symmetry = 'NoSym'
adf_exci_sett.input.adf.Excitations.lowest = 10
adf_exci_sett.input.adf.Excitations.OnlySing = ''


# Import all xyz files in the folder 'molecules'

molecules = read_molecules('molecules')


print("Step 1: prescreening with DFTB")
print("==============================")

promising_molecules = {}

for name, mol in molecules.items():
  dftb_job = AMSJob(name='DFTB_'+name, molecule=mol, settings=go_sett+dftb_sett)
  dftb_job.run()

  if dftb_job.results.has_good_excitations(1,6):
    promising_molecules[name] = dftb_job.results.get_main_molecule()

print(f"Found {len(promising_molecules)} promising molecules with DFTB")


print("Step 2: Optimization and excitations calculation with ADF")
print("=========================================================")

for name, mol in promising_molecules.items():
  adf_go_job = AMSJob(name='ADF_GO_'+name, molecule=mol, settings=go_sett+adf_sett)
  adf_go_job.run()

  optimized_mol = adf_go_job.results.get_main_molecule()

  adf_exci_job = AMSJob(name='ADF_exci_'+name, molecule=optimized_mol, 
                        settings=sp_sett+adf_exci_sett)
  adf_exci_job.run()

  if adf_exci_job.results.has_good_excitations(2,4):
    print(f"Molecule {name} has excitation(s) satysfying our criteria!")
    print(optimized_mol)
    exci_energies, oscillator_str = adf_exci_job.results.get_excitations()
    print("Excitation energy [eV], oscillator strength:")
    for e,o in zip(exci_energies, oscillator_str):
        print(f"{e:8.4f}, {o:8.4f}")
