diff --git a/HISTORY.rst b/HISTORY.rst index 1f3a68e7..a9ddc7ab 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,6 +2,21 @@ History ======= +1.1.0 (2023-02-20) +------------------ + +* Introduction of simple model language that can be used to describe + sample structures. The module *orsopy.fileio.model_language* is used to implement + and parse the model language. + See https://www.reflectometry.org/projects/simple_model for specifications. + Sample model examples can be found in the examples folder together + with scripts using the orsopy module to parse and plot the data. +* Add polarization channels for x-ray experiments +* Implement ErrorValue class for optional description of errors + on values within the file header. +* Update of .ort standard according to discussions with community. + (E.g. rename of column attribute "dimension" to "physical_quantity") + 1.0.1 (2022-06-28) ------------------ @@ -9,6 +24,8 @@ History * Update the schema files for released .ort standard. * Sample.sample_parameters keys to be strings and values restricted to Value, ValueRange, ValueVector or ComplexValue. +* Add *as_unit* method to value classes that uses the *pint* library to convert + values to supplied unit automatically. 1.0.0 (2022-06-10) ------------------ diff --git a/examples/simple_model_example_3.yml b/examples/simple_model_example_3.yml index 76e28642..caa04301 100644 --- a/examples/simple_model_example_3.yml +++ b/examples/simple_model_example_3.yml @@ -6,10 +6,10 @@ sample: substrate: sequence: - material: Si - sigma: 2 + roughness: 2 - material: SiO2 thickness: 5 - sigma: 3 + roughness: 3 film: repetitions: 5 stack: head_group 4 | tail | tail | head_group 4 @@ -35,7 +35,7 @@ sample: H2O: 0.3 D2O: 0.7 globals: - sigma: {magnitude: 5, unit: angstrom} + roughness: {magnitude: 5, unit: angstrom} length_unit: angstrom mass_density_unit: g/cm^3 sld_unit: 1/angstrom^2 diff --git a/orsopy/__init__.py b/orsopy/__init__.py index a5923eec..02f80e2a 100644 --- a/orsopy/__init__.py +++ b/orsopy/__init__.py @@ -1,3 +1,3 @@ """Top-level package for orsopy.""" -__version__ = "1.0.1" +__version__ = "1.1.0" diff --git a/orsopy/dataclasses.py b/orsopy/dataclasses.py index b4174745..a66bc5ea 100644 --- a/orsopy/dataclasses.py +++ b/orsopy/dataclasses.py @@ -9,3 +9,14 @@ def _field_init(f, frozen, locals, self_name): return _field_init_real(f, frozen, locals, self_name, False) + + +elif sys.version_info < (3, 7, 0): + # fix bug in python 3.6 when using default_factory for dataclass objects + _orig_field = field + + def field(*args, **opts): + if "default_factory" in opts and not opts["default_factory"] in [list, tuple]: + return opts["default_factory"]() + else: + return _orig_field(*args, **opts) diff --git a/orsopy/fileio/model_language.py b/orsopy/fileio/model_language.py index cb89feea..dca1d827 100644 --- a/orsopy/fileio/model_language.py +++ b/orsopy/fileio/model_language.py @@ -8,6 +8,7 @@ from typing import Any, Dict, List, Optional, Union +from ..dataclasses import field from ..utils.chemical_formula import Formula from ..utils.density_resolver import DensityResolver from .base import ComplexValue, Header, Value, orsodataclass @@ -26,12 +27,12 @@ def find_idx(string, start, value): @orsodataclass class ModelParameters(Header): - roughness: Optional[Value] = Value(0.3, "nm") - length_unit: Optional[str] = "nm" - mass_density_unit: Optional[str] = "g/cm^3" - number_density_unit: Optional[str] = "1/nm^3" - sld_unit: Optional[str] = "1/angstrom^2" - magnetic_moment_unit: Optional[str] = "muB" + roughness: Value = field(default_factory=lambda: Value(0.3, "nm")) + length_unit: str = "nm" + mass_density_unit: str = "g/cm^3" + number_density_unit: str = "1/nm^3" + sld_unit: str = "1/angstrom^2" + magnetic_moment_unit: str = "muB" @orsodataclass @@ -152,6 +153,8 @@ def get_sld(self, xray_energy=None) -> complex: class Composit(Header): composition: Dict[str, float] + original_name = None + def resolve_names(self, resolvable_items): self._composition_materials = {} for key, value in self.composition.items(): @@ -288,6 +291,8 @@ class SubStack(Header): arguments: Optional[List[Any]] = None keywords: Optional[Dict[str, Any]] = None + original_name = None + def resolve_names(self, resolvable_items): if self.stack is None and self.sequence is None: raise ValueError("SubStack has to either define stack or sequence") @@ -319,6 +324,7 @@ def resolve_names(self, resolvable_items): obj.thickness = thickness else: obj = Layer(material=item, thickness=thickness) + obj.original_name = item if hasattr(obj, "resolve_names"): obj.resolve_names(resolvable_items) output.append(obj) @@ -375,6 +381,8 @@ def __post_init__(self): def resolvable_items(self): output = {} if self.sub_stacks: + for key, ssi in self.sub_stacks.items(): + ssi.original_name = key output.update(self.sub_stacks) if self.layers: for key, li in self.layers.items(): @@ -385,6 +393,8 @@ def resolvable_items(self): mi.original_name = key output.update(self.materials) if self.composits: + for key, ci in self.composits.items(): + ci.original_name = key output.update(self.composits) return output @@ -421,6 +431,7 @@ def resolve_stack(self): obj.thickness = thickness else: obj = Layer(material=item, thickness=thickness) + obj.original_name = item if hasattr(obj, "resolve_names"): obj.resolve_names(ri) if hasattr(obj, "resolve_defaults"): diff --git a/orsopy/fileio/schema/refl_header.schema.json b/orsopy/fileio/schema/refl_header.schema.json index a187bfe0..e3700221 100644 --- a/orsopy/fileio/schema/refl_header.schema.json +++ b/orsopy/fileio/schema/refl_header.schema.json @@ -384,6 +384,361 @@ "real" ] }, + "Material": { + "title": "Material", + "type": "object", + "properties": { + "formula": { + "type": [ + "string", + "null" + ] + }, + "mass_density": { + "anyOf": [ + { + "type": "number" + }, + { + "$ref": "#/definitions/Value" + }, + { + "type": "null" + } + ] + }, + "number_density": { + "anyOf": [ + { + "type": "number" + }, + { + "$ref": "#/definitions/Value" + }, + { + "type": "null" + } + ] + }, + "sld": { + "anyOf": [ + { + "type": "number" + }, + { + "$ref": "#/definitions/ComplexValue" + }, + { + "$ref": "#/definitions/Value" + }, + { + "type": "null" + } + ] + }, + "magnetic_moment": { + "anyOf": [ + { + "type": "number" + }, + { + "$ref": "#/definitions/Value" + }, + { + "type": "null" + } + ] + }, + "relative_density": { + "type": [ + "number", + "null" + ] + }, + "comment": { + "type": [ + "string", + "null" + ] + } + } + }, + "Composit": { + "title": "Composit", + "type": "object", + "properties": { + "composition": { + "additionalProperties": { + "type": "number" + }, + "type": [ + "object", + "null" + ] + }, + "comment": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "composition" + ] + }, + "Layer": { + "title": "Layer", + "type": "object", + "properties": { + "thickness": { + "anyOf": [ + { + "type": "number" + }, + { + "$ref": "#/definitions/Value" + }, + { + "type": "null" + } + ] + }, + "roughness": { + "anyOf": [ + { + "type": "number" + }, + { + "$ref": "#/definitions/Value" + }, + { + "type": "null" + } + ] + }, + "material": { + "anyOf": [ + { + "$ref": "#/definitions/Material" + }, + { + "$ref": "#/definitions/Composit" + }, + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "composition": { + "additionalProperties": { + "type": "number" + }, + "type": [ + "object", + "null" + ] + }, + "comment": { + "type": [ + "string", + "null" + ] + } + } + }, + "SubStack": { + "title": "SubStack", + "type": "object", + "properties": { + "repetitions": { + "default": 1, + "type": [ + "integer", + "null" + ] + }, + "stack": { + "type": [ + "string", + "null" + ] + }, + "sequence": { + "items": { + "$ref": "#/definitions/Layer" + }, + "type": [ + "array", + "null" + ] + }, + "represents": { + "type": [ + "string", + "null" + ] + }, + "arguments": { + "items": {}, + "type": [ + "array", + "null" + ] + }, + "keywords": { + "type": [ + "object", + "null" + ] + }, + "comment": { + "type": [ + "string", + "null" + ] + } + } + }, + "ModelParameters": { + "title": "ModelParameters", + "type": "object", + "properties": { + "roughness": { + "default": { + "magnitude": 0.3, + "unit": "nm", + "error": null, + "comment": null + }, + "allOf": [ + { + "$ref": "#/definitions/Value" + } + ] + }, + "length_unit": { + "default": "nm", + "type": [ + "string", + "null" + ] + }, + "mass_density_unit": { + "default": "g/cm^3", + "type": [ + "string", + "null" + ] + }, + "number_density_unit": { + "default": "1/nm^3", + "type": [ + "string", + "null" + ] + }, + "sld_unit": { + "default": "1/angstrom^2", + "type": [ + "string", + "null" + ] + }, + "magnetic_moment_unit": { + "default": "muB", + "type": [ + "string", + "null" + ] + }, + "comment": { + "type": [ + "string", + "null" + ] + } + } + }, + "SampleModel": { + "title": "SampleModel", + "type": "object", + "properties": { + "stack": { + "type": [ + "string", + "null" + ] + }, + "origin": { + "type": [ + "string", + "null" + ] + }, + "sub_stacks": { + "additionalProperties": { + "$ref": "#/definitions/SubStack" + }, + "type": [ + "object", + "null" + ] + }, + "layers": { + "additionalProperties": { + "$ref": "#/definitions/Layer" + }, + "type": [ + "object", + "null" + ] + }, + "materials": { + "additionalProperties": { + "$ref": "#/definitions/Material" + }, + "type": [ + "object", + "null" + ] + }, + "composits": { + "additionalProperties": { + "$ref": "#/definitions/Composit" + }, + "type": [ + "object", + "null" + ] + }, + "globals": { + "anyOf": [ + { + "$ref": "#/definitions/ModelParameters" + } + ] + }, + "reference": { + "type": [ + "string", + "null" + ] + }, + "comment": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "stack" + ] + }, "Sample": { "title": "Sample", "type": "object", @@ -451,6 +806,13 @@ "null" ] }, + "model": { + "anyOf": [ + { + "$ref": "#/definitions/SampleModel" + } + ] + }, "comment": { "type": [ "string", diff --git a/orsopy/fileio/schema/refl_header.schema.yaml b/orsopy/fileio/schema/refl_header.schema.yaml index d436e34e..afb82162 100644 --- a/orsopy/fileio/schema/refl_header.schema.yaml +++ b/orsopy/fileio/schema/refl_header.schema.yaml @@ -51,6 +51,22 @@ definitions: - real title: ComplexValue type: object + Composit: + properties: + comment: + type: + - string + - 'null' + composition: + additionalProperties: + type: number + type: + - object + - 'null' + required: + - composition + title: Composit + type: object DataSource: properties: comment: @@ -256,6 +272,73 @@ definitions: - wavelength title: InstrumentSettings type: object + Layer: + properties: + comment: + type: + - string + - 'null' + composition: + additionalProperties: + type: number + type: + - object + - 'null' + material: + anyOf: + - $ref: '#/definitions/Material' + - $ref: '#/definitions/Composit' + - type: string + - type: 'null' + roughness: + anyOf: + - type: number + - $ref: '#/definitions/Value' + - type: 'null' + thickness: + anyOf: + - type: number + - $ref: '#/definitions/Value' + - type: 'null' + title: Layer + type: object + Material: + properties: + comment: + type: + - string + - 'null' + formula: + type: + - string + - 'null' + magnetic_moment: + anyOf: + - type: number + - $ref: '#/definitions/Value' + - type: 'null' + mass_density: + anyOf: + - type: number + - $ref: '#/definitions/Value' + - type: 'null' + number_density: + anyOf: + - type: number + - $ref: '#/definitions/Value' + - type: 'null' + relative_density: + type: + - number + - 'null' + sld: + anyOf: + - type: number + - $ref: '#/definitions/ComplexValue' + - $ref: '#/definitions/Value' + - type: 'null' + title: Material + type: object Measurement: properties: additional_files: @@ -295,6 +378,47 @@ definitions: - data_files title: Measurement type: object + ModelParameters: + properties: + comment: + type: + - string + - 'null' + length_unit: + default: nm + type: + - string + - 'null' + magnetic_moment_unit: + default: muB + type: + - string + - 'null' + mass_density_unit: + default: g/cm^3 + type: + - string + - 'null' + number_density_unit: + default: 1/nm^3 + type: + - string + - 'null' + roughness: + allOf: + - $ref: '#/definitions/Value' + default: + comment: null + error: null + magnitude: 0.3 + unit: nm + sld_unit: + default: 1/angstrom^2 + type: + - string + - 'null' + title: ModelParameters + type: object Person: properties: affiliation: @@ -483,6 +607,9 @@ definitions: type: - array - 'null' + model: + anyOf: + - $ref: '#/definitions/SampleModel' name: type: - string @@ -505,6 +632,55 @@ definitions: - name title: Sample type: object + SampleModel: + properties: + comment: + type: + - string + - 'null' + composits: + additionalProperties: + $ref: '#/definitions/Composit' + type: + - object + - 'null' + globals: + anyOf: + - $ref: '#/definitions/ModelParameters' + layers: + additionalProperties: + $ref: '#/definitions/Layer' + type: + - object + - 'null' + materials: + additionalProperties: + $ref: '#/definitions/Material' + type: + - object + - 'null' + origin: + type: + - string + - 'null' + reference: + type: + - string + - 'null' + stack: + type: + - string + - 'null' + sub_stacks: + additionalProperties: + $ref: '#/definitions/SubStack' + type: + - object + - 'null' + required: + - stack + title: SampleModel + type: object Software: properties: comment: @@ -527,6 +703,42 @@ definitions: - name title: Software type: object + SubStack: + properties: + arguments: + items: {} + type: + - array + - 'null' + comment: + type: + - string + - 'null' + keywords: + type: + - object + - 'null' + repetitions: + default: 1 + type: + - integer + - 'null' + represents: + type: + - string + - 'null' + sequence: + items: + $ref: '#/definitions/Layer' + type: + - array + - 'null' + stack: + type: + - string + - 'null' + title: SubStack + type: object Value: properties: comment: diff --git a/tests/test_fileio/test_model_language.py b/tests/test_fileio/test_model_language.py index 186fcf6f..57d657f4 100644 --- a/tests/test_fileio/test_model_language.py +++ b/tests/test_fileio/test_model_language.py @@ -358,7 +358,7 @@ def test_resolve_stack(self): stack = sm.resolve_stack() assert len(stack) == 1 assert stack[0] == ml.Layer( - ml.Value(0.0, "nm"), roughness=ml.ModelParameters.roughness, material=ml.Material(formula="Si") + ml.Value(0.0, "nm"), roughness=defaults.roughness, material=ml.Material(formula="Si") ) def test_resolve_to_layers(self):