Repository: duartegroup/autodE
Pull request #280 (2023-07-08 13:45:00)

DHS code improvements
- Changes trust radius for optimiser to 0.1 Angstrom (since default step size for DHS is also 0.1 angstrom, anything larger does not make sense)
- Replaces `left_coord` and `right_coord` properties of the image pair object with `left_coords` and `right_coords` to match with other parts of autodE.


***

## Checklist

* [x] The changes include an associated explanation of how/why
* [x] Test pass
* [x] Documentation has been updated
* [x] Changelog has been updated


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/duartegroup/autodE/pull/280?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=duartegroup) Report
> Merging [#280](https://app.codecov.io/gh/duartegroup/autodE/pull/280?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=duartegroup) (3e50e73) into [v1.4.0](https://app.codecov.io/gh/duartegroup/autodE/commit/e05752d3a7515e8c9c3eebf8d89aed98f8c4683e?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=duartegroup) (e05752d) will **decrease** coverage by `0.14%`.
> The diff coverage is `97.96%`.

```diff
@@            Coverage Diff             @@
##           v1.4.0     #280      +/-   ##
==========================================
- Coverage   97.32%   97.19%   -0.14%     
==========================================
  Files         203      201       -2     
  Lines       22055    22370     +315     
==========================================
+ Hits        21466    21743     +277     
- Misses        589      627      +38     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `97.19% <97.96%> (-0.14%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=duartegroup#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://app.codecov.io/gh/duartegroup/autodE/pull/280?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=duartegroup) | Coverage Δ | |
|---|---|---|
| [autode/transition\_states/templates.py](https://app.codecov.io/gh/duartegroup/autodE/pull/280?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=duartegroup#diff-YXV0b2RlL3RyYW5zaXRpb25fc3RhdGVzL3RlbXBsYXRlcy5weQ==) | `93.85% <ø> (ø)` | |
| [autode/wrappers/keywords/basis\_sets.py](https://app.codecov.io/gh/duartegroup/autodE/pull/280?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=duartegroup#diff-YXV0b2RlL3dyYXBwZXJzL2tleXdvcmRzL2Jhc2lzX3NldHMucHk=) | `100.00% <ø> (ø)` | |
| [tests/test\_pes/test\_rs.py](https://app.codecov.io/gh/duartegroup/autodE/pull/280?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=duartegroup#diff-dGVzdHMvdGVzdF9wZXMvdGVzdF9ycy5weQ==) | `97.19% <ø> (ø)` | |
| [autode/transition\_states/base.py](https://app.codecov.io/gh/duartegroup/autodE/pull/280?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=duartegroup#diff-YXV0b2RlL3RyYW5zaXRpb25fc3RhdGVzL2Jhc2UucHk=) | `73.07% <76.92%> (+1.07%)` | :arrow_up: |
| [autode/path/adaptive.py](https://app.codecov.io/gh/duartegroup/autodE/pull/280?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=duartegroup#diff-YXV0b2RlL3BhdGgvYWRhcHRpdmUucHk=) | `91.48% <77.77%> (-1.11%)` | :arrow_down: |
| [autode/opt/coordinates/internals.py](https://app.codecov.io/gh/duartegroup/autodE/pull/280?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=duartegroup#diff-YXV0b2RlL29wdC9jb29yZGluYXRlcy9pbnRlcm5hbHMucHk=) | `97.22% <83.33%> (-0.88%)` | :arrow_down: |
| [autode/transition\_states/transition\_state.py](https://app.codecov.io/gh/duartegroup/autodE/pull/280?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=duartegroup#diff-YXV0b2RlL3RyYW5zaXRpb25fc3RhdGVzL3RyYW5zaXRpb25fc3RhdGUucHk=) | `75.00% <86.95%> (+0.32%)` | :arrow_up: |
| [autode/opt/optimisers/trust\_region.py](https://app.codecov.io/gh/duartegroup/autodE/pull/280?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=duartegroup#diff-YXV0b2RlL29wdC9vcHRpbWlzZXJzL3RydXN0X3JlZ2lvbi5weQ==) | `94.28% <87.50%> (-0.78%)` | :arrow_down: |
| [autode/pes/unrelaxed.py](https://app.codecov.io/gh/duartegroup/autodE/pull/280?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=duartegroup#diff-YXV0b2RlL3Blcy91bnJlbGF4ZWQucHk=) | `94.44% <90.90%> (+8.73%)` | :arrow_up: |
| [autode/reactions/reaction.py](https://app.codecov.io/gh/duartegroup/autodE/pull/280?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=duartegroup#diff-YXV0b2RlL3JlYWN0aW9ucy9yZWFjdGlvbi5weQ==) | `97.10% <91.30%> (-0.51%)` | :arrow_down: |
| ... and [96 more](https://app.codecov.io/gh/duartegroup/autodE/pull/280?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=duartegroup) | |

... and [4 files with indirect coverage changes](https://app.codecov.io/gh/duartegroup/autodE/pull/280/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=duartegroup)


shoubhikraj: @t-young31 Feel free to merge the PR

Patch:
diff --git a/autode/bracket/base.py b/autode/bracket/base.py
index 0a4744b..f045f86 100644
--- a/autode/bracket/base.py
+++ b/autode/bracket/base.py
@@ -114,8 +114,8 @@ class BaseBracketMethod(ABC):
         logger.info(
             f"{self._name} Macro-iteration #{self._macro_iter}: "
             f"Distance = {self.imgpair.dist:.4f}; Energy (initial species) = "
-            f"{self.imgpair.left_coord.e:.6f}; Energy (final species) = "
-            f"{self.imgpair.right_coord.e:.6f}"
+            f"{self.imgpair.left_coords.e:.6f}; Energy (final species) = "
+            f"{self.imgpair.right_coords.e:.6f}"
         )
 
     @property
diff --git a/autode/bracket/dhs.py b/autode/bracket/dhs.py
index 6e5393f..771f458 100644
--- a/autode/bracket/dhs.py
+++ b/autode/bracket/dhs.py
@@ -85,7 +85,7 @@ class DistanceConstrainedOptimiser(RFOptimiser):
     def __init__(
         self,
         pivot_point: Optional[CartesianCoordinates],
-        init_trust: float = 0.2,
+        init_trust: float = 0.1,
         line_search: bool = True,
         angle_thresh: Angle = Angle(5, units="deg"),
         old_coords_read_hess: Optional[CartesianCoordinates] = None,
@@ -143,8 +143,7 @@ class DistanceConstrainedOptimiser(RFOptimiser):
             )
             sub_opt._coords = self._old_coords
             sub_opt._coords = self._coords
-            new_h = sub_opt._updated_h()
-            self._coords.update_h_from_cart_h(new_h)
+            self._coords.h = np.array(sub_opt._updated_h())
         else:
             # no hessian available, use low level method
             self._coords.update_h_from_cart_h(self._low_level_cart_hessian)
@@ -412,9 +411,9 @@ class DHSImagePair(EuclideanImagePair):
     def get_coord_by_side(self, side: ImageSide) -> CartesianCoordinates:
         """For external usage, supplies the coordinate object by side"""
         if side == ImageSide.left:
-            return self.left_coord
+            return self.left_coords
         elif side == ImageSide.right:
-            return self.right_coord
+            return self.right_coords
         else:
             raise ValueError
 
@@ -429,9 +428,9 @@ class DHSImagePair(EuclideanImagePair):
             side (ImageSide): left or right
         """
         if side == ImageSide.left:
-            self.left_coord = new_coord
+            self.left_coords = new_coord
         elif side == ImageSide.right:
-            self.right_coord = new_coord
+            self.right_coords = new_coord
         else:
             raise ValueError
         return None
@@ -560,14 +559,14 @@ class DHS(BaseBracketMethod):
         steps in the distance-constrained optimiser, to return to the MEP
         """
         assert self._method is not None, "Must have a set method"
-        assert self.imgpair.left_coord.e and self.imgpair.right_coord.e
+        assert self.imgpair.left_coords.e and self.imgpair.right_coords.e
 
-        if self.imgpair.left_coord.e < self.imgpair.right_coord.e:
+        if self.imgpair.left_coords.e < self.imgpair.right_coords.e:
             side = ImageSide.left
-            pivot = self.imgpair.right_coord
+            pivot = self.imgpair.right_coords
         else:
             side = ImageSide.right
-            pivot = self.imgpair.left_coord
+            pivot = self.imgpair.left_coords
 
         old_coords: Any = self.imgpair.get_coord_by_side(side)
         old_coords = old_coords if old_coords.h is not None else None
diff --git a/autode/bracket/imagepair.py b/autode/bracket/imagepair.py
index 2447095..a7245de 100644
--- a/autode/bracket/imagepair.py
+++ b/autode/bracket/imagepair.py
@@ -128,10 +128,10 @@ class BaseImagePair(ABC):
         self._left_history = OptimiserHistory()
         self._right_history = OptimiserHistory()
         # push the first coordinates into history
-        self.left_coord = CartesianCoordinates(
+        self.left_coords = CartesianCoordinates(
             self._left_image.coordinates.to("ang")
         )
-        self.right_coord = CartesianCoordinates(
+        self.right_coords = CartesianCoordinates(
             self._right_image.coordinates.to("ang")
         )
 
@@ -252,12 +252,12 @@ class BaseImagePair(ABC):
         return len(self._left_history) + len(self._right_history) - 2
 
     @property
-    def left_coord(self) -> CartesianCoordinates:
+    def left_coords(self) -> CartesianCoordinates:
         """The coordinates of the left image"""
         return self._left_history[-1]
 
-    @left_coord.setter
-    def left_coord(self, value: CartesianCoordinates):
+    @left_coords.setter
+    def left_coords(self, value: CartesianCoordinates):
         """
         Sets the coordinates of the left image, also updates
         the coordinates of the species
@@ -277,18 +277,18 @@ class BaseImagePair(ABC):
         else:
             raise TypeError
 
-        self._left_image.coordinates = self.left_coord
+        self._left_image.coordinates = self.left_coords
         if _flush_old_hessians:
             if len(self._left_history) >= 3:
                 self._left_history[-3].h = None
 
     @property
-    def right_coord(self) -> CartesianCoordinates:
+    def right_coords(self) -> CartesianCoordinates:
         """The coordinates of the right image"""
         return self._right_history[-1]
 
-    @right_coord.setter
-    def right_coord(self, value: CartesianCoordinates):
+    @right_coords.setter
+    def right_coords(self, value: CartesianCoordinates):
         """
         Sets the coordinates of the right image, also updates
         the coordinates of the species
@@ -308,7 +308,7 @@ class BaseImagePair(ABC):
         else:
             raise TypeError
 
-        self._right_image.coordinates = self.right_coord
+        self._right_image.coordinates = self.right_coords
         if _flush_old_hessians:
             if len(self._right_history) >= 3:
                 self._right_history[-3].h = None
@@ -353,10 +353,10 @@ class BaseImagePair(ABC):
             ]
             left_engrad, right_engrad = [job.result() for job in jobs]
 
-        self.left_coord.e = left_engrad[0]
-        self.left_coord.update_g_from_cart_g(left_engrad[1])
-        self.right_coord.e = right_engrad[0]
-        self.right_coord.update_g_from_cart_g(right_engrad[1])
+        self.left_coords.e = left_engrad[0]
+        self.left_coords.update_g_from_cart_g(left_engrad[1])
+        self.right_coords.e = right_engrad[0]
+        self.right_coords.update_g_from_cart_g(right_engrad[1])
         return None
 
     def update_both_img_hessian_by_calc(self):
@@ -379,8 +379,8 @@ class BaseImagePair(ABC):
             ]
             left_hess, right_hess = [job.result() for job in jobs]
 
-        self.left_coord.update_h_from_cart_h(left_hess)
-        self.right_coord.update_h_from_cart_h(right_hess)
+        self.left_coords.update_h_from_cart_h(left_hess)
+        self.right_coords.update_h_from_cart_h(right_hess)
         return None
 
     def update_both_img_hessian_by_formula(self):
@@ -398,7 +398,7 @@ class BaseImagePair(ABC):
                 y=coords_l.g - coords_k.g,
             )
 
-            coords_l.update_h_from_cart_h(updater.updated_h)
+            coords_l.h = np.array(updater.updated_h)
 
         return None
 
@@ -428,7 +428,7 @@ class EuclideanImagePair(BaseImagePair, ABC):
         go from right to left image (i.e. right -> left)
         """
         return np.array(
-            self.left_coord.to("cart") - self.right_coord.to("cart")
+            self.left_coords.to("cart") - self.right_coords.to("cart")
         )
 
     @property
@@ -454,7 +454,16 @@ class EuclideanImagePair(BaseImagePair, ABC):
         Y. Liu, H. Qui, M. Lei, J. Chem. Theory. Comput., 2023
         https://doi.org/10.1021/acs.jctc.3c00151
         """
-        assert self.left_coord.g is not None and self.right_coord.g is not None
+        assert (
+            self.left_coords.g is not None and self.right_coords.g is not None
+        )
+
+        def cos_angle(vec1, vec2) -> float:
+            """Returns the cos(theta) between two vectors (1D arrays)"""
+            dot = float(np.dot(vec1, vec2)) / (
+                np.linalg.norm(vec1) * np.linalg.norm(vec2)
+            )
+            return dot
 
         # NOTE: The angle between the force vector on right image
         # and the distance vector must be more than 90 degrees. Similarly
@@ -462,8 +471,8 @@ class EuclideanImagePair(BaseImagePair, ABC):
         # that the parallel component of the forces on each image are
         # pointing away from each other. (force is negative gradient).
 
-        left_cos_theta = np.dot(-self.left_coord.g, self.dist_vec)
-        right_cos_theta = np.dot(-self.right_coord.g, self.dist_vec)
+        left_cos_theta = cos_angle(-self.left_coords.g, self.dist_vec)
+        right_cos_theta = cos_angle(-self.right_coords.g, self.dist_vec)
 
         assert -1.0 < left_cos_theta < 1.0
         assert -1.0 < right_cos_theta < 1.0
diff --git a/autode/utils.py b/autode/utils.py
index c1a16b1..9eb2607 100644
--- a/autode/utils.py
+++ b/autode/utils.py
@@ -1,4 +1,5 @@
 import os
+import sys
 import platform
 import shutil
 import copy
@@ -82,7 +83,7 @@ def _copy_into_current_config(
 
 def get_total_memory() -> int:
     """Returns total amount of physical memory available in bytes"""
-    if platform.system() == "Windows":
+    if sys.platform == "win32" or platform.system() == "Windows":
         return _get_total_memory_on_windows()
     else:
         return os.sysconf("SC_PAGE_SIZE") * os.sysconf("SC_PHYS_PAGES")


Test patch:
diff --git a/tests/test_bracket/test_dhs.py b/tests/test_bracket/test_dhs.py
index 6900d2d..cde924f 100644
--- a/tests/test_bracket/test_dhs.py
+++ b/tests/test_bracket/test_dhs.py
@@ -102,13 +102,13 @@ def test_dhs_image_pair():
     mol2 = mol1.new_species()
     imgpair = DHSImagePair(mol1, mol2)
 
-    coords = imgpair.left_coord + 0.1
+    coords = imgpair.left_coords + 0.1
 
     # check the functions that get one side
     with pytest.raises(ValueError):
         imgpair.put_coord_by_side(coords, 2)
 
-    imgpair.left_coord = coords
+    imgpair.left_coords = coords
     with pytest.raises(ValueError):
         step = imgpair.get_last_step_by_side(2)
 
@@ -124,20 +124,20 @@ def test_dhs_image_pair_ts_guess(caplog):
     mol1 = Molecule(smiles="CCO")
     imgpair = DHSImagePair(mol1, mol1.copy())
 
-    imgpair.left_coord.e = PotentialEnergy(-0.144, "Ha")
+    imgpair.left_coords.e = PotentialEnergy(-0.144, "Ha")
 
     with caplog.at_level("ERROR"):
         peak = imgpair.ts_guess
     assert "Energy values are missing in the trajectory" in caplog.text
 
-    imgpair.right_coord.e = PotentialEnergy(-0.145, "Ha")
-    imgpair.left_coord.g = np.ones_like(imgpair.left_coord)  # spoof gradient
+    imgpair.right_coords.e = PotentialEnergy(-0.145, "Ha")
+    imgpair.left_coords.g = np.ones_like(imgpair.left_coords)  # spoof gradient
     peak = imgpair.ts_guess
     assert peak is not None
 
-    assert np.allclose(peak.coordinates.flatten(), imgpair.left_coord)
+    assert np.allclose(peak.coordinates.flatten(), imgpair.left_coords)
     assert np.isclose(peak.energy, -0.144)
-    assert np.allclose(peak.gradient.flatten(), imgpair.left_coord.g)
+    assert np.allclose(peak.gradient.flatten(), imgpair.left_coords.g)
 
 
 @requires_working_xtb_install
@@ -160,10 +160,10 @@ def test_dhs_single_step():
     dhs._initialise_run()
 
     imgpair = dhs.imgpair
-    assert imgpair.left_coord.e is not None
-    assert imgpair.right_coord.e is not None
+    assert imgpair.left_coords.e is not None
+    assert imgpair.right_coords.e is not None
     old_dist = imgpair.dist
-    assert imgpair.left_coord.e > imgpair.right_coord.e
+    assert imgpair.left_coords.e > imgpair.right_coords.e
 
     # take a single step
     dhs._step()
@@ -203,11 +203,11 @@ def test_dhs_gs_single_step(caplog):
     right_pred = dhs_gs._get_dhs_step(ImageSide.right)  # 50% DHS + 50% GS
 
     imgpair = dhs_gs.imgpair
-    assert imgpair.left_coord.e > imgpair.right_coord.e
+    assert imgpair.left_coords.e > imgpair.right_coords.e
     assert len(imgpair._left_history) == 1
     assert len(imgpair._right_history) == 2
 
-    hybrid_step = right_pred - imgpair.right_coord
+    hybrid_step = right_pred - imgpair.right_coords
     dhs_step = imgpair.dist_vec
     dhs_step = dhs_step / np.linalg.norm(dhs_step) * step_size
     gs_step = imgpair._right_history[-1] - imgpair._right_history[-2]
diff --git a/tests/test_bracket/test_imagepair.py b/tests/test_bracket/test_imagepair.py
index 710654d..dd12cff 100644
--- a/tests/test_bracket/test_imagepair.py
+++ b/tests/test_bracket/test_imagepair.py
@@ -35,20 +35,20 @@ def test_imagpair_coordinates():
     # it's an array of right shape
     coord_array = np.array(mol.coordinates.flatten())
     with pytest.raises(TypeError):
-        imgpair.left_coord = coord_array
+        imgpair.left_coords = coord_array
     with pytest.raises(TypeError):
-        imgpair.right_coord = coord_array
+        imgpair.right_coords = coord_array
 
     coords = CartesianCoordinates(mol.coordinates)
     coords += 0.1
     # no error if Cartesian coordinates
-    imgpair.left_coord = coords
+    imgpair.left_coords = coords
     coords = CartesianCoordinates(np.arange(mol.n_atoms + 1))
     num = mol.n_atoms * 3
     with pytest.raises(ValueError, match=f"Must have {num} entries"):
-        imgpair.left_coord = coords
+        imgpair.left_coords = coords
     with pytest.raises(ValueError, match=f"Must have {num} entries"):
-        imgpair.right_coord = coords
+        imgpair.right_coords = coords
 
 
 def test_imagepair_method_typing():
@@ -148,15 +148,15 @@ def test_imgpair_energy_plotting(caplog):
     mol2 = Molecule(smiles="CCO")
 
     imgpair = NullImagePair(mol1, mol2)
-    imgpair.left_coord.e = Energy(-3.14)
-    imgpair.right_coord.e = Energy(-2.87)
+    imgpair.left_coords.e = Energy(-3.14)
+    imgpair.right_coords.e = Energy(-2.87)
     # spoof new coordinates
-    imgpair.left_coord = imgpair.left_coord * 0.99
-    imgpair.right_coord = imgpair.right_coord * 0.99
-    imgpair.left_coord.e = Energy(-1.99)
-    imgpair.right_coord.e = Energy(-2.15)
+    imgpair.left_coords = imgpair.left_coords * 0.99
+    imgpair.right_coords = imgpair.right_coords * 0.99
+    imgpair.left_coords.e = Energy(-1.99)
+    imgpair.right_coords.e = Energy(-2.15)
     # if CINEB run at the end
-    imgpair._cineb_coords = imgpair.left_coord * 0.99
+    imgpair._cineb_coords = imgpair.left_coords * 0.99
     imgpair._cineb_coords.e = Energy(-0.99)
 
     # test all distance_metrics
@@ -172,7 +172,7 @@ def test_imgpair_energy_plotting(caplog):
         imgpair.plot_energies(filename="test.pdf", distance_metric="abc")
 
     # if any energy is missing, also no plotting should be done
-    imgpair.left_coord.e = None
+    imgpair.left_coords.e = None
     with caplog.at_level("ERROR"):
         imgpair.plot_energies(
             filename="test_noE.pdf", distance_metric="relative"
@@ -187,9 +187,9 @@ def test_imgpair_trajectory_plotting():
     mol2 = Molecule(smiles="CCO")
 
     imgpair = NullImagePair(mol1, mol2)
-    imgpair.left_coord = imgpair.left_coord * 0.99
-    imgpair.right_coord = imgpair.right_coord * 0.99
-    imgpair._cineb_coords = imgpair.left_coord * 0.99
+    imgpair.left_coords = imgpair.left_coords * 0.99
+    imgpair.right_coords = imgpair.right_coords * 0.99
+    imgpair._cineb_coords = imgpair.left_coords * 0.99
 
     imgpair.print_geometries("init.xyz", "fin.xyz", "total.xyz")
     assert os.path.isfile("init.xyz")
@@ -211,14 +211,14 @@ def test_imgpair_calc_engrad():
     imgpair.set_method_and_n_cores(method=XTB(), n_cores=1)
     imgpair.update_both_img_engrad()
     # energy should be updated
-    assert imgpair.left_coord.e is not None
-    assert imgpair.right_coord.e is not None
+    assert imgpair.left_coords.e is not None
+    assert imgpair.right_coords.e is not None
     # units should be forced to Hartree
-    assert str(imgpair.left_coord.e.units) == "Unit(Ha)"
-    assert str(imgpair.right_coord.e.units) == "Unit(Ha)"
+    assert str(imgpair.left_coords.e.units) == "Unit(Ha)"
+    assert str(imgpair.right_coords.e.units) == "Unit(Ha)"
     # gradient should also be updated
-    assert imgpair.left_coord.g is not None
-    assert imgpair.right_coord.g is not None
+    assert imgpair.left_coords.g is not None
+    assert imgpair.right_coords.g is not None
 
     # since imgpair takes a copy of initial species they
     # should not be affected
@@ -238,10 +238,10 @@ def test_imgpair_calc_hess():
     imgpair.update_both_img_hessian_by_calc()
 
     # should not change gradient and energy
-    assert imgpair.left_coord.g is None
-    assert imgpair.left_coord.e is None
+    assert imgpair.left_coords.g is None
+    assert imgpair.left_coords.e is None
     # only hessian should be calculation
-    assert imgpair.left_coord.h is not None
+    assert imgpair.left_coords.h is not None
 
 
 @requires_working_xtb_install
@@ -277,29 +277,29 @@ def test_hessian_update():
     imgpair = NullImagePair(mol1, mol1.copy())
     imgpair.set_method_and_n_cores(method=XTB(), n_cores=1, hess_method=XTB())
 
-    imgpair.left_coord.update_g_from_cart_g(g)
-    imgpair.left_coord.update_h_from_cart_h(h)
-    imgpair.right_coord.update_g_from_cart_g(g)
-    imgpair.right_coord.update_h_from_cart_h(h)
+    imgpair.left_coords.g = g.copy()
+    imgpair.left_coords.h = h.copy()
+    imgpair.right_coords.g = g.copy()
+    imgpair.right_coords.h = h.copy()
 
-    assert imgpair.left_coord.h is not None
+    assert imgpair.left_coords.h is not None
 
-    coord = imgpair.left_coord.copy()
+    coord = imgpair.left_coords.copy()
     coord[0] += 0.1
 
-    imgpair.left_coord = coord
-    imgpair.right_coord = coord
+    imgpair.left_coords = coord
+    imgpair.right_coords = coord
 
     new_g = np.loadtxt("n2_new_grad.txt")
 
-    assert imgpair.left_coord.h is None
-    imgpair.left_coord.update_g_from_cart_g(new_g)
-    imgpair.right_coord.update_g_from_cart_g(new_g)
+    assert imgpair.left_coords.h is None
+    imgpair.left_coords.g = new_g.copy()
+    imgpair.right_coords.g = new_g.copy()
 
     # update the hessian with update formula
     imgpair.update_both_img_hessian_by_formula()
-    assert imgpair.left_coord.h is not None
-    assert imgpair.right_coord.h is not None
+    assert imgpair.left_coords.h is not None
+    assert imgpair.right_coords.h is not None
 
     # calling Hessian update again will raise exception
     with pytest.raises(AssertionError):
@@ -321,16 +321,16 @@ def test_imgpair_hessian_flushing():
     imgpair = NullImagePair(mol1, mol1.copy())
     # turn off flushing Hessians
     autode.bracket.imagepair._flush_old_hessians = False
-    imgpair.left_coord.update_h_from_cart_h(h)
+    imgpair.left_coords.h = h.copy()
     # generate dummy coordinates
-    imgpair.left_coord = imgpair.left_coord * 0.99
-    imgpair.left_coord = imgpair.left_coord * 0.99
+    imgpair.left_coords = imgpair.left_coords * 0.99
+    imgpair.left_coords = imgpair.left_coords * 0.99
     assert imgpair._left_history[0].h is not None
 
     # turn on flushing Hessians again
     autode.bracket.imagepair._flush_old_hessians = True
     imgpair = NullImagePair(mol1, mol1.copy())
-    imgpair.right_coord.update_h_from_cart_h(h)
-    imgpair.right_coord = imgpair.right_coord * 0.99
-    imgpair.right_coord = imgpair.right_coord * 0.99
+    imgpair.right_coords.h = h.copy()
+    imgpair.right_coords = imgpair.right_coords * 0.99
+    imgpair.right_coords = imgpair.right_coords * 0.99
     assert imgpair._right_history[0].h is None