flashmd 0.2.1__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flashmd-0.2.1/src/flashmd.egg-info → flashmd-0.2.3}/PKG-INFO +8 -2
- {flashmd-0.2.1 → flashmd-0.2.3}/README.md +7 -1
- {flashmd-0.2.1 → flashmd-0.2.3}/pyproject.toml +1 -1
- {flashmd-0.2.1 → flashmd-0.2.3}/src/flashmd/ase/bussi.py +3 -2
- {flashmd-0.2.1 → flashmd-0.2.3}/src/flashmd/ase/langevin.py +9 -4
- {flashmd-0.2.1 → flashmd-0.2.3}/src/flashmd/ase/velocity_verlet.py +36 -0
- flashmd-0.2.3/src/flashmd/constraints.py +97 -0
- {flashmd-0.2.1 → flashmd-0.2.3}/src/flashmd/ipi.py +5 -5
- {flashmd-0.2.1 → flashmd-0.2.3}/src/flashmd/stepper.py +7 -0
- {flashmd-0.2.1 → flashmd-0.2.3/src/flashmd.egg-info}/PKG-INFO +8 -2
- {flashmd-0.2.1 → flashmd-0.2.3}/src/flashmd.egg-info/SOURCES.txt +1 -0
- {flashmd-0.2.1 → flashmd-0.2.3}/LICENSE +0 -0
- {flashmd-0.2.1 → flashmd-0.2.3}/setup.cfg +0 -0
- {flashmd-0.2.1 → flashmd-0.2.3}/src/flashmd/__init__.py +0 -0
- {flashmd-0.2.1 → flashmd-0.2.3}/src/flashmd/ase/__init__.py +0 -0
- {flashmd-0.2.1 → flashmd-0.2.3}/src/flashmd/ase/npt.py +0 -0
- {flashmd-0.2.1 → flashmd-0.2.3}/src/flashmd/models.py +0 -0
- {flashmd-0.2.1 → flashmd-0.2.3}/src/flashmd.egg-info/dependency_links.txt +0 -0
- {flashmd-0.2.1 → flashmd-0.2.3}/src/flashmd.egg-info/requires.txt +0 -0
- {flashmd-0.2.1 → flashmd-0.2.3}/src/flashmd.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flashmd
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Accelerated molecular dynamics with large-time-step predictions
|
|
5
5
|
Author: flashmd developers
|
|
6
6
|
License: Apache-2.0
|
|
@@ -60,12 +60,15 @@ from flashmd.ase.langevin import Langevin
|
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
# Choose your time step (go for 10-30x what you would use in normal MD for your system)
|
|
63
|
-
time_step =
|
|
63
|
+
time_step = 64 # 64 fs; also available: 1, 2, 4, 8, 16, 32, 128 fs
|
|
64
64
|
|
|
65
65
|
# Create a structure and initialize velocities
|
|
66
66
|
atoms = ase.build.bulk("Al", "fcc", cubic=True)
|
|
67
67
|
MaxwellBoltzmannDistribution(atoms, temperature_K=300)
|
|
68
68
|
|
|
69
|
+
# It is generally a good idea to remove any net velocity from the system
|
|
70
|
+
atoms.set_velocities(atoms.get_velocities() - atoms.get_momenta().sum(axis=0) / atoms.get_masses().sum())
|
|
71
|
+
|
|
69
72
|
# Load models
|
|
70
73
|
device="cuda" if torch.cuda.is_available() else "cpu"
|
|
71
74
|
energy_model, flashmd_model = get_pretrained("pet-omatpes", time_step)
|
|
@@ -131,3 +134,6 @@ pip install flashmd==0.1.2 ase==3.24.0 pet-mad==1.4.3
|
|
|
131
134
|
|
|
132
135
|
and using the "PET-MAD" models (PBEsol) from https://huggingface.co/lab-cosmo/flashmd.
|
|
133
136
|
Note that the results were obtained through the i-PI interface.
|
|
137
|
+
|
|
138
|
+
Instructions and material to reproduce the results in the paper are available on
|
|
139
|
+
Materials Cloud at https://doi.org/10.24435/materialscloud:b7-xq.
|
|
@@ -31,12 +31,15 @@ from flashmd.ase.langevin import Langevin
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
# Choose your time step (go for 10-30x what you would use in normal MD for your system)
|
|
34
|
-
time_step =
|
|
34
|
+
time_step = 64 # 64 fs; also available: 1, 2, 4, 8, 16, 32, 128 fs
|
|
35
35
|
|
|
36
36
|
# Create a structure and initialize velocities
|
|
37
37
|
atoms = ase.build.bulk("Al", "fcc", cubic=True)
|
|
38
38
|
MaxwellBoltzmannDistribution(atoms, temperature_K=300)
|
|
39
39
|
|
|
40
|
+
# It is generally a good idea to remove any net velocity from the system
|
|
41
|
+
atoms.set_velocities(atoms.get_velocities() - atoms.get_momenta().sum(axis=0) / atoms.get_masses().sum())
|
|
42
|
+
|
|
40
43
|
# Load models
|
|
41
44
|
device="cuda" if torch.cuda.is_available() else "cpu"
|
|
42
45
|
energy_model, flashmd_model = get_pretrained("pet-omatpes", time_step)
|
|
@@ -102,3 +105,6 @@ pip install flashmd==0.1.2 ase==3.24.0 pet-mad==1.4.3
|
|
|
102
105
|
|
|
103
106
|
and using the "PET-MAD" models (PBEsol) from https://huggingface.co/lab-cosmo/flashmd.
|
|
104
107
|
Note that the results were obtained through the i-PI interface.
|
|
108
|
+
|
|
109
|
+
Instructions and material to reproduce the results in the paper are available on
|
|
110
|
+
Materials Cloud at https://doi.org/10.24435/materialscloud:b7-xq.
|
|
@@ -16,10 +16,11 @@ class Bussi(VelocityVerlet):
|
|
|
16
16
|
model: AtomisticModel | List[AtomisticModel],
|
|
17
17
|
time_constant: float = 10.0 * ase.units.fs,
|
|
18
18
|
device: str | torch.device = "auto",
|
|
19
|
-
rescale_energy: bool =
|
|
19
|
+
rescale_energy: bool = False,
|
|
20
|
+
random_rotation: bool = False,
|
|
20
21
|
**kwargs,
|
|
21
22
|
):
|
|
22
|
-
super().__init__(atoms, timestep, model, device, rescale_energy, **kwargs)
|
|
23
|
+
super().__init__(atoms, timestep, model, device, rescale_energy, random_rotation, **kwargs)
|
|
23
24
|
|
|
24
25
|
self.temperature_K = temperature_K
|
|
25
26
|
self.time_constant = time_constant
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
from .velocity_verlet import VelocityVerlet
|
|
2
2
|
import ase.units
|
|
3
3
|
from typing import List
|
|
4
|
-
|
|
5
|
-
# from ..utils.pretrained import load_pretrained_models
|
|
6
4
|
from metatomic.torch import AtomisticModel
|
|
7
5
|
import torch
|
|
8
6
|
import ase
|
|
@@ -17,14 +15,19 @@ class Langevin(VelocityVerlet):
|
|
|
17
15
|
temperature_K: float,
|
|
18
16
|
model: AtomisticModel | List[AtomisticModel],
|
|
19
17
|
time_constant: float = 100.0 * ase.units.fs,
|
|
18
|
+
fixcm: bool = True,
|
|
20
19
|
device: str | torch.device = "auto",
|
|
21
|
-
rescale_energy: bool =
|
|
20
|
+
rescale_energy: bool = False,
|
|
21
|
+
random_rotation: bool = False,
|
|
22
22
|
**kwargs,
|
|
23
23
|
):
|
|
24
|
-
super().__init__(atoms, timestep, model, device, rescale_energy, **kwargs)
|
|
24
|
+
super().__init__(atoms, timestep, model, device, rescale_energy, random_rotation, **kwargs)
|
|
25
25
|
|
|
26
26
|
self.temperature_K = temperature_K
|
|
27
27
|
self.friction = 1.0 / time_constant
|
|
28
|
+
self.fixcm = fixcm
|
|
29
|
+
if self.fixcm:
|
|
30
|
+
self.atoms.set_velocities(self.atoms.get_velocities() - self.atoms.get_momenta().sum(axis=0) / self.atoms.get_masses().sum())
|
|
28
31
|
|
|
29
32
|
def step(self):
|
|
30
33
|
self.apply_langevin_half_step()
|
|
@@ -39,3 +42,5 @@ class Langevin(VelocityVerlet):
|
|
|
39
42
|
ase.units.kB * self.temperature_K * self.atoms.get_masses()[:, None]
|
|
40
43
|
) * np.random.randn(*old_momenta.shape)
|
|
41
44
|
self.atoms.set_momenta(new_momenta)
|
|
45
|
+
if self.fixcm:
|
|
46
|
+
self.atoms.set_velocities(self.atoms.get_velocities() - self.atoms.get_momenta().sum(axis=0) / self.atoms.get_masses().sum())
|
|
@@ -9,6 +9,7 @@ from metatomic.torch import System
|
|
|
9
9
|
import ase
|
|
10
10
|
from ..stepper import FlashMDStepper
|
|
11
11
|
import numpy as np
|
|
12
|
+
from scipy.spatial.transform import Rotation
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class VelocityVerlet(MolecularDynamics):
|
|
@@ -19,6 +20,7 @@ class VelocityVerlet(MolecularDynamics):
|
|
|
19
20
|
model: AtomisticModel | List[AtomisticModel],
|
|
20
21
|
device: str | torch.device = "auto",
|
|
21
22
|
rescale_energy: bool = True,
|
|
23
|
+
random_rotation: bool = False,
|
|
22
24
|
**kwargs,
|
|
23
25
|
):
|
|
24
26
|
super().__init__(atoms, timestep, **kwargs)
|
|
@@ -41,6 +43,7 @@ class VelocityVerlet(MolecularDynamics):
|
|
|
41
43
|
|
|
42
44
|
self.stepper = FlashMDStepper(model, self.device)
|
|
43
45
|
self.rescale_energy = rescale_energy
|
|
46
|
+
self.random_rotation = random_rotation
|
|
44
47
|
|
|
45
48
|
def step(self):
|
|
46
49
|
if self.rescale_energy:
|
|
@@ -49,7 +52,33 @@ class VelocityVerlet(MolecularDynamics):
|
|
|
49
52
|
system = _convert_atoms_to_system(
|
|
50
53
|
self.atoms, device=self.device, dtype=self.dtype
|
|
51
54
|
)
|
|
55
|
+
|
|
56
|
+
if self.random_rotation:
|
|
57
|
+
# generate a random rotation matrix with SciPy
|
|
58
|
+
R = torch.tensor(
|
|
59
|
+
_get_random_rotation(),
|
|
60
|
+
device=system.positions.device,
|
|
61
|
+
dtype=system.positions.dtype,
|
|
62
|
+
)
|
|
63
|
+
# apply the random rotation
|
|
64
|
+
old_cell = system.cell
|
|
65
|
+
system.cell = system.cell @ R.T
|
|
66
|
+
system.positions = system.positions @ R.T
|
|
67
|
+
# change momentum TensorMap in place
|
|
68
|
+
system.get_data("momenta").block().values[:] = (
|
|
69
|
+
(system.get_data("momenta").block().values.squeeze(-1) @ R.T).unsqueeze(-1)
|
|
70
|
+
)
|
|
71
|
+
|
|
52
72
|
new_system = self.stepper.step(system)
|
|
73
|
+
|
|
74
|
+
if self.random_rotation:
|
|
75
|
+
# revert q, p to the original reference frame, load old cell
|
|
76
|
+
new_system.cell = old_cell
|
|
77
|
+
new_system.positions = new_system.positions @ R
|
|
78
|
+
new_system.get_data("momenta").block().values[:] = (
|
|
79
|
+
(new_system.get_data("momenta").block().values.squeeze(-1) @ R).unsqueeze(-1)
|
|
80
|
+
)
|
|
81
|
+
|
|
53
82
|
self.atoms.set_positions(new_system.positions.detach().cpu().numpy())
|
|
54
83
|
self.atoms.set_momenta(
|
|
55
84
|
new_system.get_data("momenta")
|
|
@@ -120,3 +149,10 @@ def _convert_atoms_to_system(
|
|
|
120
149
|
),
|
|
121
150
|
)
|
|
122
151
|
return system
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _get_random_rotation():
|
|
155
|
+
R = Rotation.random().as_matrix()
|
|
156
|
+
if np.random.rand() < 0.5:
|
|
157
|
+
R *= -1 # allow improper rotations
|
|
158
|
+
return R
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from typing import Dict, List
|
|
2
|
+
|
|
3
|
+
import torch
|
|
4
|
+
from metatensor.torch import TensorBlock, TensorMap
|
|
5
|
+
from metatomic.torch import System
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@torch.jit.script
|
|
9
|
+
def enforce_physical_constraints(
|
|
10
|
+
systems: List[System],
|
|
11
|
+
predictions: Dict[str, TensorMap],
|
|
12
|
+
timestep: float,
|
|
13
|
+
) -> Dict[str, TensorMap]:
|
|
14
|
+
"""
|
|
15
|
+
Enforces physical constraints in the predictions of a FlashMD model, namely
|
|
16
|
+
conservation of momentum of the center of mass and uniform linear motion of the
|
|
17
|
+
center of mass.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
new_predictions: Dict[str, TensorMap] = {}
|
|
21
|
+
|
|
22
|
+
for key, prediction_tmap in predictions.items():
|
|
23
|
+
if key == "momenta":
|
|
24
|
+
# conservation of momentum of the center of mass
|
|
25
|
+
system_sizes = [len(s) for s in systems]
|
|
26
|
+
masses = [s.get_data("masses").block().values for s in systems]
|
|
27
|
+
total_masses = [m.sum() for m in masses]
|
|
28
|
+
momenta_before = [s.get_data("momenta").block().values for s in systems]
|
|
29
|
+
momenta_now = torch.split(prediction_tmap.block().values, system_sizes)
|
|
30
|
+
velocities_now = [p / m[:, None] for p, m in zip(momenta_now, masses)]
|
|
31
|
+
velocities_com_before = [
|
|
32
|
+
torch.sum(p, dim=0) / M for p, M in zip(momenta_before, total_masses)
|
|
33
|
+
]
|
|
34
|
+
velocities_com_now = [
|
|
35
|
+
torch.sum(p, dim=0) / M for p, M in zip(momenta_now, total_masses)
|
|
36
|
+
]
|
|
37
|
+
velocities_now = [
|
|
38
|
+
v - v_com_now_i + v_com_before_i
|
|
39
|
+
for v, v_com_before_i, v_com_now_i in zip(
|
|
40
|
+
velocities_now, velocities_com_before, velocities_com_now
|
|
41
|
+
)
|
|
42
|
+
]
|
|
43
|
+
momenta_now = [v * m[:, None] for v, m in zip(velocities_now, masses)]
|
|
44
|
+
new_predictions[key] = TensorMap(
|
|
45
|
+
prediction_tmap.keys,
|
|
46
|
+
[
|
|
47
|
+
TensorBlock(
|
|
48
|
+
values=torch.concatenate(momenta_now),
|
|
49
|
+
samples=prediction_tmap.block().samples,
|
|
50
|
+
components=prediction_tmap.block().components,
|
|
51
|
+
properties=prediction_tmap.block().properties,
|
|
52
|
+
)
|
|
53
|
+
],
|
|
54
|
+
)
|
|
55
|
+
elif key == "positions":
|
|
56
|
+
# uniform linear motion of the center of mass
|
|
57
|
+
system_sizes = [len(s) for s in systems]
|
|
58
|
+
masses = [s.get_data("masses").block().values for s in systems]
|
|
59
|
+
total_masses = [m.sum() for m in masses]
|
|
60
|
+
positions_before = [s.positions.unsqueeze(-1) for s in systems]
|
|
61
|
+
momenta = [s.get_data("momenta").block().values for s in systems]
|
|
62
|
+
positions_now = torch.split(prediction_tmap.block().values, system_sizes)
|
|
63
|
+
velocities_com = [
|
|
64
|
+
torch.sum(p, dim=0) / M for p, M in zip(momenta, total_masses)
|
|
65
|
+
]
|
|
66
|
+
positions_com_before = [
|
|
67
|
+
torch.sum(q * m[:, None], dim=0) / M
|
|
68
|
+
for q, m, M in zip(positions_before, masses, total_masses)
|
|
69
|
+
]
|
|
70
|
+
positions_com_now = [
|
|
71
|
+
torch.sum(q * m[:, None], dim=0) / M
|
|
72
|
+
for q, m, M in zip(positions_now, masses, total_masses)
|
|
73
|
+
]
|
|
74
|
+
positions_now = [
|
|
75
|
+
q - q_com_now_i + q_com_before_i + v_com_i * timestep
|
|
76
|
+
for q, q_com_now_i, q_com_before_i, v_com_i in zip(
|
|
77
|
+
positions_now,
|
|
78
|
+
positions_com_now,
|
|
79
|
+
positions_com_before,
|
|
80
|
+
velocities_com,
|
|
81
|
+
)
|
|
82
|
+
]
|
|
83
|
+
new_predictions[key] = TensorMap(
|
|
84
|
+
prediction_tmap.keys,
|
|
85
|
+
[
|
|
86
|
+
TensorBlock(
|
|
87
|
+
values=torch.concatenate(positions_now),
|
|
88
|
+
samples=prediction_tmap.block().samples,
|
|
89
|
+
components=prediction_tmap.block().components,
|
|
90
|
+
properties=prediction_tmap.block().properties,
|
|
91
|
+
)
|
|
92
|
+
],
|
|
93
|
+
)
|
|
94
|
+
else:
|
|
95
|
+
new_predictions[key] = prediction_tmap
|
|
96
|
+
|
|
97
|
+
return new_predictions
|
|
@@ -15,7 +15,7 @@ from metatensor.torch import Labels, TensorBlock, TensorMap
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def get_standard_vv_step(
|
|
18
|
-
sim, model=None, device=None, rescale_energy=
|
|
18
|
+
sim, model=None, device=None, rescale_energy=False, random_rotation=False
|
|
19
19
|
):
|
|
20
20
|
"""
|
|
21
21
|
Returns a velocity Verlet stepper function for i-PI simulations.
|
|
@@ -56,7 +56,7 @@ def get_standard_vv_step(
|
|
|
56
56
|
return vv_step
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
def get_flashmd_vv_step(sim, model, device, rescale_energy=
|
|
59
|
+
def get_flashmd_vv_step(sim, model, device, rescale_energy=False, random_rotation=False):
|
|
60
60
|
capabilities = model.capabilities()
|
|
61
61
|
|
|
62
62
|
model_timestep = float(model.module.timestep)
|
|
@@ -113,7 +113,7 @@ def get_flashmd_vv_step(sim, model, device, rescale_energy=True, random_rotation
|
|
|
113
113
|
kinetic_energy = sim.properties("kinetic_md")
|
|
114
114
|
alpha = np.sqrt(1.0 - (new_energy - old_energy) / kinetic_energy)
|
|
115
115
|
motion.beads.p[:] = alpha * dstrip(motion.beads.p)
|
|
116
|
-
motion.integrator.pconstraints() # just to
|
|
116
|
+
motion.integrator.pconstraints() # just to be sure
|
|
117
117
|
|
|
118
118
|
info("@flashmd: End of VV step", verbosity.debug)
|
|
119
119
|
|
|
@@ -156,7 +156,7 @@ def get_nvt_stepper(
|
|
|
156
156
|
sim,
|
|
157
157
|
model,
|
|
158
158
|
device,
|
|
159
|
-
rescale_energy=
|
|
159
|
+
rescale_energy=False,
|
|
160
160
|
random_rotation=False,
|
|
161
161
|
use_standard_vv=False,
|
|
162
162
|
):
|
|
@@ -224,7 +224,7 @@ def get_npt_stepper(
|
|
|
224
224
|
sim,
|
|
225
225
|
model,
|
|
226
226
|
device,
|
|
227
|
-
rescale_energy=
|
|
227
|
+
rescale_energy=False,
|
|
228
228
|
random_rotation=False,
|
|
229
229
|
use_standard_vv=False,
|
|
230
230
|
):
|
|
@@ -5,6 +5,8 @@ import torch
|
|
|
5
5
|
from metatomic.torch import System
|
|
6
6
|
from metatrain.utils.neighbor_lists import get_system_with_neighbor_lists
|
|
7
7
|
from metatomic.torch import AtomisticModel
|
|
8
|
+
from .constraints import enforce_physical_constraints
|
|
9
|
+
import ase.units
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
class FlashMDStepper:
|
|
@@ -14,6 +16,7 @@ class FlashMDStepper:
|
|
|
14
16
|
device: torch.device,
|
|
15
17
|
):
|
|
16
18
|
self.model = model.to(device)
|
|
19
|
+
self.time_step = float(model.module.timestep) * ase.units.fs
|
|
17
20
|
|
|
18
21
|
# one of these for each model:
|
|
19
22
|
self.evaluation_options = ModelEvaluationOptions(
|
|
@@ -41,6 +44,10 @@ class FlashMDStepper:
|
|
|
41
44
|
model_outputs = self.model(
|
|
42
45
|
[system], self.evaluation_options, check_consistency=False
|
|
43
46
|
)
|
|
47
|
+
model_outputs = enforce_physical_constraints(
|
|
48
|
+
[system], model_outputs, timestep=self.time_step
|
|
49
|
+
)
|
|
50
|
+
|
|
44
51
|
new_q = model_outputs["positions"].block().values.squeeze(-1)
|
|
45
52
|
new_p = model_outputs["momenta"].block().values.squeeze(-1)
|
|
46
53
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flashmd
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Accelerated molecular dynamics with large-time-step predictions
|
|
5
5
|
Author: flashmd developers
|
|
6
6
|
License: Apache-2.0
|
|
@@ -60,12 +60,15 @@ from flashmd.ase.langevin import Langevin
|
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
# Choose your time step (go for 10-30x what you would use in normal MD for your system)
|
|
63
|
-
time_step =
|
|
63
|
+
time_step = 64 # 64 fs; also available: 1, 2, 4, 8, 16, 32, 128 fs
|
|
64
64
|
|
|
65
65
|
# Create a structure and initialize velocities
|
|
66
66
|
atoms = ase.build.bulk("Al", "fcc", cubic=True)
|
|
67
67
|
MaxwellBoltzmannDistribution(atoms, temperature_K=300)
|
|
68
68
|
|
|
69
|
+
# It is generally a good idea to remove any net velocity from the system
|
|
70
|
+
atoms.set_velocities(atoms.get_velocities() - atoms.get_momenta().sum(axis=0) / atoms.get_masses().sum())
|
|
71
|
+
|
|
69
72
|
# Load models
|
|
70
73
|
device="cuda" if torch.cuda.is_available() else "cpu"
|
|
71
74
|
energy_model, flashmd_model = get_pretrained("pet-omatpes", time_step)
|
|
@@ -131,3 +134,6 @@ pip install flashmd==0.1.2 ase==3.24.0 pet-mad==1.4.3
|
|
|
131
134
|
|
|
132
135
|
and using the "PET-MAD" models (PBEsol) from https://huggingface.co/lab-cosmo/flashmd.
|
|
133
136
|
Note that the results were obtained through the i-PI interface.
|
|
137
|
+
|
|
138
|
+
Instructions and material to reproduce the results in the paper are available on
|
|
139
|
+
Materials Cloud at https://doi.org/10.24435/materialscloud:b7-xq.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|