Skip to content

Commit 6642a10

Browse files
authored
Merge pull request #97 from pdobbelaere/serialization
Serialization refactor
2 parents 6a9b07b + 02f5f1a commit 6642a10

27 files changed

Lines changed: 615 additions & 775 deletions

psiflow/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
from .serialization import ( # noqa: F401
66
_DataFuture,
77
deserialize,
8-
serializable,
98
serialize,
9+
register_serializable,
1010
)
1111

1212

@@ -30,4 +30,3 @@ def resolve_and_check(path: Path) -> Path:
3030
load = ExecutionContextLoader.load
3131
context = ExecutionContextLoader.context
3232
wait = ExecutionContextLoader.wait
33-

psiflow/data/dataset.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
)
3737

3838

39-
@psiflow.serializable
39+
@psiflow.register_serializable
4040
class Dataset:
4141
"""
4242
A class representing a dataset of atomic structures.

psiflow/execution.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ def __init__(
287287
# TODO: how to handle env variables?
288288
# disable thread affinity and busy-idling until we can isolate task resources
289289
default_env_vars = {
290+
"PYTHONUNBUFFERED": "TRUE",
290291
"OMP_PROC_BIND": "FALSE",
291292
"OMP_WAIT_POLICY": "PASSIVE",
292293
"OMP_DISPLAY_ENV": "VERBOSE", # verbose OMP log

psiflow/functions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ def __post_init__(self):
297297
# OMP_NUM_THREADS for parallel evaluation does not work..
298298
# https://github.com/dftd3/simple-dftd3/issues/49
299299
# TODO: check whether this is still the case
300-
os.environ["OMP_NUM_THREADS"] = str(self.num_threads * 10)
300+
os.environ["OMP_NUM_THREADS"] = str(self.num_threads)
301301

302302
from dftd3.ase import DFTD3
303303

psiflow/hamiltonians.py

Lines changed: 78 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import urllib
22
from functools import partial
33
from pathlib import Path
4-
from typing import ClassVar, Optional, Union, Callable, Sequence
4+
from typing import Optional, Union, Callable, Sequence
55

66
import numpy as np
77
from parsl.app.app import python_app
@@ -26,13 +26,16 @@
2626
from psiflow.utils.io import dump_json
2727

2828

29-
@psiflow.serializable
29+
apply_threads = python_app(_apply, executors=["default_threads"])
30+
apply_htex = python_app(_apply, executors=["default_htex"])
31+
apply_modelevaluation = python_app(_apply, executors=["ModelEvaluation"])
32+
33+
3034
class Hamiltonian(Computable):
31-
# TODO: app is actually an instance variable, but serialization complains..
32-
outputs: ClassVar[tuple] = ("energy", "forces", "stress")
35+
outputs: tuple = ("energy", "forces", "stress")
3336
batch_size = 1000
34-
app: ClassVar[Callable]
35-
function_name: ClassVar[str]
37+
app: Callable
38+
function_name: str
3639

3740
def compute(
3841
self,
@@ -44,12 +47,7 @@ def compute(
4447
outputs = tuple(self.__class__.outputs)
4548
if batch_size == -1:
4649
batch_size = self.__class__.batch_size
47-
return compute(
48-
arg,
49-
self.app,
50-
outputs_=outputs,
51-
batch_size=batch_size,
52-
)
50+
return compute(arg, self.get_app(), outputs_=outputs, batch_size=batch_size)
5351

5452
def __eq__(self, hamiltonian: "Hamiltonian") -> bool:
5553
raise NotImplementedError
@@ -80,32 +78,13 @@ def serialize_function(self, **kwargs) -> DataFuture:
8078
).outputs[0]
8179

8280
def parameters(self) -> dict:
83-
return {}
84-
85-
86-
@psiflow.serializable
87-
class Zero(Hamiltonian):
88-
89-
def __init__(self):
90-
apply_zero = python_app(_apply, executors=["default_threads"])
91-
self.app = partial(apply_zero, function_cls=ZeroFunction)
92-
93-
def __eq__(self, hamiltonian: Hamiltonian) -> bool:
94-
if type(hamiltonian) is Zero:
95-
return True
96-
return False
97-
98-
def __mul__(self, a: float) -> "Zero":
99-
return Zero()
100-
101-
def __add__(self, hamiltonian: Hamiltonian) -> Hamiltonian:
102-
# (Zero + Hamiltonian) is different from (Hamiltonian + Zero)
103-
return hamiltonian
81+
raise NotImplementedError
10482

105-
__rmul__ = __mul__ # handle float * Zero
83+
def get_app(self) -> Callable:
84+
raise NotImplementedError
10685

10786

108-
@psiflow.serializable
87+
@psiflow.register_serializable
10988
class MixtureHamiltonian(Hamiltonian):
11089
hamiltonians: list[Hamiltonian]
11190
coefficients: list[float]
@@ -127,10 +106,9 @@ def compute( # override compute for efficient batching
127106
) -> Union[list[AppFuture], AppFuture]:
128107
if outputs is None:
129108
outputs = list(self.__class__.outputs)
130-
apply_apps = [h.app for h in self.hamiltonians]
109+
apply_apps = [h.get_app() for h in self.hamiltonians]
131110
reduce_func = partial(
132-
aggregate_multiple,
133-
coefficients=np.array(self.coefficients),
111+
aggregate_multiple, coefficients=np.array(self.coefficients)
134112
)
135113
return compute(
136114
arg,
@@ -231,25 +209,47 @@ def serialize(self, **kwargs) -> list[DataFuture]:
231209
return [h.serialize_function(**kwargs) for h in self.hamiltonians]
232210

233211

234-
@psiflow.serializable
212+
@psiflow.register_serializable
213+
class Zero(Hamiltonian):
214+
function_name: str = "ZeroFunction"
215+
216+
def __init__(self):
217+
pass
218+
219+
def get_app(self) -> Callable:
220+
return partial(apply_threads, function_cls=ZeroFunction)
221+
222+
def __eq__(self, hamiltonian: Hamiltonian) -> bool:
223+
if type(hamiltonian) is Zero:
224+
return True
225+
return False
226+
227+
def __mul__(self, a: float) -> "Zero":
228+
return Zero()
229+
230+
def __add__(self, hamiltonian: Hamiltonian) -> Hamiltonian:
231+
# (Zero + Hamiltonian) is different from (Hamiltonian + Zero)
232+
return hamiltonian
233+
234+
__rmul__ = __mul__ # handle float * Zero
235+
236+
237+
@psiflow.register_serializable
235238
class EinsteinCrystal(Hamiltonian):
236-
reference_geometry: Union[Geometry, AppFuture]
239+
# TODO: logic not consistent depending on Geometry | AppFuture
240+
reference_geometry: Geometry | AppFuture
237241
force_constant: float
238-
function_name: ClassVar[str] = "EinsteinCrystalFunction"
242+
function_name: str = "EinsteinCrystalFunction"
239243

240-
def __init__(
241-
self, geometry: Union[Geometry, AppFuture[Geometry]], force_constant: float
242-
):
244+
def __init__(self, geometry: Union[Geometry, AppFuture], force_constant: float):
243245
super().__init__()
244246
self.reference_geometry = copy_app_future(geometry)
245247
self.force_constant = force_constant
246248
self.external = None # needed
247-
self._create_apps()
248249

249-
def _create_apps(self):
250-
apply_app = python_app(_apply, executors=["default_threads"])
251-
self.app = partial(
252-
apply_app, function_cls=EinsteinCrystalFunction, **self.parameters()
250+
def get_app(self) -> Callable:
251+
return partial(
252+
apply_threads, function_cls=EinsteinCrystalFunction, **self.parameters()
253253
)
254254

255255
def parameters(self) -> dict:
@@ -269,34 +269,27 @@ def __eq__(self, hamiltonian: Hamiltonian) -> bool:
269269
return True
270270

271271

272-
@psiflow.serializable
272+
@psiflow.register_serializable
273273
class PlumedHamiltonian(Hamiltonian):
274274
plumed_input: str # TODO: or future?
275275
external: Optional[psiflow._DataFuture]
276-
function_name: ClassVar[str] = "PlumedFunction"
276+
function_name: str = "PlumedFunction"
277277

278278
def __init__(
279279
self,
280280
plumed_input: str,
281281
external: Union[None, str, Path, File, DataFuture] = None,
282282
):
283283
super().__init__()
284-
285284
self.plumed_input = remove_comments_printflush(plumed_input)
286285
if type(external) in [str, Path]:
287286
external = File(str(external))
288287
if external is not None:
289288
assert external.filepath in self.plumed_input
290289
self.external = external
291-
self._create_apps()
292290

293-
def _create_apps(self):
294-
apply_app = python_app(_apply, executors=["default_htex"])
295-
self.app = partial(
296-
apply_app,
297-
function_cls=PlumedFunction,
298-
**self.parameters(),
299-
)
291+
def get_app(self) -> Callable:
292+
return partial(apply_htex, function_cls=PlumedFunction, **self.parameters())
300293

301294
def parameters(self) -> dict:
302295
if self.external is not None: # ensure parameters depends on self.external
@@ -314,28 +307,24 @@ def __eq__(self, other: Hamiltonian) -> bool:
314307
return True
315308

316309

317-
@psiflow.serializable
310+
@psiflow.register_serializable
318311
class Harmonic(Hamiltonian):
319-
reference_geometry: Union[Geometry, AppFuture[Geometry]]
320-
hessian: Union[np.ndarray, AppFuture[np.ndarray]]
321-
function_name: ClassVar[str] = "HarmonicFunction"
312+
reference_geometry: Geometry | AppFuture
313+
hessian: np.ndarray | AppFuture
314+
function_name: str = "HarmonicFunction"
322315

323316
def __init__(
324317
self,
325-
reference_geometry: Union[Geometry, AppFuture[Geometry]],
326-
hessian: Union[np.ndarray, AppFuture[np.ndarray]],
318+
reference_geometry: Geometry | AppFuture,
319+
hessian: np.ndarray | AppFuture,
327320
):
328321
# TODO: why not copy_app_future(geometry) like others?
329322
self.reference_geometry = reference_geometry
330323
self.hessian = hessian
331-
self._create_apps()
332324

333-
def _create_apps(self):
334-
apply_app = python_app(_apply, executors=["default_threads"])
335-
self.app = partial(
336-
apply_app,
337-
function_cls=HarmonicFunction,
338-
**self.parameters(),
325+
def get_app(self) -> Callable:
326+
return partial(
327+
apply_threads, function_cls=HarmonicFunction, **self.parameters()
339328
)
340329

341330
def parameters(self) -> dict:
@@ -367,30 +356,26 @@ def __eq__(self, hamiltonian: Hamiltonian) -> bool:
367356
return True
368357

369358

370-
@psiflow.serializable
359+
@psiflow.register_serializable
371360
class D3Hamiltonian(Hamiltonian):
372361
method: str
373362
damping: str
374-
function_name: ClassVar[str] = "DispersionFunction"
363+
function_name: str = "DispersionFunction"
375364

376365
def __init__(self, method: str, damping: str = "d3bj"):
377366
self.method, self.damping = method, damping
378-
self._create_apps()
379367

380-
def _create_apps(self):
381-
# TODO: does this make sense? GPU settings are useless for example
368+
def get_app(self) -> Callable:
369+
# execution-side parameters of function are not included in self.parameters()
382370
evaluation = psiflow.context().definitions["ModelEvaluation"]
383-
apply_app = python_app(_apply, executors=["ModelEvaluation"])
384371
resources = evaluation.wq_resources(1)
385-
resources.pop("gpus", None)
386-
387-
# execution-side parameters of function are not included in self.parameters()
388-
self.app = partial(
389-
apply_app,
372+
resources.pop("gpus", None) # do not request GPU
373+
return partial(
374+
apply_modelevaluation,
390375
function_cls=DispersionFunction,
391376
parsl_resource_specification=resources,
392377
**self.parameters(),
393-
num_threads=resources.get("cores", 1), # TODO: sloppy
378+
num_threads=resources.get("cores"),
394379
)
395380

396381
def parameters(self) -> dict:
@@ -406,11 +391,11 @@ def __eq__(self, hamiltonian: Hamiltonian) -> bool:
406391
return True
407392

408393

409-
@psiflow.serializable
394+
@psiflow.register_serializable
410395
class MACEHamiltonian(Hamiltonian):
411396
external: psiflow._DataFuture
412397
atomic_energies: dict[str, float]
413-
function_name: ClassVar[str] = "MACEFunction"
398+
function_name: str = "MACEFunction"
414399

415400
def __init__(
416401
self,
@@ -422,16 +407,13 @@ def __init__(
422407
self.external = File(external)
423408
else:
424409
self.external = external
425-
self._create_apps()
426410

427-
def _create_apps(self):
411+
def get_app(self) -> Callable:
412+
# execution-side parameters of function are not included in self.parameters()
428413
evaluation = psiflow.context().definitions["ModelEvaluation"]
429-
apply_app = python_app(_apply, executors=["ModelEvaluation"])
430414
resources = evaluation.wq_resources(1)
431-
432-
# execution-side parameters of function are not included in self.parameters()
433-
self.app = partial(
434-
apply_app,
415+
return partial(
416+
apply_modelevaluation,
435417
function_cls=MACEFunction,
436418
parsl_resource_specification=resources,
437419
**self.parameters(),
@@ -469,7 +451,7 @@ def __eq__(self, hamiltonian: Hamiltonian) -> bool:
469451
# TODO: the methods below are outdated..
470452

471453
@classmethod
472-
def mace_mp0(cls, size: str = "small") -> 'MACEHamiltonian':
454+
def mace_mp0(cls, size: str = "small") -> "MACEHamiltonian":
473455
urls = dict(
474456
small="https://github.com/ACEsuit/mace-mp/releases/download/mace_mp_0/2023-12-10-mace-128-L0_energy_epoch-249.model", # 2023-12-10-mace-128-L0_energy_epoch-249.model
475457
large="https://github.com/ACEsuit/mace-mp/releases/download/mace_mp_0/2023-12-03-mace-128-L1_epoch-199.model",
@@ -483,7 +465,7 @@ def mace_mp0(cls, size: str = "small") -> 'MACEHamiltonian':
483465
return cls(parsl_file, {})
484466

485467
@classmethod
486-
def mace_cc(cls) -> 'MACEHamiltonian':
468+
def mace_cc(cls) -> "MACEHamiltonian":
487469
url = "https://github.com/molmod/psiflow/raw/main/examples/data/ani500k_cc_cpu.model"
488470
parsl_file = psiflow.context().new_file("mace_mp_", ".pth")
489471
urllib.request.urlretrieve(

psiflow/learning.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def evaluate_outputs(
100100

101101

102102
@typeguard.typechecked
103-
@psiflow.serializable
103+
# @psiflow.serializable
104104
class Learning:
105105
reference: Reference
106106
path_output: str

psiflow/metrics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ def _initialize_wandb(
454454

455455

456456
@typeguard.typechecked
457-
@psiflow.serializable
457+
# @psiflow.serializable
458458
class Metrics:
459459
wandb_group: Optional[str]
460460
wandb_project: Optional[str]

0 commit comments

Comments
 (0)