Skip to content

Stand-alone Usage

Purpose

Understand how to instantiate schema classes and populate quantities directly in Python, without parser-plugin complexity. The page ends with a short normalization-methods intro.

Assignment 2.1: Simulation timing basics

Assignment 2.1

Create a Simulation instance, assign cpu1_start=0 s and cpu1_end=24 min 30 s, and compute elapsed time in seconds and hours.

Solution 2.1
from nomad.units import ureg

from nomad_simulations.schema_packages.general import Simulation

simulation = Simulation()
simulation.cpu1_start = 0 * ureg.second
simulation.cpu1_end = 30 * ureg.second + 24 * ureg.minute

elapsed_seconds = (simulation.cpu1_end - simulation.cpu1_start).to('second').magnitude
elapsed_hours = (simulation.cpu1_end - simulation.cpu1_start).to('hour').magnitude

assert elapsed_seconds == 1470
assert round(elapsed_hours, 6) == round(1470 / 3600, 6)

Assignment 2.2: Program typing and composition

Assignment 2.2

Create a Program(name='VASP', version='5.0.0'), attach it to Simulation.program, then assign an integer to version and inspect the stored result.

Solution 2.2
from nomad_simulations.schema_packages.general import Program, Simulation

simulation = Simulation()
program = Program(name='VASP', version='5.0.0')
simulation.program = program

assert simulation.program.name == 'VASP'
assert simulation.program.version == '5.0.0'

# Compatible scalar inputs are auto-converted to string when possible.
program.version = 5
assert program.version == '5'

Assignment 2.3: DFT under Simulation.model_method

Assignment 2.3

Instantiate a DFT method and append it to Simulation.model_method. Use DFT.xc (XCFunctional) to represent XC identity in the current schema.

Solution 2.3
from nomad_simulations.schema_packages.general import Simulation
from nomad_simulations.schema_packages.model_method import DFT, XCFunctional

simulation = Simulation()
dft = DFT(name='DFT', type='KS', xc=XCFunctional(functional_key='PBE'))
simulation.model_method.append(dft)

assert isinstance(simulation.model_method[0], DFT)
assert simulation.model_method[0].xc.functional_key == 'PBE'

Assignment 2.4: SelfConsistency in numerical_settings

Assignment 2.4

Add SelfConsistency(threshold_change=1e-3, threshold_change_unit='joule') under DFT.numerical_settings and verify parent-child linkage.

Solution 2.4
from nomad_simulations.schema_packages.model_method import DFT
from nomad_simulations.schema_packages.numerical_settings import SelfConsistency

scf = SelfConsistency(threshold_change=1e-3, threshold_change_unit='joule')
dft = DFT(name='DFT', type='KS', numerical_settings=[scf])

assert dft.numerical_settings[0].threshold_change == 1e-3
assert dft.numerical_settings[0].threshold_change_unit == 'joule'
assert scf.m_parent is dft

Assignment 2.5: AtomsState and atomic numbers

Assignment 2.5

Create AtomsState entries for Ga and As and resolve atomic numbers.

Solution 2.5
from nomad import utils

from nomad_simulations.schema_packages.atoms_state import AtomsState

logger = utils.get_logger(__name__)
atoms_states = [AtomsState(chemical_symbol=symbol) for symbol in ['Ga', 'As']]
atomic_numbers = [atom.resolve_atomic_number(logger=logger) for atom in atoms_states]

assert atomic_numbers == [31, 33]

Assignment 2.6: ModelSystem, particle_states, and chemical formulas

Assignment 2.6

Build a representative ModelSystem with positions, lattice vectors, and particle_states, then normalize and inspect formula variants.

Solution 2.6
import numpy as np
from nomad import utils
from nomad.datamodel import EntryArchive
from nomad.units import ureg

from nomad_simulations.schema_packages.atoms_state import AtomsState
from nomad_simulations.schema_packages.model_system import ModelSystem

logger = utils.get_logger(__name__)

model_system = ModelSystem(is_representative=True)
model_system.lattice_vectors = np.eye(3) * 5.65 * ureg.angstrom
model_system.periodic_boundary_conditions = [True, True, True]
model_system.positions = np.array([[0, 0, 0], [2.825, 2.825, 2.825]]) * ureg.angstrom
model_system.particle_states.append(AtomsState(chemical_symbol='Ga'))
model_system.particle_states.append(AtomsState(chemical_symbol='As'))

model_system.normalize(archive=EntryArchive(), logger=logger)

assert model_system.chemical_formula is not None
assert model_system.chemical_formula.iupac == 'GaAs'
assert model_system.chemical_formula.hill == 'AsGa'
assert model_system.chemical_formula.anonymous == 'AB'

Current schema terminology

Legacy AtomicCell/cell/atoms_state patterns are replaced by direct ModelSystem geometry (positions, lattice_vectors, periodic_boundary_conditions) and per-particle data in particle_states.

Assignment 2.7: Outputs, references, and SCF deltas

Assignment 2.7

Create an Outputs section with SCFSteps and TotalEnergy values across steps, normalize, and verify automatic model_system_ref, model_method_ref, and delta_energies_total population.

Solution 2.7
import numpy as np
from nomad import utils
from nomad.datamodel import EntryArchive
from nomad.units import ureg

from nomad_simulations.schema_packages.atoms_state import AtomsState
from nomad_simulations.schema_packages.general import Simulation
from nomad_simulations.schema_packages.model_method import DFT
from nomad_simulations.schema_packages.model_system import ModelSystem
from nomad_simulations.schema_packages.numerical_settings import SelfConsistency
from nomad_simulations.schema_packages.outputs import Outputs, SCFSteps
from nomad_simulations.schema_packages.properties import TotalEnergy

