Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ASE support for net charge #58

Open
bieniekmateusz opened this issue Sep 8, 2021 · 10 comments
Open

ASE support for net charge #58

bieniekmateusz opened this issue Sep 8, 2021 · 10 comments
Labels
ase Related to the atomic simulation environment integration

Comments

@bieniekmateusz
Copy link

Describe the bug

Directly using XTB-python interface the charge= parameter can be passed, but employing the same approach with ASE/XTB, charge doesn't get propagated

Ie, this works for charge=0 and charge=1 and changes energy accordingly

calc = Calculator(Param.GFN2xTB, mol.get_atomic_numbers(), mol.get_scaled_positions(), charge=1)
res = calc.singlepoint()
print('energy', res.get_energy())

However, this doesn't remember the charge and always sets charge to 0:

mol2 = read('qca_seed10.xyz') 
mol2.set_calculator(XTB(method="GFN2-xTB", charge=1))
print('pe', mol2.get_potential_energy())

To Reproduce

unzip the file and run the script
f.zip

  1. xtb.version is 20.1
  2. output:
direct calc energy 71.23435304058115
direct calc energy 71.54337595108338
energy before conversion -63.89323100198211
ase/xtb pe -1738.623373139004
energy before conversion -63.89323100198211
ase/xtb pe -1738.623373139004

Expected behaviour

charge affecting the final energy when ase/xtb is used

The issue seems to be in the XTB class :
_charge = self.atoms.get_initial_charges().sum()

Thanks!

@bieniekmateusz bieniekmateusz added the unconfirmed This report has not yet been confirmed by the developers label Sep 8, 2021
@awvwgk
Copy link
Member

awvwgk commented Sep 8, 2021

ASE is using an array of initial_charges which is part of the Atoms object rather than the Calculator object, therefore the XTB ASE calculator does not support setting a charge but determines it from the initial_charges property of the Atoms object it is working with.

@awvwgk awvwgk added ase Related to the atomic simulation environment integration and removed unconfirmed This report has not yet been confirmed by the developers labels Sep 8, 2021
@bieniekmateusz
Copy link
Author

Thanks for having a look @awvwgk . I know what you mean, I am still worried about the inconsistency. Also, as I am using a .xyz file without the charges, this leaves me with no room to correct that with ase/xtb-python interface. Thanks!

@awvwgk
Copy link
Member

awvwgk commented Sep 9, 2021

My point here is similar to the argument in #42 and #47 for the multiplicity. Since ASE provides a canonical way to solve this issue, the XTB calculator will not implement something to bypass this mechanism.

Keep in mind that in ASE you can actually change the charge of your system after creating the calculator, the calculator must adjust to this change correctly, by adding this property to the calculator it becomes more difficult to keep the information in sync with the geometry used.

@bieniekmateusz
Copy link
Author

I must have missed the canonical way that you're mentioning, would you mind pointing me towards? I naively assumed that the XTB calculator would a similar charge parameter to the example case.

Thanks

@awvwgk
Copy link
Member

awvwgk commented Sep 9, 2021

Have a look at the Atoms object constructor which supports:

charges: list of float
    Initial atomic charges.

And set_initial_charges as way to modify the system charge.

@aizvorski
Copy link

I have been working with other ase calculators (eg psi4) and can confirm that it is possible to have a net charge for the whole system without having the initial_charges array set. I don't know too much about how this works under the hood, but this is a minimal example to reproduce

atoms = ase.io.read('test.sdf')
calc = ase.calculators.psi4.Psi4(atoms = atoms,  charge = -1, multiplicity = 1)
atoms.calc = calc
print(atoms.get_initial_charges())  # this is all zeros
print(atoms.get_potential_energy()) # this works correctly and uses the charge passed in

It would be cool is the xtb calculator worked the same way

@awvwgk
Copy link
Member

awvwgk commented Sep 14, 2021

I asked in the ASE matrix channel and got the following answer:

Total charge and multiplicity are not normalized in any way. So if your code accepts charge=5 in the input file, then the calculator would presumably accept the same thing.
(atoms.get_initial_charges() exists, but its purpose is more like a help for wavefunction/orbital initialization, mostly for the benefit of GPAW, rather than a way to actually control the total charge.)

I'm a bit unhappy about the lack of standardization here, since this means we have to implement our own caching logic for the total charge and multiplicity for the XTB ASE calculator.

@awvwgk
Copy link
Member

awvwgk commented Sep 14, 2021

For backwards compatibility we should keep the possibility to read from initial_charges but I'm open to accept a patch to implement an overwrite with charge and multiplicity parameters for the XTB ASE calculator (no hacking in Fortran or C involved, only Python needed).

@Shunyang2018
Copy link

Shunyang2018 commented May 4, 2022

Thank you all for the discussion! I also try to get this charge function recently.

  1. I pass the ASE Atoms object to xtb calculator to bypass the API:
from xtb.interface import Molecule,Calculator, Param, Environment
calc = Calculator(Param.IPEAxTB, mol.arrays['numbers'],mol.arrays['positions'],charge=charge,uhf=spin)
calc.set_output('log.txt')
res =  calc.singlepoint().get_energy()
  1. can we just read the charge from ase-xtb api rather than try to modify the initial charges array?
    instead of:
    _charge = self.atoms.get_initial_charges().sum()

we can use similar codes in the psi4 API:

# https://wiki.fysik.dtu.dk/ase/_modules/ase/calculators/psi4.html#Psi4

charge = self.parameters.get('charge')
mult = self.parameters.get('multiplicity')
  1. we can use Atoms.info to save the spin and charge information
    here is an example output:
xyz.info = {'charge':1.0,'spin':0.0}

xyz.info
Out[37]: {'charge': 1.0, 'spin': 0.0}

now, you can use

_charge = self.atoms.info['charge']

Hope those can help! It would be cool to have this function!

@njzjz
Copy link

njzjz commented May 22, 2023

A simple hack to assign net charge for XTB:

import numpy as np
from xtb.ase.calculator import XTB as OldXTB


class XTB(OldXTB):
    """ASE calculator for XTB with net charge."""

    def __init__(self, *args, charge=0, **kwargs):
        self.charge = charge
        super().__init__(*args, **kwargs)

    def _create_api_calculator(self):
        initial_charges = np.zeros(len(self.atoms))
        initial_charges[0] = self.charge
        self.atoms.set_initial_charges(initial_charges)
        return super()._create_api_calculator()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ase Related to the atomic simulation environment integration
Projects
None yet
Development

No branches or pull requests

5 participants