Skip to content

Commit 8db92c2

Browse files
authored
Adds orsopy integration for .ort files and ORSO models (#131)
* written ort file reader * made model parsing only if the model is there * fixed union method * added orso integration notebook * added tests * added pint to requirements and added orso extras install * backwards compatibility * removed to-project and updated notebook * fixed print formatting * review fixes * fixed repeat names * fix iterator * new data * updated example data and better name resolution * removed some data * review fixes
1 parent 8746e33 commit 8db92c2

13 files changed

+1225
-4
lines changed

.github/workflows/run_tests.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,5 @@ jobs:
5353
- name: Install and Test with pytest
5454
run: |
5555
export PATH="$pythonLocation:$PATH"
56-
python -m pip install -e .[Dev]
56+
python -m pip install -e .[Dev,Orso]
5757
pytest tests/ --cov=RATapi --cov-report=term

RATapi/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
from RATapi.controls import Controls
77
from RATapi.project import Project
88
from RATapi.run import run
9-
from RATapi.utils import convert, plotting
9+
from RATapi.utils import convert, orso, plotting
1010

11-
__all__ = ["examples", "models", "events", "ClassList", "Controls", "Project", "run", "plotting", "convert"]
11+
__all__ = ["examples", "models", "events", "ClassList", "Controls", "Project", "run", "plotting", "convert", "orso"]

RATapi/classlist.py

+13
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,19 @@ def extend(self, other: Sequence[T]) -> None:
296296
self._check_unique_name_fields(other)
297297
self.data.extend(other)
298298

299+
def union(self, other: Sequence[T]) -> None:
300+
"""Extend the ClassList by a sequence, ignoring input items with names that already exist."""
301+
if other and not (isinstance(other, Sequence) and not isinstance(other, str)):
302+
other = [other]
303+
304+
self.extend(
305+
[
306+
item
307+
for item in other
308+
if hasattr(item, self.name_field) and getattr(item, self.name_field) not in self.get_names()
309+
]
310+
)
311+
299312
def set_fields(self, index: Union[int, slice, str, T], **kwargs) -> None:
300313
"""Assign the values of an existing object's attributes using keyword arguments."""
301314
self._validate_name_field(kwargs)

RATapi/examples/data/c_PLP0011859_q.ort

+441
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# ``orsopy`` Integration"
8+
]
9+
},
10+
{
11+
"cell_type": "markdown",
12+
"metadata": {},
13+
"source": [
14+
"``python-RAT`` contains some integration with ``orsopy``, allowing for convenient interaction with the ``.ort`` file format. This integration is available through the `RATapi.utils.orso` submodule."
15+
]
16+
},
17+
{
18+
"cell_type": "code",
19+
"execution_count": null,
20+
"metadata": {},
21+
"outputs": [],
22+
"source": [
23+
"import RATapi.utils.orso"
24+
]
25+
},
26+
{
27+
"cell_type": "markdown",
28+
"metadata": {},
29+
"source": [
30+
"## Creating models from the ORSO model description language\n",
31+
"\n",
32+
"The [ORSO model description format](https://www.reflectometry.org/advanced_and_expert_level/file_format/simple_model) allows the description of a standard slab model as a one-line string, provided that all the layer materials are defined [in the ORSO SLD database](https://slddb.esss.dk/slddb/).\n",
33+
"\n",
34+
"The function `RATapi.utils.orso.orso_model_to_rat` function can read a model and return an `ORSOSample` dataclass, which gives bulk in and bulk out parameters for the model, a list of all layers defined in the model, and all the parameters needed to define those layers as RAT models. \n",
35+
"\n",
36+
"**Note:** the ORSO format gives the thicknesses of materials in *nanometres*. When we convert them to RAT parameters, the units will be converted to Angstroms.\n",
37+
"\n",
38+
"For example, the string `air | Ni 100 | SiO2 0.5 | Si` describes a 1000 angstrom nickel film backed by a 5 angstrom silicon oxide layer. The bulk-in and bulk-out are air and silicon respectively. The roughnesses and SLDs will be calculated or taken from the ORSO SLD database."
39+
]
40+
},
41+
{
42+
"cell_type": "code",
43+
"execution_count": null,
44+
"metadata": {},
45+
"outputs": [],
46+
"source": [
47+
"# create the RAT parameters and layers from this model\n",
48+
"sample = RATapi.utils.orso.orso_model_to_rat(\"air | Ni 100 | SiO2 0.5 | Si\")\n",
49+
"print(sample)"
50+
]
51+
},
52+
{
53+
"cell_type": "markdown",
54+
"metadata": {},
55+
"source": [
56+
"You can also set `absorption=True` and the model will account for absorption. For example if we change the nickel film for a boron carbide film and want to account for its relatively high absorption, we can add it to the output:"
57+
]
58+
},
59+
{
60+
"cell_type": "code",
61+
"execution_count": null,
62+
"metadata": {},
63+
"outputs": [],
64+
"source": [
65+
"sample = RATapi.utils.orso.orso_model_to_rat(\"vacuum | B4C 100 | SiO2 0.5 | Si\", absorption=True)\n",
66+
"print(sample)"
67+
]
68+
},
69+
{
70+
"cell_type": "markdown",
71+
"metadata": {},
72+
"source": [
73+
"Finally, ORSO supports defining repeated layers using parentheses. For example, if we had a polarising multilayer of 5 repetitions of 70 angstrom silicon and 70 angstrom iron, we could represent it as `air | 5 ( Si 7 | Fe 7 ) | Si`.\n",
74+
"\n",
75+
"RAT will only create the number of layers and parameters necessary, but the `ORSOSample` object's `model` attribute will give a list of layer names with the structure of the model preserved, which can be given as the layer model for a Contrast."
76+
]
77+
},
78+
{
79+
"cell_type": "code",
80+
"execution_count": null,
81+
"metadata": {},
82+
"outputs": [],
83+
"source": [
84+
"sample = RATapi.utils.orso.orso_model_to_rat(\"air | 5 ( Si 7 | Fe 7 ) | Si\")\n",
85+
"print(sample)"
86+
]
87+
},
88+
{
89+
"cell_type": "markdown",
90+
"metadata": {},
91+
"source": [
92+
"## Reading in data and models from .ort files"
93+
]
94+
},
95+
{
96+
"cell_type": "markdown",
97+
"metadata": {},
98+
"source": [
99+
"RAT can also load both data and model information from an .ort file. This is done through the `ORSOProject` object, which takes a file path and can also optionally account for absorption.\n",
100+
"\n",
101+
"The example data file we use here is example data for an unknown film on deposited on silicon."
102+
]
103+
},
104+
{
105+
"cell_type": "code",
106+
"execution_count": null,
107+
"metadata": {},
108+
"outputs": [],
109+
"source": [
110+
"import pathlib\n",
111+
"data_path = pathlib.Path(\"../data\")\n",
112+
"\n",
113+
"orso_data = RATapi.utils.orso.ORSOProject(data_path / \"c_PLP0011859_q.ort\")\n",
114+
"print(orso_data)"
115+
]
116+
},
117+
{
118+
"cell_type": "markdown",
119+
"metadata": {},
120+
"source": [
121+
"The `ORSOProject` object contains two lists: `ORSOProject.data` and `ORSOProject.samples`. The former is a list of Data objects with each dataset defined in the file, and the latter is a list of `ORSOSample` objects (like above) with model information. Note that if the .ort file does not define a model for a dataset, that index of `ORSOProject.samples` will be None.\n",
122+
"\n",
123+
"It's then easy to access this data to create a RAT `Project` that represents our data."
124+
]
125+
},
126+
{
127+
"cell_type": "code",
128+
"execution_count": null,
129+
"metadata": {},
130+
"outputs": [],
131+
"source": [
132+
"from RATapi.models import Background, Contrast, Parameter, Resolution\n",
133+
"\n",
134+
"dataset = orso_data.data[0]\n",
135+
"sample = orso_data.samples[0]\n",
136+
"\n",
137+
"project = RATapi.Project(\n",
138+
" name = \"Example Project\",\n",
139+
" geometry = \"substrate/liquid\",\n",
140+
" parameters = sample.parameters,\n",
141+
" bulk_in = [sample.bulk_in],\n",
142+
" bulk_out = [sample.bulk_out],\n",
143+
" scalefactors = [Parameter(name=\"Scalefactor\", min=0, value=0.34, max=1.5)],\n",
144+
" background_parameters = [Parameter(name=\"Background Parameter\", min=0, value=2e-6, max=1)],\n",
145+
" backgrounds = [Background(name=\"Background\", type=\"constant\", source=\"Background Parameter\")],\n",
146+
" resolutions = [Resolution(name=\"Data Resolution\", type=\"data\")],\n",
147+
" data = [dataset],\n",
148+
" layers = sample.layers,\n",
149+
" contrasts = [Contrast(\n",
150+
" name = \"prist4\",\n",
151+
" data = dataset.name,\n",
152+
" background = \"Background\",\n",
153+
" bulk_in = sample.bulk_in.name,\n",
154+
" bulk_out = sample.bulk_out.name,\n",
155+
" scalefactor = \"Scalefactor\",\n",
156+
" resolution = \"Data Resolution\",\n",
157+
" model = sample.model,\n",
158+
" )]\n",
159+
")\n",
160+
"\n",
161+
"controls = RATapi.Controls()\n",
162+
"project, results = RATapi.run(project, controls)\n",
163+
"RATapi.plotting.plot_ref_sld(project, results)"
164+
]
165+
}
166+
],
167+
"metadata": {
168+
"kernelspec": {
169+
"display_name": ".venv",
170+
"language": "python",
171+
"name": "python3"
172+
},
173+
"language_info": {
174+
"codemirror_mode": {
175+
"name": "ipython",
176+
"version": 3
177+
},
178+
"file_extension": ".py",
179+
"mimetype": "text/x-python",
180+
"name": "python",
181+
"nbconvert_exporter": "python",
182+
"pygments_lexer": "ipython3",
183+
"version": "3.11.2"
184+
}
185+
},
186+
"nbformat": 4,
187+
"nbformat_minor": 2
188+
}

0 commit comments

Comments
 (0)