logger = utils.get_logger(__name__)

simulation = Simulation()

model_system = ModelSystem(is_representative=True)
model_system.lattice_vectors = np.eye(3) * 5.0 * ureg.angstrom
model_system.periodic_boundary_conditions = [True, True, True]
model_system.positions = np.array([[0, 0, 0], [1.25, 1.25, 1.25]]) * ureg.angstrom
model_system.particle_states.append(AtomsState(chemical_symbol='Si'))
model_system.particle_states.append(AtomsState(chemical_symbol='Si'))
simulation.model_system.append(model_system)

method = DFT(
    name='DFT',
    type='KS',
    numerical_settings=[
        SelfConsistency(threshold_change=1e-6, threshold_change_unit='joule')
    ],
)
simulation.model_method.append(method)

outputs = Outputs(scf_steps=SCFSteps())
for value in [1.0, 1.5, 2.0, 2.1, 2.101]:
    outputs.total_energies.append(TotalEnergy(value=value * ureg.eV))
simulation.outputs.append(outputs)

outputs.normalize(archive=EntryArchive(), logger=logger)

assert outputs.model_system_ref is simulation.model_system[0]
assert outputs.model_method_ref is simulation.model_method[0]
assert outputs.scf_steps is not None
assert outputs.scf_steps.delta_energies_total is not None
assert len(outputs.scf_steps.delta_energies_total) == 4
first_delta_joule = outputs.scf_steps.delta_energies_total[0].to('joule').magnitude
assert np.isclose(first_delta_joule, (0.5 * ureg.eV).to('joule').magnitude)

Assignment 2.8: output-property collections and spin channels

Assignment 2.8

Store scalar and spin-resolved ElectronicBandGap entries in Outputs and extract spin-polarized entries with extract_spin_polarized_property.

Solution 2.8
from nomad.units import ureg

from nomad_simulations.schema_packages.outputs import Outputs
from nomad_simulations.schema_packages.properties import ElectronicBandGap

outputs = Outputs()
outputs.electronic_band_gaps.append(ElectronicBandGap(value=2.0 * ureg.eV))
outputs.electronic_band_gaps.append(
    ElectronicBandGap(value=1.8 * ureg.eV, spin_channel=0)
)
outputs.electronic_band_gaps.append(
    ElectronicBandGap(value=1.9 * ureg.eV, spin_channel=1)
)

spin_polarized = outputs.extract_spin_polarized_property('electronic_band_gaps')

assert len(outputs.electronic_band_gaps) == 3
assert len(spin_polarized) == 2
assert {gap.spin_channel for gap in spin_polarized} == {0, 1}

About variable-dependent properties

The old generic variables pattern previously shown for ElectronicBandGap is not the current design. Variable grids are now represented by property-specific sections (for example DOS/ spectra energy grids).

Normalization Exercises

The following exercises focus on practical normalization behavior from current tests.

Exercise N1: representative ModelSystem normalization

Exercise N1

Normalize a representative atomic ModelSystem and confirm that derived fields (type, chemical_formula) are populated.

Solution N1
import numpy as np
from nomad import utils
from nomad.datamodel import EntryArchive
from nomad.units import ureg

from nomad_simulations.schema_packages.atoms_state import AtomsState
from nomad_simulations.schema_packages.model_system import ModelSystem

logger = utils.get_logger(__name__)

# Pattern based on tests/test_model_system.py normalization behavior.
model_system = ModelSystem(is_representative=True)
model_system.lattice_vectors = np.eye(3) * 5.43 * ureg.angstrom
model_system.periodic_boundary_conditions = [True, True, True]
model_system.positions = (
    np.array([[0.0, 0.0, 0.0], [1.3575, 1.3575, 1.3575]]) * ureg.angstrom
)
model_system.particle_states.append(AtomsState(chemical_symbol='Si'))
model_system.particle_states.append(AtomsState(chemical_symbol='Si'))

model_system.normalize(EntryArchive(), logger)

assert model_system.type == 'bulk'
assert model_system.chemical_formula is not None
assert model_system.chemical_formula.reduced == 'Si'

Exercise N2: Outputs SCF deltas from total energies

Exercise N2

Populate only total_energies and run Outputs.normalize(...) to confirm that scf_steps.delta_energies_total is computed automatically.

Solution N2
import numpy as np
from nomad import utils
from nomad.datamodel import EntryArchive
from nomad.units import ureg

from nomad_simulations.schema_packages.outputs import Outputs, SCFSteps
from nomad_simulations.schema_packages.properties import TotalEnergy

logger = utils.get_logger(__name__)

# Pattern based on tests/workflow/test_convergence_targets.py.
outputs = Outputs(scf_steps=SCFSteps())
outputs.total_energies.append(TotalEnergy(value=1.0 * ureg.eV))
outputs.total_energies.append(TotalEnergy(value=1.5 * ureg.eV))
outputs.total_energies.append(TotalEnergy(value=2.1 * ureg.eV))

outputs.normalize(EntryArchive(), logger)

assert outputs.scf_steps is not None
assert outputs.scf_steps.delta_energies_total is not None
assert len(outputs.scf_steps.delta_energies_total) == 2
expected_first = (0.5 * ureg.eV).to('joule').magnitude
assert np.isclose(
    outputs.scf_steps.delta_energies_total[0].to('joule').magnitude, expected_first
)

For full normalization execution-order rules, see Normalization.