Repository: Quantum-Accelerators/quacc (python)

Issue:
MP GGA VASP workflows resort and cause problems with per-atom defaults like LDA+U
### Details about the quacc environment

- **quacc version**: latest
- **Python version**: 3.11


### What is the issue?

The ASE Vasp calculator resorts the atoms when writing POSCARs, which is a problem when any specific script (like the scripts to guess MP-like LDA+U or magmom settings). 

This is a problem because using the mp_gga_relax_job will silently cause problems if the input atoms object does not have the same order as what the Vasp calculator will plan to do. See the script below for the minimum reproducible example. Running the MP GGA relax jobs on atoms objects with random sorting and LDAU settings will also have problems, but the calculator won't actually throw errors as VASP will just ignore more LDA+U parameters than the number of species. 

### How can we easily reproduce the issue?

```python
from quacc.calculators.vasp import Vasp
from ase import Atoms
atoms = Atoms('HOH', cell=[10,10,10])
atoms.set_calculator(Vasp(atoms, ldau=True, ldauu=[1,2,3]))
atoms.calc.write_input(atoms)
```

Notice that in this example, INCAR has LDAUU=[1,2,3], while the POSCAR has been resorted to not be HOH but instead be H2O, so that there are only two species. 

Pull request:
Fix sorting with MP sets
Closes #2583, I think.

Patch:
diff --git a/src/quacc/calculators/vasp/params.py b/src/quacc/calculators/vasp/params.py
index 369754f9..de3c5799 100644
--- a/src/quacc/calculators/vasp/params.py
+++ b/src/quacc/calculators/vasp/params.py
@@ -398,8 +398,10 @@ class MPtoASEConverter:
         if atoms is None and prev_dir is None:
             raise ValueError("Either atoms or prev_dir must be provided.")
         self.atoms = atoms
-        self.structure = AseAtomsAdaptor.get_structure(atoms) if self.atoms else None
         self.prev_dir = prev_dir
+        if self.atoms:
+            self.ase_sort, self.ase_resort = Vasp_()._make_sort(self.atoms)
+            self.structure = AseAtomsAdaptor.get_structure(self.atoms[self.ase_sort])
 
     def convert_dict_set(self, dict_set: DictSet) -> dict:
         """


Test patch:
diff --git a/tests/core/calculators/vasp/test_vasp.py b/tests/core/calculators/vasp/test_vasp.py
index 15d77f1f..f0d090dc 100644
--- a/tests/core/calculators/vasp/test_vasp.py
+++ b/tests/core/calculators/vasp/test_vasp.py
@@ -8,6 +8,7 @@ from shutil import which
 
 import numpy as np
 import pytest
+from ase.atoms import Atoms
 from ase.build import bulk
 from ase.calculators.singlepoint import SinglePointDFTCalculator
 from ase.calculators.vasp import Vasp as Vasp_
@@ -909,6 +910,19 @@ def test_pmg_input_set2():
     }
 
 
+def test_ldau_mp():
+    atoms = Atoms("HOHMn")
+    atoms.center(vacuum=10)
+    atoms.pbc = True
+    atoms[0].position += 0.1
+    atoms[1].position -= 0.1
+    parameters = MPtoASEConverter(atoms=atoms).convert_dict_set(MPRelaxSet)
+    assert len(parameters["ldauu"]) == 3
+    assert parameters["ldauu"] == [0, 0, 3.9]
+    assert len(parameters["magmom"]) == 4
+    assert parameters["magmom"] == [0.6, 0.6, 0.6, 5.0]
+
+
 @pytest.mark.skipif(which(get_settings().VASP_CMD), reason="VASP is installed")
 def test_run(monkeypatch, tmp_path):
     monkeypatch.chdir(tmp_path)