Skip to content

Commit 9c1add4

Browse files
authored
Add workflow tests (#256)
* Fix mapping of in-outputs * General workflow tests * Add geometry optimization test * Add molecular dynamics test * Add phonon test * Revert removed utils test * Move log_output fixture * Add single point test * Add GW tests * Add elastic test * Make labels private * Add dmft test * Move coveralls actions, add nomad infra * Try only python 3.12 * Update pyproject.toml * Add beyond_dft tests * Extend beyond_dft test * Add equation_of_state test * Add max_ent test * Add photon_polarization test * Add tb test * Add thermodyanamics test * Add xs test
1 parent cd1616e commit 9c1add4

39 files changed

+1353
-153
lines changed

.github/workflows/actions.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@ on: [push]
66
# `pull-requests` is for permission to pull request
77
permissions:
88
contents: write
9+
packages: write
10+
attestations: write
11+
id-token: write
912
checks: write
1013
pull-requests: write
1114

1215
env:
1316
UV_SYSTEM_PYTHON: true
17+
REGISTRY: ghcr.io
1418
UV_VERSION: 0.7
19+
1520
jobs:
1621
install-and-test:
1722
runs-on: ubuntu-latest
@@ -21,6 +26,16 @@ jobs:
2126
python_version: ["3.10", "3.11", "3.12"]
2227
steps:
2328
- uses: actions/checkout@v4
29+
30+
- uses: docker/login-action@v3
31+
with:
32+
registry: ${{ env.REGISTRY }}
33+
username: ${{ github.actor }}
34+
password: ${{ secrets.GITHUB_TOKEN }}
35+
36+
- name: Start containers
37+
run: |
38+
docker compose up -d --quiet-pull
2439
- name: Set up Python ${{matrix.python_version}}
2540
uses: actions/setup-python@v5
2641
with:

docker-compose.yml

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
name: nomad-simulations-test
2+
services:
3+
# broker for celery
4+
rabbitmq:
5+
restart: unless-stopped
6+
image: rabbitmq:3.11.5
7+
container_name: rabbitmq
8+
environment:
9+
- RABBITMQ_ERLANG_COOKIE=SWQOKODSQALRPCLNMEQG
10+
- RABBITMQ_DEFAULT_USER=rabbitmq
11+
- RABBITMQ_DEFAULT_PASS=rabbitmq
12+
- RABBITMQ_DEFAULT_VHOST=/
13+
ports:
14+
- "5672:5672"
15+
volumes:
16+
- rabbitmq:/var/lib/rabbitmq
17+
healthcheck:
18+
test: ["CMD", "rabbitmq-diagnostics", "--silent", "--quiet", "ping"]
19+
interval: 10s
20+
timeout: 10s
21+
retries: 30
22+
start_period: 10s
23+
24+
# the search engine
25+
elastic:
26+
restart: unless-stopped
27+
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.24
28+
container_name: elastic
29+
environment:
30+
- ES_JAVA_OPTS=-Xms512m -Xmx512m
31+
- discovery.type=single-node
32+
ports:
33+
- "9200:9200"
34+
volumes:
35+
- elastic:/usr/share/elasticsearch/data
36+
healthcheck:
37+
test:
38+
- "CMD"
39+
- "curl"
40+
- "--fail"
41+
- "--silent"
42+
- "http://elastic:9200/_cat/health"
43+
interval: 10s
44+
timeout: 10s
45+
retries: 30
46+
start_period: 60s
47+
48+
# the user data db
49+
mongo:
50+
restart: unless-stopped
51+
image: mongo:5.0.6
52+
container_name: mongo
53+
environment:
54+
- MONGO_DATA_DIR=/data/db
55+
- MONGO_LOG_DIR=/dev/null
56+
ports:
57+
- "27017:27017"
58+
volumes:
59+
- mongo:/data/db
60+
- ./.volumes/mongo:/backup
61+
command: mongod --logpath=/dev/null # --quiet
62+
healthcheck:
63+
test:
64+
- "CMD"
65+
- "mongo"
66+
- "mongo:27017/test"
67+
- "--quiet"
68+
- "--eval"
69+
- "'db.runCommand({ping:1}).ok'"
70+
interval: 10s
71+
timeout: 10s
72+
retries: 30
73+
start_period: 10s
74+
75+
keycloak:
76+
image: gitlab-registry.mpcdf.mpg.de/nomad-lab/nomad-test-keycloak:v1.0
77+
platform: linux/amd64
78+
container_name: keycloak
79+
environment:
80+
- KC_HTTP_PORT=8008
81+
- KC_HOSTNAME=http://localhost:8008
82+
ports:
83+
- 8008:8008
84+
- 9000:9000
85+
86+
volumes:
87+
mongo:
88+
name: "mongo"
89+
elastic:
90+
name: "elastic"
91+
rabbitmq:
92+
name: "rabbitmq"
93+

nomad.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
keycloak:
2+
server_url: 'http://localhost:8008/'
3+
public_server_url: 'http://localhost:8008/'
4+
realm_name: 'fairdi_nomad_test'
5+
username: 'admin'
6+
password: 'password'
7+
client:
8+
url: 'http://localhost:8000/fairdi/nomad/latest/api'
9+
user: 'admin'
10+
password: 'password'
11+
fs:
12+
staging: '/tmp/volumes/fs/staging'

src/nomad_simulations/schema_packages/workflow/beyond_dft.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@
1616

1717

1818
class BeyondDFTModel(SimulationWorkflowModel):
19-
label = 'DFT+ workflow parameters'
19+
_label = 'DFT+ workflow parameters'
2020

2121

2222
class BeyondDFTResults(SimulationWorkflowResults):
2323
"""
2424
Contains reference to DFT outputs.
2525
"""
2626

27-
label = 'DFT+ workflow results'
27+
_label = 'DFT+ workflow results'
2828

2929
dft = SubSection(sub_section=ElectronicStructureResults)
3030

src/nomad_simulations/schema_packages/workflow/dmft.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010

1111

1212
class DFTTBDDMFTModel(BeyondDFTModel):
13-
label = 'DMFT+MaxEnt workflow parameters'
13+
_label = 'DFT+TB+DMFT workflow parameters'
1414

1515

1616
class DFTTBDMFTResults(BeyondDFTResults):
17-
label = 'DMFT+MaxEnt workflow results'
17+
_label = 'DFT+TB+DMFT workflow results'
1818

1919

2020
class DFTTBDMFTWorkflow(BeyondDFTWorkflow):
@@ -43,8 +43,11 @@ def normalize(self, archive: EntryArchive, logger: BoundLogger) -> None:
4343

4444
super().normalize(archive, logger)
4545

46-
if self.task and not self.task[-1].name:
47-
self.task[-1].name = 'DMFT'
46+
if len(self.tasks) == 3 and not self.tasks[1].name:
47+
self.tasks[1].name = 'TB'
48+
49+
if self.tasks and not self.tasks[-1].name:
50+
self.tasks[-1].name = 'DMFT'
4851

4952

5053
m_package.__init_metainfo__()

src/nomad_simulations/schema_packages/workflow/elastic.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from structlog.stdlib import BoundLogger
1414

1515
from nomad_simulations.schema_packages.general import Program
16+
from nomad_simulations.schema_packages.utils import log
1617

1718
from .general import INCORRECT_N_TASKS, SimulationWorkflow, SimulationWorkflowModel
1819
from .thermodynamics import ThermodynamicsResults
@@ -21,6 +22,8 @@
2122

2223

2324
class ElasticModel(SimulationWorkflowModel):
25+
_label = 'Elastic model'
26+
2427
program = Quantity(
2528
type=Reference(Program),
2629
shape=[],
@@ -129,6 +132,8 @@ class StrainDiagrams(ArchiveSection):
129132

130133

131134
class ElasticResults(ThermodynamicsResults):
135+
_label = 'Elastic results'
136+
132137
n_deformations = Quantity(
133138
type=np.int32,
134139
shape=[],
@@ -382,17 +387,21 @@ class Elastic(SimulationWorkflow):
382387
Definitions for an elastic workflow.
383388
"""
384389

385-
task_label = 'Deformation'
390+
_task_label = 'Deformation'
386391

387-
def map_inputs(self, archive: EntryArchive, logger: BoundLogger) -> None:
392+
@log
393+
def map_inputs(self, archive: EntryArchive) -> None:
388394
if not self.model:
389395
self.model = ElasticModel()
390-
super().map_inputs(archive, logger)
396+
logger = self.map_inputs.__annotations__['logger']
397+
super().map_inputs(archive, logger=logger)
391398

392-
def map_outputs(self, archive: EntryArchive, logger: BoundLogger) -> None:
399+
@log
400+
def map_outputs(self, archive: EntryArchive) -> None:
393401
if not self.results:
394402
self.results = ElasticResults()
395-
super().map_outputs(archive, logger)
403+
logger = self.map_outputs.__annotations__['logger']
404+
super().map_outputs(archive, logger=logger)
396405

397406
def normalize(self, archive: EntryArchive, logger: BoundLogger) -> None:
398407
super().normalize(archive, logger)

src/nomad_simulations/schema_packages/workflow/equation_of_state.py

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636

3737

3838
class EquationOfStateModel(SimulationWorkflowModel):
39+
_label = 'EquationOfState workflow parameters'
40+
3941
program = Quantity(
4042
type=Reference(Program),
4143
shape=[],
@@ -123,6 +125,8 @@ class EOSFit(ArchiveSection):
123125

124126

125127
class EquationOfStateResults(SimulationWorkflowResults):
128+
_label = 'EquationOfState workflow results'
129+
126130
n_points = Quantity(
127131
type=np.int32,
128132
shape=[],
@@ -153,41 +157,48 @@ class EquationOfStateResults(SimulationWorkflowResults):
153157

154158
def normalize(self, archive: EntryArchive, logger: BoundLogger) -> None:
155159
if self.n_points is None:
156-
self.n_points = len(archive.data.outputs)
160+
try:
161+
self.n_points = len(archive.data.outputs)
162+
except Exception:
163+
logger.error('No Outputs found.')
157164

158165
if self.energies is None:
159166
try:
160-
self.energies = [
167+
energies = [
161168
outputs.total_energies[-1].value.to('joule').magnitude
162169
for outputs in archive.data.outputs
163170
]
171+
if energies:
172+
self.energies = energies
164173
except Exception:
165174
logger.error('Total energy not found in outputs.')
166175

167176
if self.volumes is None:
168177
try:
169178
volumes = []
170179
for system in archive.data.model_system:
180+
lattice_vectors = None
171181
for cell in system.cell:
172182
if cell.lattice_vectors is not None:
173-
cell = system.atoms.lattice_vectors.to('m').magnitude
174-
volumes.append(get_volume(cell))
183+
lattice_vectors = cell.lattice_vectors.to('m').magnitude
175184
break
176-
self.volumes = volumes
185+
volumes.append(get_volume(lattice_vectors))
186+
if volumes:
187+
self.volumes = volumes
177188
except Exception:
178-
pass
189+
logger.error('Error getting volume from model_system.')
190+
return
191+
192+
to_fit = self.energies is not None and self.volumes is not None
179193

180-
if self.n_points != len(self.energies) != len(self.volumes):
181-
logger.error('Iconsistent size of energies and volumes.')
194+
if to_fit and not (self.n_points == len(self.energies) == len(self.volumes)):
195+
logger.error('Inconsistent size of energies and volumes.')
196+
to_fit = False
182197

183-
if (
184-
not self.eos_fit
185-
and self.results.volumes is not None
186-
and self.results.energies is not None
187-
):
198+
if not self.eos_fit and to_fit:
188199
# convert to ase units in order for function optimization to work
189-
volumes = self.results.volumes.to('angstrom ** 3').magnitude
190-
energies = self.results.energies.to('eV').magnitude
200+
volumes = self.volumes.to('angstrom ** 3').magnitude
201+
energies = self.energies.to('eV').magnitude
191202
for function_name, ase_name in FUNCTION_NAMES.items():
192203
try:
193204
eos = aseEOS(volumes, energies, ase_name)
@@ -202,7 +213,7 @@ def normalize(self, archive: EntryArchive, logger: BoundLogger) -> None:
202213
equilibrium_energy=eos.e0 * ureg.eV,
203214
rms_error=rms_error,
204215
)
205-
self.results.eos_fit.append(eos_fit)
216+
self.eos_fit.append(eos_fit)
206217
except Exception:
207218
logger.warning('EOS fit unsuccesful.')
208219

@@ -212,7 +223,7 @@ class EquationOfState(ParallelWorkflow):
212223
Definitions for equation of state workflow.
213224
"""
214225

215-
task_label = 'Volume'
226+
_task_label = 'Volume'
216227

217228
@log
218229
def map_inputs(self, archive: EntryArchive) -> None:

0 commit comments

Comments
 (0)