Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions tests/missing_dspacing_angle.xdi
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# XDI/1.0 EDC/5.02
# Beamline.name: X11A
# Column.1: angle deg
# Column.2: mutrans
# Element.edge: K
# Element.symbol: Cu
# Facility.energy: 2.584 GeV
# Facility.name: NSLS
# Facility.xray_source: bend magnet
# Mono.name: Si(111)
# Sample.name: Cu
# Sample.prep: Cu metal foil
# Sample.temperature: 10 K
# Scan.edge_energy: 8980.0 eV
# Scan.start_time: 1992-09-15T01:52:53
# ///
# Cu foil, 10K — angle-abscissa fixture (missing Mono.d_spacing on purpose)
# ---
# angle mutrans
.1234500E+02 .1013661E+01
.1234600E+02 .9949864E+00
.1234700E+02 .9900679E+00
8 changes: 6 additions & 2 deletions tests/test_MissingFields.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,9 @@ def test_missing_element(self):
self.assertIn("element", self.errors)
self.assertEqual(2, len(self.errors["element"]))

def test_missing_mono(self):
self.assertIn("mono", self.errors)
def test_mono_dspacing_not_required_for_energy_abscissa(self):
# missing_fields.xdi has Column.1: energy — per XDI/1.0 spec,
# Mono.d_spacing is only required when the abscissa is
# monochromator angle or encoder step count. So no mono error
# should fire despite Mono.d_spacing being absent.
self.assertNotIn("mono", self.errors)
49 changes: 49 additions & 0 deletions tests/test_MonoDspacingConditional.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""XDI/1.0 spec: Mono.d_spacing is required only when the abscissa
(Column.1) is monochromator angle or encoder step count. For energy
or wavelength abscissae, Mono.d_spacing is optional.

Ref: https://github.com/XraySpectroscopy/XAS-Data-Interchange/blob/master/specification/spec.md
"""
import os
import unittest
from xdi_validator import validate


HERE = os.path.dirname(__file__)


class TestMonoDspacingConditional(unittest.TestCase):

def _validate(self, fname):
with open(os.path.join(HERE, fname), "r") as f:
errors, _data = validate(f)
return errors

def test_angle_abscissa_missing_dspacing_errors(self):
errors = self._validate("missing_dspacing_angle.xdi")
self.assertIn("mono", errors,
"Column.1: angle → Mono.d_spacing must be required")

def test_angle_abscissa_with_dspacing_ok(self):
errors = self._validate("valid_angle_dspacing.xdi")
self.assertNotIn("mono", errors,
"Column.1: angle with Mono.d_spacing present → no mono error")

def test_energy_abscissa_missing_dspacing_ok(self):
# valid.xdi has Column.1: energy and Mono.d_spacing present —
# remove the d_spacing header at read-time (in-memory) and
# confirm no mono error fires for an energy abscissa.
import io
with open(os.path.join(HERE, "valid.xdi"), "r") as f:
src = f.read()
stripped = "\n".join(
line for line in src.splitlines()
if not line.lstrip("# ").startswith("Mono.d_spacing")
)
errors, _ = validate(io.StringIO(stripped))
self.assertNotIn("mono", errors,
"Column.1: energy without Mono.d_spacing → no mono error")


if __name__ == "__main__":
unittest.main()
3 changes: 3 additions & 0 deletions tests/test_WriteToFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ def setUp(self):
self.generate_xdi_result = write_xdi(self.data, os.path.dirname(__file__) + "/generated_valid.xdi" )
self.invalid_data = copy.deepcopy(self.data)
self.invalid_data["element"].pop("symbol")
# Switch abscissa to angle so d_spacing becomes required per
# XDI/1.0 spec; then remove it to exercise the mono error path.
self.invalid_data["column"]["1"] = "angle deg"
self.invalid_data["mono"].pop("d_spacing")


Expand Down
3 changes: 3 additions & 0 deletions tests/test_WriteToFileTwo.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ def setUp(self):
self.generate_xdi_result = write_xdi(self.data, os.path.dirname(__file__) + "/generated_valid.xdi" )
self.invalid_data = copy.deepcopy(self.data)
self.invalid_data["element"].pop("symbol")
# Switch abscissa to angle so d_spacing becomes required per
# XDI/1.0 spec; then remove it to exercise the mono error path.
self.invalid_data["column"]["1"] = "angle deg"
self.invalid_data["mono"].pop("d_spacing")


Expand Down
23 changes: 23 additions & 0 deletions tests/valid_angle_dspacing.xdi
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# XDI/1.0 EDC/5.02
# Beamline.name: X11A
# Column.1: angle deg
# Column.2: mutrans
# Element.edge: K
# Element.symbol: Cu
# Facility.energy: 2.584 GeV
# Facility.name: NSLS
# Facility.xray_source: bend magnet
# Mono.d_spacing: 3.135301
# Mono.name: Si(111)
# Sample.name: Cu
# Sample.prep: Cu metal foil
# Sample.temperature: 10 K
# Scan.edge_energy: 8980.0 eV
# Scan.start_time: 1992-09-15T01:52:53
# ///
# Cu foil, 10K — angle abscissa with Mono.d_spacing present
# ---
# angle mutrans
.1234500E+02 .1013661E+01
.1234600E+02 .9949864E+00
.1234700E+02 .9900679E+00
32 changes: 31 additions & 1 deletion xdi_validator/XDI_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,6 @@ def get_schema() -> dict:
"name": {"type": "string"},
"d_spacing": {"type": "number"},
},
"required": ["d_spacing"],
},
"detector": {
"description": "Tags related to the details of the photon detection system",
Expand Down Expand Up @@ -566,6 +565,37 @@ def get_schema() -> dict:
},
},
"required": ["version", "subversion", "element", "mono"],
"allOf": [
{
"$comment": (
"XDI/1.0 spec: 'The Mono.d_spacing header field must be "
"specified if the abscissa is conveyed as monochromator "
"angle.' d_spacing is required only when Column.1 "
"(the abscissa) is angle or encoder; for energy or "
"wavelength abscissae, d_spacing is optional. "
"See https://github.com/XraySpectroscopy/"
"XAS-Data-Interchange/blob/master/specification/spec.md"
),
"if": {
"properties": {
"column": {
"properties": {
"1": {
"pattern": "^(angle|encoder)(\\s|$)"
}
},
"required": ["1"]
}
},
"required": ["column"]
},
"then": {
"properties": {
"mono": {"required": ["d_spacing"]}
}
}
}
],
}

return schemadef
Loading