Skip to content

Commit 44aeafc

Browse files
Merge pull request #67 from scipp/supermirror-efficiency
Add 2nd order polynomial supermirror efficiency function
2 parents 611e4eb + de63535 commit 44aeafc

File tree

5 files changed

+200
-27
lines changed

5 files changed

+200
-27
lines changed

docs/api-reference/index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
HalfPolarizedWorkflow
1616
PolarizationAnalysisWorkflow
1717
SupermirrorWorkflow
18+
```
1819

1920
## Classes
2021

@@ -44,6 +45,8 @@
4445
PolarizationCorrectedData
4546
Polarized
4647
Polarizer
48+
SecondDegreePolynomialEfficiency
49+
SupermirrorEfficiencyFunction
4750
Up
4851
```
4952

docs/user-guide/zoom.ipynb

Lines changed: 91 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -258,26 +258,26 @@
258258
"metadata": {},
259259
"outputs": [],
260260
"source": [
261-
"pol_workflow = pol.he3.He3CellWorkflow(in_situ=False, incoming_polarized=True)\n",
261+
"he3_workflow = pol.he3.He3CellWorkflow(in_situ=False, incoming_polarized=True)\n",
262262
"# TODO Is plus correct here, this is period 0? Do we also have minus data?\n",
263-
"pol_workflow[pol.he3.He3AnalyzerTransmissionFractionParallel] = transmission\n",
263+
"he3_workflow[pol.he3.He3AnalyzerTransmissionFractionParallel] = transmission\n",
264264
"# TODO Fake empty transmission for now, would need to load different period\n",
265-
"pol_workflow[pol.he3.He3AnalyzerTransmissionFractionAntiParallel] = transmission[\n",
265+
"he3_workflow[pol.he3.He3AnalyzerTransmissionFractionAntiParallel] = transmission[\n",
266266
" 'time', 0:0\n",
267267
"]\n",
268-
"pol_workflow[\n",
268+
"he3_workflow[\n",
269269
" pol.he3.He3CellTransmissionFractionIncomingUnpolarized[\n",
270270
" pol.Analyzer, pol.Depolarized\n",
271271
" ]\n",
272272
"] = transmission_depolarized\n",
273273
"\n",
274274
"# When in_situ=False, these params are used as starting guess for the fit\n",
275-
"pol_workflow[pol.he3.He3CellLength[pol.Analyzer]] = 0.1 * sc.Unit('m')\n",
276-
"pol_workflow[pol.he3.He3CellPressure[pol.Analyzer]] = 1.0 * sc.Unit('bar')\n",
277-
"pol_workflow[pol.he3.He3CellTemperature[pol.Analyzer]] = 300.0 * sc.Unit('K')\n",
275+
"he3_workflow[pol.he3.He3CellLength[pol.Analyzer]] = 0.1 * sc.Unit('m')\n",
276+
"he3_workflow[pol.he3.He3CellPressure[pol.Analyzer]] = 1.0 * sc.Unit('bar')\n",
277+
"he3_workflow[pol.he3.He3CellTemperature[pol.Analyzer]] = 300.0 * sc.Unit('K')\n",
278278
"\n",
279-
"pol_workflow[pol.he3.He3TransmissionEmptyGlass[pol.Analyzer]] = transmission_empty_glass\n",
280-
"pol_workflow.visualize(\n",
279+
"he3_workflow[pol.he3.He3TransmissionEmptyGlass[pol.Analyzer]] = transmission_empty_glass\n",
280+
"he3_workflow.visualize(\n",
281281
" pol.TransmissionFunction[pol.Analyzer], graph_attr={'rankdir': 'LR'}\n",
282282
")"
283283
]
@@ -297,7 +297,7 @@
297297
"metadata": {},
298298
"outputs": [],
299299
"source": [
300-
"func = pol_workflow.compute(pol.TransmissionFunction[pol.Analyzer])"
300+
"func = he3_workflow.compute(pol.TransmissionFunction[pol.Analyzer])"
301301
]
302302
},
303303
{
@@ -378,6 +378,87 @@
378378
"source": [
379379
"func.polarization_function.T1"
380380
]
381+
},
382+
{
383+
"cell_type": "markdown",
384+
"id": "79f78366",
385+
"metadata": {},
386+
"source": [
387+
"## Correction workflow\n",
388+
"\n",
389+
"In the previous section we have setup the workflow for the analyzer.\n",
390+
"We also computed the transmission function there, but in production this will be done implicitly by running the entire workflow we will setup here.\n",
391+
"We can combine this with the workflow for the polarizer to obtain the full correction workflow:"
392+
]
393+
},
394+
{
395+
"cell_type": "code",
396+
"execution_count": null,
397+
"id": "41bb77db",
398+
"metadata": {},
399+
"outputs": [],
400+
"source": [
401+
"supermirror_workflow = pol.SupermirrorWorkflow()\n",
402+
"supermirror_workflow.visualize(pol.TransmissionFunction[pol.Polarizer])"
403+
]
404+
},
405+
{
406+
"cell_type": "markdown",
407+
"id": "c9209c68",
408+
"metadata": {},
409+
"source": [
410+
"We will use a second-order polynomial supermirror efficiency function:"
411+
]
412+
},
413+
{
414+
"cell_type": "code",
415+
"execution_count": null,
416+
"id": "b2ef8c40",
417+
"metadata": {},
418+
"outputs": [],
419+
"source": [
420+
"# Note that these coefficients are meaningless, please fill in correct values!\n",
421+
"supermirror_workflow[pol.SupermirrorEfficiencyFunction[pol.Polarizer]] = (\n",
422+
" pol.SecondDegreePolynomialEfficiency(\n",
423+
" a=0.5 * sc.Unit('1/angstrom**2'),\n",
424+
" b=0.4 * sc.Unit('1/angstrom'),\n",
425+
" c=0.3 * sc.Unit('dimensionless'),\n",
426+
" )\n",
427+
")"
428+
]
429+
},
430+
{
431+
"cell_type": "code",
432+
"execution_count": null,
433+
"id": "c95094c7",
434+
"metadata": {},
435+
"outputs": [],
436+
"source": [
437+
"workflow = pol.PolarizationAnalysisWorkflow(\n",
438+
" polarizer_workflow=supermirror_workflow,\n",
439+
" analyzer_workflow=he3_workflow,\n",
440+
")"
441+
]
442+
},
443+
{
444+
"cell_type": "markdown",
445+
"id": "d5e6b0b6",
446+
"metadata": {},
447+
"source": [
448+
"For a single channel, the complete workflow looks as follows:"
449+
]
450+
},
451+
{
452+
"cell_type": "code",
453+
"execution_count": null,
454+
"id": "91a76593",
455+
"metadata": {},
456+
"outputs": [],
457+
"source": [
458+
"workflow.visualize(\n",
459+
" pol.PolarizationCorrectedData[pol.Up, pol.Up], graph_attr={'rankdir': 'LR'}\n",
460+
")"
461+
]
381462
}
382463
],
383464
"metadata": {

src/ess/polarization/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@
3434
He3TransmissionFunction,
3535
Polarized,
3636
)
37-
from .supermirror import SupermirrorWorkflow
37+
from .supermirror import (
38+
SecondDegreePolynomialEfficiency,
39+
SupermirrorEfficiencyFunction,
40+
SupermirrorWorkflow,
41+
)
3842
from .types import (
3943
Analyzer,
4044
Down,
@@ -73,6 +77,8 @@
7377
"Polarizer",
7478
"PolarizingElement",
7579
"ReducedSampleDataBySpinChannel",
80+
"SecondDegreePolynomialEfficiency",
81+
"SupermirrorEfficiencyFunction",
7682
"SupermirrorWorkflow",
7783
"TransmissionFunction",
7884
"NoAnalyzer",

src/ess/polarization/supermirror.py

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# SPDX-License-Identifier: BSD-3-Clause
22
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
33

4+
from abc import ABC, abstractmethod
45
from dataclasses import dataclass
56
from typing import Generic
67

@@ -10,10 +11,44 @@
1011
from .types import PlusMinus, PolarizingElement, TransmissionFunction
1112

1213

13-
class SupermirrorEfficiencyFunction(Generic[PolarizingElement]):
14+
class SupermirrorEfficiencyFunction(Generic[PolarizingElement], ABC):
15+
"""Base class for supermirror efficiency functions"""
16+
17+
@abstractmethod
18+
def __call__(self, *, wavelength: sc.Variable) -> sc.DataArray:
19+
"""Return the efficiency of a supermirror for a given wavelength"""
20+
21+
22+
@dataclass
23+
class SecondDegreePolynomialEfficiency(
24+
SupermirrorEfficiencyFunction[PolarizingElement]
25+
):
26+
"""
27+
Efficiency of a supermirror as a second-degree polynomial
28+
29+
The efficiency is given by a * wavelength^2 + b * wavelength + c
30+
31+
Parameters
32+
----------
33+
a:
34+
Coefficient of the quadratic term, with unit of 1/angstrom^2
35+
b:
36+
Coefficient of the linear term, with unit of 1/angstrom
37+
c:
38+
Constant term, dimensionless
39+
"""
40+
41+
a: sc.Variable
42+
b: sc.Variable
43+
c: sc.Variable
44+
1445
def __call__(self, *, wavelength: sc.Variable) -> sc.DataArray:
1546
"""Return the efficiency of a supermirror for a given wavelength"""
16-
raise NotImplementedError
47+
return (
48+
(self.a * wavelength**2).to(unit='', copy=False)
49+
+ (self.b * wavelength).to(unit='', copy=False)
50+
+ self.c.to(unit='', copy=False)
51+
)
1752

1853

1954
@dataclass
@@ -37,13 +72,6 @@ def apply(self, data: sc.DataArray, plus_minus: PlusMinus) -> sc.DataArray:
3772
return self(wavelength=data.coords['wavelength'], plus_minus=plus_minus)
3873

3974

40-
def get_supermirror_efficiency_function() -> (
41-
SupermirrorEfficiencyFunction[PolarizingElement]
42-
):
43-
# TODO This will need some input parameters
44-
return SupermirrorEfficiencyFunction[PolarizingElement]()
45-
46-
4775
def get_supermirror_transmission_function(
4876
efficiency_function: SupermirrorEfficiencyFunction[PolarizingElement],
4977
) -> TransmissionFunction[PolarizingElement]:
@@ -52,14 +80,8 @@ def get_supermirror_transmission_function(
5280
)
5381

5482

55-
supermirror_providers = (
56-
get_supermirror_efficiency_function,
57-
get_supermirror_transmission_function,
58-
)
59-
60-
6183
def SupermirrorWorkflow() -> sciline.Pipeline:
6284
"""
6385
Workflow for computing transmission functions for supermirror polarizing elements.
6486
"""
65-
return sciline.Pipeline(supermirror_providers)
87+
return sciline.Pipeline((get_supermirror_transmission_function,))

tests/supermirror_test.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# SPDX-License-Identifier: BSD-3-Clause
2+
# Copyright (c) 2024 Scipp contributors (https://github.com/scipp)
3+
import pytest
4+
import scipp as sc
5+
6+
import ess.polarization as pol
7+
8+
9+
def test_SecondDegreePolynomialEfficiency_raises_if_units_incompatible():
10+
wav = sc.scalar(1.0, unit='m')
11+
with pytest.raises(sc.UnitError, match=" to `dimensionless` is not valid"):
12+
eff = pol.SecondDegreePolynomialEfficiency(
13+
a=sc.scalar(1.0, unit='1/angstrom'),
14+
b=sc.scalar(1.0, unit='1/angstrom'),
15+
c=sc.scalar(1.0),
16+
)
17+
eff(wavelength=wav)
18+
with pytest.raises(sc.UnitError, match=" to `dimensionless` is not valid"):
19+
eff = pol.SecondDegreePolynomialEfficiency(
20+
a=sc.scalar(1.0, unit='1/angstrom**2'),
21+
b=sc.scalar(1.0, unit='1/angstrom**2'),
22+
c=sc.scalar(1.0),
23+
)
24+
eff(wavelength=wav)
25+
with pytest.raises(sc.UnitError, match=" to `dimensionless` is not valid"):
26+
eff = pol.SecondDegreePolynomialEfficiency(
27+
a=sc.scalar(1.0, unit='1/angstrom**2'),
28+
b=sc.scalar(1.0, unit='1/angstrom'),
29+
c=sc.scalar(1.0, unit='1/angstrom'),
30+
)
31+
eff(wavelength=wav)
32+
with pytest.raises(sc.UnitError, match=" to `dimensionless` is not valid"):
33+
eff = pol.SecondDegreePolynomialEfficiency(
34+
a=sc.scalar(1.0, unit='1/angstrom**2'),
35+
b=sc.scalar(1.0, unit='1/angstrom'),
36+
c=sc.scalar(1.0),
37+
)
38+
eff(wavelength=wav / sc.scalar(1.0, unit='s'))
39+
40+
41+
def test_SecondDegreePolynomialEfficiency_produces_correct_values():
42+
a = sc.scalar(1.0, unit='1/angstrom**2')
43+
b = sc.scalar(2.0, unit='1/angstrom')
44+
c = sc.scalar(3.0)
45+
f = pol.SecondDegreePolynomialEfficiency(a=a, b=b, c=c)
46+
assert f(wavelength=sc.scalar(0.0, unit='angstrom')) == 3.0
47+
assert f(wavelength=sc.scalar(1.0, unit='angstrom')) == 6.0
48+
assert f(wavelength=sc.scalar(2.0, unit='angstrom')) == 11.0
49+
50+
51+
def test_SecondDegreePolynomialEfficiency_converts_units():
52+
a = sc.scalar(1.0, unit='1/angstrom**2')
53+
b = sc.scalar(20.0, unit='1/nm')
54+
c = sc.scalar(3.0)
55+
f = pol.SecondDegreePolynomialEfficiency(a=a, b=b, c=c)
56+
assert f(wavelength=sc.scalar(0.0, unit='angstrom')) == 3.0
57+
assert f(wavelength=sc.scalar(1.0, unit='angstrom')) == 6.0
58+
assert f(wavelength=sc.scalar(2.0, unit='angstrom')) == 11.0
59+
assert f(wavelength=sc.scalar(0.0, unit='nm')) == 3.0
60+
assert f(wavelength=sc.scalar(0.1, unit='nm')) == 6.0
61+
assert f(wavelength=sc.scalar(0.2, unit='nm')) == 11.0

0 commit comments

Comments
 (0)