# This program is in the public domain
# Author: Paul Kienzle
r"""
Reflectometry instrument definitions.
An instrument definition contains all the information necessary to compute
the resolution for a measurement. See :mod:`resolution` for details.
This module is intended to help define new instrument loaders
Scanning Reflectometers
=======================
:mod:`refl1d.instrument` (this module) defines two instrument types:
:class:`Monochromatic` and :class:`Pulsed`. These represent
generic scanning and time of flight instruments, respectively.
To perform a simulation or load a data set, a measurement geometry must
be defined. In the following example, we set up the geometry for a
pretend instrument SP:2. The complete geometry needs to include information
to calculate wavelength resolution (wavelength and wavelength dispersion)
as well as angular resolution (slit distances and openings, and perhaps
sample size and sample warp). In this case, we are using a scanning
monochromatic instrument with slits of 0.1 mm below 0.5\ |deg| and
opening slits above 0.5\ |deg| starting at 0.2 mm. The monochromatic
instrument assumes a fixed $\Delta \theta / \theta$ while opening.
>>> from refl1d.names import *
>>> geometry = Monochromatic(instrument="SP:2", radiation="neutron",
... wavelength=5.0042, dLoL=0.009, d_s1=230+1856, d_s2=230,
... Tlo=0.5, slits_at_Tlo=0.2, slits_below=0.1)
This instrument can be used to a data file, or generate a
measurement probe for use in modeling or to read in a previously
measured data set or generate a probe for simulation:
>>> from numpy import linspace, loadtxt
>>> datafile = sample_data('10ndt001.refl')
>>> Q, R, dR = loadtxt(datafile).T
>>> probe = geometry.probe(Q=Q, data=(R, dR))
>>> simulation = geometry.probe(T=linspace(0, 5, 51))
All instrument parameters can be specified when constructing the probe,
replacing the defaults that are associated with the instrument. For
example, to include sample broadening effects in the resolution:
>>> probe2 = geometry.probe(Q=Q, data=(R, dR), sample_broadening=0.1,
... name="probe2")
For magnetic systems a polarized beam probe is needed::
>>> magnetic_probe = geometry.magnetic_probe(T=np.linspace(0, 5, 100))
The string representation of the geometry prints a multi-line
description of the default instrument configuration:
>>> print(geometry)
== Instrument SP:2 ==
radiation = neutron at 5.0042 Angstrom with 0.9% resolution
slit distances = 2086 mm and 230 mm
fixed region below 0.5 and above 90 degrees
slit openings at Tlo are 0.2 mm
sample width = 1e+10 mm
sample broadening = 0 degrees
Predefined Instruments
======================
Specific instruments can be defined for each facility. This saves the users
having to remember details of the instrument geometry.
For example, the above SP:2 instrument could be defined as follows:
>>> class SP2(Monochromatic):
... instrument = "SP:2"
... radiation = "neutron"
... wavelength = 5.0042 # Angstroms
... dLoL = 0.009 # FWHM
... d_s1 = 230.0 + 1856.0 # mm
... d_s2 = 230.0 # mm
... def load(self, filename, **kw):
... Q, R, dR = loadtxt(datafile).T
... probe = self.probe(Q=Q, data=(R, dR), **kw)
... return probe
This definition can then be used to define the measurement geometry. We
have added a load method which knows about the facility file format (in
this case, three column ASCII data Q, R, dR) so that we can load a datafile
in a couple of lines of code:
>>> geometry = SP2(Tlo=0.5, slits_at_Tlo=0.2, slits_below=0.1)
>>> probe3 = geometry.load(datafile)
The defaults() method prints the static components of the geometry:
>>> print(SP2.defaults())
== Instrument class SP:2 ==
radiation = neutron at 5.0042 Angstrom with 0.9% resolution
slit distances = 2086 mm and 230 mm
GUI Usage
=========
Graphical user interfaces follow different usage patterns from scripts.
Here the emphasis will be on selecting a data set to process, displaying
its default metadata and allowing the user to override it.
File loading should follow the pattern established in reflectometry
reduction, with an extension registry and a fallback scheme whereby
files can be checked in a predefined order. If the file cannot be
loaded, then the next loader is tried. This should be extended with
the concept of a magic signature such as those used by graphics and
sound file applications: read the first block and run it through
the signature check before trying to load it. For unrecognized
extensions, all loaders can be tried.
The file loader should return an instrument instance with metadata
initialized from the file header. This metadata can be displayed
to the user along with a plot of the data and the resolution. When
metadata values are changed, the resolution can be recomputed and the
display updated. When the data set is accepted, the final resolution
calculation can be performed.
"""
from __future__ import division, print_function
# TODO: the resolution calculator should not be responsible for loading
# the data; maybe do it as a mixin?
import numpy as np
#from numpy import pi, inf, sqrt, log, degrees, radians, cos, sin, tan
from .resolution import QL2T
from .resolution import bins, binwidths, binedges
from .resolution import slit_widths, divergence
from .probe import make_probe, PolarizedNeutronProbe
from .reflectivity import BASE_GUIDE_ANGLE
[docs]
class Monochromatic(object):
"""
Instrument representation for scanning reflectometers.
:Parameters:
*instrument* : string
name of the instrument
*radiation* : string | xray or neutron
source radiation type
*d_s1*, *d_s2* : float | mm
distance from sample to pre-sample slits 1 and 2; post-sample
slits are ignored
*wavelength* : float | |Ang|
wavelength of the instrument
*dLoL* : float
constant relative wavelength dispersion; wavelength range and
dispersion together determine the bins
*slits* : float OR (float, float) | mm
fixed slits
*slits_at_Tlo* : float OR (float, float) | mm
slit 1 and slit 2 openings at Tlo; this can be a scalar if both
slits are open by the same amount, otherwise it is a pair (s1, s2).
*slits_at_Qlo* : float OR (float, float) | mm
equivalent to slits_at_Tlo, for instruments that are controlled by
Q rather than theta
*Tlo*, *Thi* : float | |deg|
range of opening slits, or inf if slits are fixed.
*Qlo*, *Qhi* : float | |1/Ang|
range of opening slits when instrument is controlled by Q.
*slits_below*, *slits_above* : float OR (float, float) | mm
slit 1 and slit 2 openings below Tlo and above Thi; again, these
can be scalar if slit 1 and slit 2 are the same, otherwise they
are each a pair (s1, s2). Below and above default to the values of
the slits at Tlo and Thi respectively.
*sample_width* : float | mm
width of sample; at low angle with tiny samples, stray neutrons
miss the sample and are not reflected onto the detector, so the
sample itself acts as a slit, therefore the width of the sample
may be needed to compute the resolution correctly
*sample_broadening* : float | |deg| FWHM
amount of angular divergence (+) or focusing (-) introduced by
the sample; this is caused by sample warp, and may be read off
of the rocking curve by subtracting (s1+s2)/2/(d_s1-d_s2) from
the FWHM width of the rocking curve
"""
instrument = "monochromatic"
radiation = "unknown"
# Required attributes
wavelength = None
dLoL = None
d_s1 = None
d_s2 = None
# Optional attributes
Tlo = 90 # Use 90 for fixed slits; this is effectively inf
Thi = 90
fixed_slits = None
slits_at_Tlo = None # Slit openings at Tlo, and default for slits_below
slits_below = None # Slit openings below Tlo, or fixed slits if Tlo=90
slits_above = None
sample_width = 1e10 # Large but finite value
sample_broadening = 0
def __init__(self, **kw):
self._translate_Q_to_theta(kw)
for k, v in kw.items():
if hasattr(self, k):
setattr(self, k, v)
else:
raise TypeError("unexpected keyword argument '%s'"%k)
[docs]
def probe(self, **kw):
"""
Return a probe for use in simulation.
:Parameters:
*Q* : [float] | |Ang|
Q values to be measured.
*T* : [float] | |deg|
Angles to be measured.
Additional keyword parameters
:Returns:
*probe* : Probe
Measurement probe with complete resolution information. The
probe will not have any data.
If both *Q* and *T* are specified then *Q* takes precedents.
You can override instrument parameters using key=value. In
particular, settings for *slits_at_Tlo*, *Tlo*, *Thi*,
*slits_below*, and *slits_above* are used to define the
angular divergence.
"""
self._translate_Q_to_theta(kw)
T, dT, L, dL = self.resolution(**kw)
sample_broadening = kw.get('sample_broadening', self.sample_broadening)
kw.update(T=T, dT=dT-sample_broadening, L=L, dL=dL)
kw.setdefault('radiation', self.radiation)
return make_probe(**kw)
[docs]
def magnetic_probe(self, Aguide=BASE_GUIDE_ANGLE, shared_beam=True, H=0, **kw):
"""
Simulate a polarized measurement probe.
Returns a probe with Q, angle, wavelength and the associated
uncertainties, but not any data.
Guide field angle *Aguide* can be specified, as well as keyword
arguments for the geometry of the probe cross sections such as
*slits_at_Tlo*, *Tlo*, *Thi*, *slits_below*, and *slits_above*
to define the angular divergence.
"""
base_name = kw.pop("name", "")
probes = [self.probe(name=base_name+xs, **kw)
for xs in ("--", "-+", "+-", "++")]
probe = PolarizedNeutronProbe(probes, Aguide=Aguide, H=H)
if shared_beam:
probe.shared_beam() # Share the beam parameters by default
return probe
[docs]
def resolution(self, **kw):
"""
Calculate resolution at each angle.
:Return:
*T*, *dT* : [float] | |deg|
Angles and angular divergence.
*L*, *dL* : [float] | |Ang|
Wavelengths and wavelength dispersion.
"""
self._translate_Q_to_theta(kw)
if 'T' not in kw:
raise TypeError("resolution requires slits and either T or Q")
L = kw.get('L', kw.get('wavelength', self.wavelength))
if 'dL' in kw:
dL = kw['dL']
else:
dL = kw.get('dLoL', self.dLoL) * L
if L is None or dL is None:
raise TypeError("Need wavelength and wavelength dispersion to compute resolution")
T = kw['T']
if 'dT' in kw:
dT = kw['dT']
else:
if 'slits' not in kw:
kw['slits'] = self.calc_slits(**kw)
dT = self.calc_dT(**kw)
return T, dT, L, dL
[docs]
def calc_slits(self, **kw):
"""
Determines slit openings from measurement pattern.
If slits are fixed simply return the same slits for every angle,
otherwise use an opening range [Tlo, Thi] and the value of the
slits at the start of the opening to define the slits. Slits
below Tlo and above Thi can be specified separately.
*T* OR *Q* incident angle or Q
*Tlo*, *Thi* angle range over which slits are opening
*slits_at_Tlo* openings at the start of the range, or fixed opening
*slits_below*, *slits_above* openings below and above the range
Use fixed_slits is available, otherwise use opening slits.
"""
self._translate_Q_to_theta(kw)
if 'T' not in kw:
raise TypeError("calc_slits requires angle T=... or Q=...")
T = kw['T']
Tlo = kw.get('Tlo', self.Tlo)
Thi = kw.get('Thi', self.Thi)
fixed_slits = kw.get('fixed_slits', self.fixed_slits)
if fixed_slits is not None:
slits_at_Tlo = slits_below = slits_above = fixed_slits
Tlo = 90
else:
slits_at_Tlo = kw.get('slits_at_Tlo', self.slits_at_Tlo)
slits_below = kw.get('slits_below', self.slits_below)
slits_above = kw.get('slits_above', self.slits_above)
# Otherwise we are using opening slits
if Tlo is None or slits_at_Tlo is None:
raise TypeError("Resolution calculation requires Tlo and slits_at_Tlo")
slits = slit_widths(T=T, slits_at_Tlo=slits_at_Tlo,
Tlo=Tlo, Thi=Thi,
slits_below=slits_below,
slits_above=slits_above)
return slits
[docs]
def calc_dT(self, **kw):
"""
Compute the angular divergence for given slits and angles
:Parameters:
*T* OR *Q* : [float] | |deg| OR |1/Ang|
measurement angles
*slits* : float OR (float, float) | mm
total slit opening from edge to edge, not beam center to edge
*d_s1*, *d_s2* : float | mm
distance from sample to slit 1 and slit 2
*sample_width* : float | mm
size of sample
*sample_broadening* : float | |deg| FWHM
resolution changes from sample warp
:Returns:
*dT* : [float] | |deg| FWHM
angular divergence
*sample_broadening* can be estimated from W, the full width at half
maximum of a rocking curve measured in degrees:
sample_broadening = W - degrees( 0.5*(s1+s2) / (d1-d2))
"""
self._translate_Q_to_theta(kw)
if 'T' not in kw or 'slits' not in kw:
raise TypeError("calc_dT requires slits and either T or Q")
slits = kw['slits']
T = kw['T']
d_s1 = kw.get('d_s1', self.d_s1)
d_s2 = kw.get('d_s2', self.d_s2)
if d_s1 is None or d_s2 is None:
raise TypeError("Need slit distances d_s1, d_s2 to compute resolution")
sample_width = kw.get('sample_width', self.sample_width)
sample_broadening = kw.get('sample_broadening', self.sample_broadening)
dT = divergence(T=T, slits=slits, distance=(d_s1, d_s2),
sample_width=sample_width,
sample_broadening=sample_broadening)
return dT
def _translate_Q_to_theta(self, kw):
"""
Rewrite keyword arguments with Q values translated to theta values.
"""
# Grab wavelength first so we can translate Qlo/Qhi to Tlo/Thi no
# matter what order the keywords appear.
wavelength = kw.get('wavelength', self.wavelength)
if "Q" in kw:
kw["T"] = QL2T(kw.pop("Q"), wavelength)
if "Qlo" in kw:
kw["Tlo"] = QL2T(kw.pop("Qlo"), wavelength)
if "Qhi" in kw:
kw["Thi"] = QL2T(kw.pop("Qhi"), wavelength)
if "slits_at_Qlo" in kw:
kw["slits_at_Tlo"] = kw.pop("slits_at_Qlo")
def __str__(self):
msg = """\
== Instrument %(name)s ==
radiation = %(radiation)s at %(L)g Angstrom with %(dLpercent)g%% resolution
slit distances = %(d_s1)g mm and %(d_s2)g mm
fixed region below %(Tlo)g and above %(Thi)g degrees
slit openings at Tlo are %(slits_at_Tlo)s mm
sample width = %(sample_width)g mm
sample broadening = %(sample_broadening)g degrees\
""" % dict(name=self.instrument, L=self.wavelength, dLpercent=self.dLoL*100,
d_s1=self.d_s1, d_s2=self.d_s2,
sample_width=self.sample_width,
sample_broadening=self.sample_broadening,
Tlo=self.Tlo, Thi=self.Thi,
slits_at_Tlo=str(self.slits_at_Tlo), radiation=self.radiation,
)
return msg
[docs]
@classmethod
def defaults(cls):
"""
Return default instrument properties as a printable string.
"""
msg = """\
== Instrument class %(name)s ==
radiation = %(radiation)s at %(L)s Angstrom with %(dL)s resolution
slit distances = %(d_s1)s mm and %(d_s2)s mm"""
L, d_s1, d_s2 = ["%g"%v if v is not None else 'unknown'
for v in (cls.wavelength, cls.d_s1, cls.d_s2)]
dL = "%g%%"%(cls.dLoL*100) if cls.dLoL else 'unknown'
return msg % dict(name=cls.instrument, radiation=cls.radiation,
L=L, dL=dL, d_s1=d_s1, d_s2=d_s2)
[docs]
class Pulsed(object):
"""
Instrument representation for pulsed reflectometers.
:Parameters:
*instrument* : string
name of the instrument
*radiation* : string | xray, neutron
source radiation type
*TOF_range* : (float, float)
usabe range of times for TOF data
*T* : float | |deg|
sample angle
*d_s1*, *d_s2* : float | mm
distance from sample to pre-sample slits 1 and 2; post-sample
slits are ignored
*wavelength* : (float, float) | |Ang|
wavelength range for the measurement
*dLoL* : float
constant relative wavelength dispersion; wavelength range and
dispersion together determine the bins
*slits* : float OR (float, float) | mm
fixed slits
*slits_at_Tlo* : float OR (float, float) | mm
slit 1 and slit 2 openings at Tlo; this can be a scalar if both
slits are open by the same amount, otherwise it is a pair (s1, s2).
*Tlo*, *Thi* : float | |deg|
range of opening slits, or inf if slits are fixed.
*slits_below*, *slits_above* : float OR (float, float) | mm
slit 1 and slit 2 openings below Tlo and above Thi; again, these
can be scalar if slit 1 and slit 2 are the same, otherwise they
are each a pair (s1, s2). Below and above default to the values of
the slits at Tlo and Thi respectively.
*sample_width* : float | mm
width of sample; at low angle with tiny samples, stray neutrons
miss the sample and are not reflected onto the detector, so the
sample itself acts as a slit, therefore the width of the sample
may be needed to compute the resolution correctly
*sample_broadening* : float | |deg| FWHM
amount of angular divergence (+) or focusing (-) introduced by
the sample; this is caused by sample warp, and may be read off
of the rocking curve by subtracting 0.5*(s1+s2)/(d_s1-d_s2) from
the FWHM width of the rocking curve
"""
instrument = "pulsed"
radiation = "neutron" # unless someone knows how to do TOF Xray...
# Required attributes
d_s1 = None
d_s2 = None
slits = None
T = None
wavelength = None
dLoL = None # usually 0.02 for 2% FWHM
# Optional attributes
TOF_range = (0, np.inf)
Tlo = 90 # Use 90 for fixed slits; this is effectively inf
Thi = 90
fixed_slits = None
slits_at_Tlo = None # Slit openings at Tlo, and default for slits_below
slits_below = None # Slit openings below Tlo, or fixed slits if Tlo=90
slits_above = None
sample_width = 1e10 # Large but finite value
sample_broadening = 0
def __init__(self, **kw):
for k, v in kw.items():
if not hasattr(self, k):
raise TypeError("unexpected keyword argument '%s'"%k)
setattr(self, k, v)
[docs]
def probe(self, **kw):
"""
Simulate a measurement probe.
Returns a probe with Q, angle, wavelength and the associated
uncertainties, but not any data.
You can override instrument parameters using key=value.
In particular, slit settings *slits* and *T* define
the angular divergence and *dLoL* defines the wavelength
resolution.
"""
low, high = kw.get('wavelength', self.wavelength)
dLoL = kw.get('dLoL', self.dLoL)
T = kw.pop('T', self.T)
L = bins(low, high, dLoL)
dL = binwidths(L)
T, dT, L, dL = self.resolution(L=L, dL=dL, T=T, **kw)
sample_broadening = kw.get('sample_broadening', self.sample_broadening)
return make_probe(T=T, dT=dT-sample_broadening, L=L, dL=dL,
radiation=self.radiation, **kw)
[docs]
def magnetic_probe(self, Aguide=BASE_GUIDE_ANGLE, shared_beam=True, **kw):
"""
Simulate a polarized measurement probe.
Returns a probe with Q, angle, wavelength and the associated
uncertainties, but not any data.
Guide field angle *Aguide* can be specified, as well as keyword
arguments for the geometry of the probe cross sections such as
slit settings *slits* and *T* to define the angular divergence
and *dLoL* to define the wavelength resolution.
"""
probes = [self.probe(**kw) for _ in range(4)]
probe = PolarizedNeutronProbe(probes, Aguide=Aguide)
if shared_beam:
probe.shared_beam() # Share the beam parameters by default
return probe
[docs]
def simulate(self, sample, uncertainty=1, **kw):
"""
Simulate a run with a particular sample.
:Parameters:
*sample* : Stack
Reflectometry model
*T* : [float] | |deg|
List of angles to be measured, such as [0.15, 0.4, 1, 2].
*slits* : [float] or [(float, float)] | mm
Slit settings for each angle.
*uncertainty* = 1 : float or [float] | %
Incident intensity is set so that the median dR/R is equal
to *uncertainty*, where R is the idealized reflectivity
of the sample.
*dLoL* = 0.02: float
Wavelength resolution
*normalize* = True : boolean
Whether to normalize the intensities
*theta_offset* = 0 : float | |deg|
Sample alignment error
*background* = 0 : float
Background counts per incident neutron (background is
assumed to be independent of measurement geometry).
*back_reflectivity* = False : boolean
Whether beam travels through incident medium
or through substrate.
*back_absorption* = 1 : float
Absorption factor for beam traveling through substrate.
Only needed for back reflectivity measurements.
"""
from numpy.random import poisson as pois
from .rebin import rebin
from .experiment import Experiment
from .probe import ProbeSet
T = kw.pop('T', self.T)
slits = kw.pop('slits', self.slits)
if slits is None:
raise TypeError('Need slit openings for simulation')
dLoL = kw.pop('dLoL', self.dLoL)
normalize = kw.pop('normalize', True)
theta_offset = kw.pop('theta_offset', 0)
background = kw.pop('background', 0)
back_reflectivity = kw.pop('back_reflectivity', False)
back_absorption = kw.pop('back_absorption', 1)
uncertainty *= 0.01
# Compute reflectivity with resolution and added noise
probes = []
for Ti, Si in zip(T, slits):
probe = self.probe(T=Ti, slits=Si, dLoL=dLoL)
probe.back_reflectivity = back_reflectivity
probe.theta_offset.value = theta_offset
probe.back_absorption.value = back_absorption
M = Experiment(probe=probe, sample=sample)
_, Rth = M.reflectivity()
# Note: probe.L is reversed because L is sorted by increasing
# Q in probe.
I = rebin(binedges(self.feather[0]), self.feather[1],
binedges(probe.L[::-1]))[::-1]
Ci = np.median(1./(uncertainty**2 * I * Rth))
Igoal = Ci*I
Ibeam = pois(Igoal)+1.
Irefl = pois(Igoal*Rth)+1.
if background > 0:
Irefl += pois(Igoal*background) + 1.0
Iback = pois(Igoal*background) + 1.0
else:
Iback = 0
# Set intensity/background _after_ calculating the theory function
# since we don't want the theory function altered by them.
probe.background.value = background
# Correct for the feather. This has to be done otherwise we
# won't see the correct reflectivity. Even if corrected for
# the feather, though, we haven't necessarily corrected for
# the overall number of counts in the measurement.
# Z = X/Y
# var Z = ( (var X / X)**2 + (var Y / Y)**2 ) * Z**2
# = (1/X + 1/Y) * (X/Y)**2
# = (Y + X) * X/Y**3
# dZ = sqrt( (Y+X)*X/Y**3) = sqrt((Y+X)*(X/Y))/Y
R = (Irefl-Iback)/Ibeam
dR = np.sqrt((Irefl + Iback + Ibeam)*(Irefl/Ibeam))/Ibeam
if np.isnan(dR).any() or (dR == 0).any() or (R <= 0).any():
print("Ibeam %s"%Ibeam)
print("Irefl %s"%Irefl)
print("Iback %s"%Iback)
print("R %s"%R)
print("dR %s"%dR)
raise RuntimeError("Should not be able to get here!!")
#dR[Irefl==0] == 1./Ibeam[Irefl==0]
#print("median %s"%np.median(dR/R))
if not normalize:
#Ci = 1./max(R)
R, dR = R*Ci, dR*Ci
probe.background.value *= Ci
probe.intensity.value = Ci
probe.R = R
probe.dR = dR
probes.append(probe)
return Experiment(sample=sample, probe=ProbeSet(probes))
[docs]
def resolution(self, L, dL, **kw):
"""
Return the resolution of the measurement. Needs *T*, *L*, *dL*
specified as keywords.
"""
T = kw.pop('T', self.T)
if 'slits' not in kw:
kw['slits'] = self.calc_slits(T=T, **kw)
dT = self.calc_dT(T, **kw)
# Compute the FWHM angular divergence in radians
# Return the resolution
return T, dT, L, dL
[docs]
def calc_slits(self, **kw):
"""
Determines slit openings from measurement pattern.
If slits are fixed simply return the same slits for every angle,
otherwise use an opening range [Tlo, Thi] and the value of the
slits at the start of the opening to define the slits. Slits
below Tlo and above Thi can be specified separately.
*T* incident angle
*Tlo*, *Thi* angle range over which slits are opening
*slits_at_Tlo* openings at the start of the range, or fixed opening
*slits_below*, *slits_above* openings below and above the range
Use fixed_slits is available, otherwise use opening slits.
"""
if 'T' not in kw:
raise TypeError("calc_slits requires angle T=...")
T = kw['T']
Tlo = kw.get('Tlo', self.Tlo)
Thi = kw.get('Thi', self.Thi)
fixed_slits = kw.get('fixed_slits', self.fixed_slits)
if fixed_slits is not None:
slits_at_Tlo = slits_below = slits_above = fixed_slits
Tlo = 90
else:
slits_at_Tlo = kw.get('slits_at_Tlo', self.slits_at_Tlo)
slits_below = kw.get('slits_below', self.slits_below)
slits_above = kw.get('slits_above', self.slits_above)
# Otherwise we are using opening slits
if Tlo is None or slits_at_Tlo is None:
raise TypeError("Resolution calculation requires Tlo and slits_at_Tlo")
slits = slit_widths(T=T, slits_at_Tlo=slits_at_Tlo,
Tlo=Tlo, Thi=Thi,
slits_below=slits_below,
slits_above=slits_above)
return slits
[docs]
def calc_dT(self, T, slits, **kw):
d_s1 = kw.get('d_s1', self.d_s1)
d_s2 = kw.get('d_s2', self.d_s2)
sample_width = kw.get('sample_width', self.sample_width)
sample_broadening = kw.get('sample_broadening', self.sample_broadening)
dT = divergence(T=T, slits=slits, distance=(d_s1, d_s2),
sample_width=sample_width,
sample_broadening=sample_broadening)
return dT
def __str__(self):
msg = """\
== Instrument %(name)s ==
radiation = %(radiation)s in %(L_min)g to %(L_max)g Angstrom with %(dLpercent)g%% resolution
slit distances = %(d_s1)g mm and %(d_s2)g mm
slit openings = %(slits)s mm
sample width = %(sample_width)g mm
sample broadening = %(sample_broadening)g degrees FWHM
""" % dict(name=self.instrument,
L_min=self.wavelength[0], L_max=self.wavelength[1],
dLpercent=self.dLoL*100,
d_s1=self.d_s1, d_s2=self.d_s2, slits=str(self.slits),
sample_width=self.sample_width,
sample_broadening=self.sample_broadening,
radiation=self.radiation,
)
return msg
[docs]
@classmethod
def defaults(cls):
"""
Return default instrument properties as a printable string.
"""
msg = """\
== Instrument class %(name)s ==
radiation = %(radiation)s in %(L_min)g to %(L_max)g Angstrom with %(dLpercent)g%% resolution
slit distances = %(d_s1)g mm and %(d_s2)g mm
""" % dict(name=cls.instrument,
L_min=cls.wavelength[0], L_max=cls.wavelength[1],
dLpercent=cls.dLoL*100,
d_s1=cls.d_s1, d_s2=cls.d_s2,
radiation=cls.radiation,
)
return msg
_ = '''
class GenericMonochromatic(Monochromatic):
def load(self, filename, **kw):
"""
Load the data, returning the associated probe. This probe will
contain Q, angle, wavelength, measured reflectivity and the
associated uncertainties.
You can override instrument parameters using key=value. In
particular, slit settings *slits_at_Tlo*, *Tlo*, *Thi*,
and *slits_below*, and *slits_above* are used to define the
angular divergence.
.. Note::
This function ignores any resolution information stored in
the file, such as dQ, dT or dL columns, and instead uses the
defined instrument parameters to calculate the resolution.
"""
# Load the data
data = np.loadtxt(filename).T
if data.shape[0] == 2:
Q, R = data
dR = None
elif data.shape[0] == 3:
Q, R, dR = data
elif data.shape[0] == 4:
Q, dQ, R, dR = data
elif data.shape[0] == 5:
Q, dQ, R, dR, L = data
if "Q" not in kw: kw["Q"] = Q
T, dT, L, dL = self.resolution(**kw)
kw.update(dict(T=T, dT=dT, L=L, dL=dL, data=(R, dR),
radiation=self.radiation))
return make_probe(**kw)
class GenericPulsed(Pulsed):
def load(self, filename, **kw):
"""
Load the data, returning the associated probe. This probe will
contain Q, angle, wavelength, measured reflectivity and the
associated uncertainties.
You can override instrument parameters using key=value.
In particular, slit settings *slits* and *T* define the
angular divergence.
"""
# Load the data
data = np.loadtxt(filename).T
Q, dQ, R, dR, L = data
dL = binwidths(L)
T = kw.pop('T', QL2T(Q, L))
T, dT, L, dL = self.resolution(L=L, dL=dL, T=T, **kw)
return make_probe(T=T, dT=dT, L=L, dL=dL, data=(R, dR),
radiation=self.radiation, **kw)
'''