@@ -11,7 +11,7 @@ The library consists of thin wrappers to `potlib` under `cpot` and a
1111
1212This is [ on PyPI] ( https://pypi.org/project/pypotlib ) , with wheels, so usage is simply:
1313
14- ``` bash
14+ ``` bash
1515pip install pypotlib
1616```
1717
@@ -21,13 +21,14 @@ work with.
2121
2222### Local Development
2323
24- The easiest way is to use the environment file, compatible with ` conda ` ,
25- ` mamba ` , ` micromamba ` etc.
24+ The easiest way is to use the ` pixi ` environment.
2625
2726``` bash
28- micromamba env create -f environment.yml
29- micromamba activate rgpotpy
27+ pixi s
3028pdm install
29+ # For tests
30+ pixi s -e with-ase
31+ pytest tests/test_cache.py
3132```
3233
3334### Production
@@ -93,6 +94,130 @@ optimizer = BFGS(neb)
9394optimizer.run(fmax = 0.04 )
9495```
9596
97+ ## Caching runs
98+
99+ ` pypotlib ` supports persistent caching via RocksDB. This allows energy and force
100+ evaluations to be stored and retrieved, significantly speeding up repeated
101+ calculations on identical configurations.
102+
103+ ``` python
104+ import pypotlib.cpot as cpot
105+ import numpy as np
106+
107+ # 1. Initialize the cache with a directory path
108+ # This will create a RocksDB database at the specified location.
109+ cache = cpot.PotentialCache(" /tmp/my_pot_cache" , create_if_missing = True )
110+
111+ # 2. Create the potential and link the cache
112+ lj = cpot.LJPot()
113+ lj.set_cache(cache)
114+
115+ # 3. Use as normal
116+ pos = np.array([[0.0 , 0.0 , 0.0 ], [3.0 , 0.0 , 0.0 ]])
117+ types = [1 , 1 ]
118+ box = np.eye(3 ) * 10.0
119+
120+ # First call: Computes and stores result in DB
121+ e1, f1 = lj(pos, types, box)
122+
123+ # Second call (same inputs): Retrieves result from DB (Instant)
124+ e2, f2 = lj(pos, types, box)
125+ ```
126+
127+ ### ASE Caching
128+
129+ The ASE calculator provides more sophisticated caching, with the internal checks
130+ for equivalent structures further reducing calls to the underlying compiled
131+ code.
132+
133+ ``` python
134+ from ase import Atoms
135+ from pypotlib import cpot
136+ from pypotlib.ase_adapters import PyPotLibCalc
137+
138+ # Setup Potential with Cache
139+ cache = cpot.PotentialCache(" ase_cache_db" )
140+ pot = cpot.CuH2Pot()
141+ pot.set_cache(cache)
142+
143+ # Create Calculator
144+ atoms = Atoms(symbols = [" Cu" , " H" ], positions = [[0 , 0 , 0 ], [0.5 , 0.5 , 0.5 ]])
145+ calc = PyPotLibCalc(pot)
146+ atoms.set_calculator(calc)
147+
148+ print (atoms.get_potential_energy())
149+ print (atoms.get_forces())
150+ ```
151+
152+ ### NEB Example with Benchmarking
153+
154+ To really see the power of the cache, we can run an NEB optimization twice. The
155+ first run performs the calculations and populates the RocksDB database. The
156+ second run, performing the exact same optimization, hits the cache for every
157+ step, reducing the computational cost to near zero.
158+
159+ ``` python
160+ import time
161+ import shutil
162+ from ase import Atoms
163+ from ase.mep import NEB
164+ from ase.optimize import BFGS
165+ from pypotlib import cpot
166+ from pypotlib.ase_adapters import PyPotLibCalc
167+
168+ # Setup a persistent cache
169+ cache_path = " /tmp/neb_demo_cache"
170+ # Clear previous cache to ensure a "cold" start for demonstration
171+ shutil.rmtree(cache_path, ignore_errors = True )
172+ cache = cpot.PotentialCache(cache_path, create_if_missing = True )
173+
174+
175+ def setup_neb_images ():
176+ """ Helper to create fresh images for the NEB."""
177+ atoms_initial = Atoms(symbols = [" H" , " H" ], positions = [(0 , 0 , 0 ), (0 , 0 , 1 )])
178+ atoms_final = Atoms(symbols = [" H" , " H" ], positions = [(0 , 0 , 2 ), (0 , 0 , 3 )])
179+
180+ images = [atoms_initial]
181+ images += [atoms_initial.copy() for _ in range (3 )]
182+ images += [atoms_final]
183+
184+ # Attach calculators with the SHARED cache
185+ for image in images:
186+ pot = cpot.LJPot()
187+ pot.set_cache(cache) # All images share the same DB
188+ image.calc = PyPotLibCalc(pot)
189+
190+ return images
191+
192+
193+ # --- Run 1: Cold Cache (Calculates & Writes) ---
194+ print (" Starting Run 1 (Cold Cache)..." )
195+ images_1 = setup_neb_images()
196+ neb_1 = NEB(images_1)
197+ neb_1.interpolate(method = " idpp" )
198+ opt_1 = BFGS(neb_1)
199+
200+ start_1 = time.time()
201+ opt_1.run(fmax = 0.04 )
202+ duration_1 = time.time() - start_1
203+ print (f " Run 1 finished in { duration_1:.4f } seconds. " )
204+
205+ # --- Run 2: Warm Cache (Reads only) ---
206+ print (" \n Starting Run 2 (Warm Cache)..." )
207+ images_2 = setup_neb_images() # Re-create identical initial state
208+ neb_2 = NEB(images_2)
209+ neb_2.interpolate(method = " idpp" )
210+ opt_2 = BFGS(neb_2)
211+
212+ start_2 = time.time()
213+ opt_2.run(fmax = 0.04 )
214+ duration_2 = time.time() - start_2
215+ print (f " Run 2 finished in { duration_2:.4f } seconds. " )
216+
217+ # --- Results ---
218+ speedup = duration_1 / duration_2 if duration_2 > 0 else 0
219+ print (f " \n Speedup factor: { speedup:.1f } x " )
220+ ```
96221
97222# Contributions
98223
@@ -102,4 +227,5 @@ all contributors to follow our [Code of
102227Conduct] ( https://github.com/TheochemUI/pypotlib/blob/main/CODE_OF_CONDUCT.md ) .
103228
104229# License
230+
105231[ MIT] ( https://github.com/TheochemUI/pypotlib/blob/main/LICENSE ) .
0 commit comments