Skip to content

API: chopcal.lib

Everything chopper-lib exposes: discs, the bands they pass, the grid mask, and the exact transmitted region.

The classes here are mostly nanobind bindings onto the C, so they are the same objects a McStas instrument works with rather than a Python reimplementation of them.

Describing a disc

Chopper

Chopper()

One disk chopper: how fast it turns, where its openings are, and how far away.

The disk is described the way the NeXus NXdisk_chopper standard and McStas' CollectorDiskChopper describe one. edges are the slit edges in degrees from the disk's own zero mark, two per opening, strictly increasing, opening edge first; beam is the angle from that mark to where the beam crosses the disk. An edge at angle a is on the beam at

t(a) = delay + (beam - a) / (360 * speed)

and every 1/|speed| seconds after that. Only beam - a appears, so a caller can hand over the disk's own numbers with no conversion to do. A negative speed turns the disk the other way, which does not move delay -- a delay is a time, so it means the same thing in either direction -- but does swap which side of it an opening arrives on.

This replaces the angle of chopper-lib 2.x, which could only describe one opening centred on the beam. Chopper.centred still writes one of those.

init(self, speed: float = 0, delay: float = 0, beam: float = 0, edges: collections.abc.Sequence[float] = [], path: float = 0, aperture: float = 0) -> None

Methods:

  • __new__ –

    Create and return a new object. See help(type) for accurate signature.

  • __repr__ –

    Unambiguous, and in the units the fields are actually stored in.

  • __str__ –

    The same disk, said out loud.

  • centred –

    centred(speed: float = 0, delay: float = 0, width: float = 0, path: float = 0, aperture: float = 0) -> chopcal._chopper_lib_impl.Chopper

  • parked_is_open –

    parked_is_open(self) -> bool

  • validate –

    validate(self) -> None

Attributes:

  • __doc__ –

    str(object='') -> str

  • __module__ –

    str(object='') -> str

  • aperture –

    Angular width of the beam where it crosses the disk, in degrees.

  • beam –

    From the disk's zero mark to where the beam crosses it, in degrees

  • delay –

    When the disk point at angle beam is on the beam path, in seconds

  • edges –

    Slit edges in degrees from the zero mark, two per opening, increasing.

  • opening –

    Total opening angle in degrees, summed over the openings

  • openings –

    The (opening, closing) edge pairs, in degrees from the disk's zero mark.

  • path –

    Flight path from the source to the disk, in metres

  • quantities –

    The fields as scipp variables, each carrying its own unit.

  • speed –

    Rotation frequency in Hz; negative turns the disk the other way

__doc__ class-attribute

__doc__ = "One disk chopper: how fast it turns, where its openings are, and how far away.\n\nThe disk is described the way the NeXus `NXdisk_chopper` standard and McStas'\n`CollectorDiskChopper` describe one. `edges` are the slit edges in degrees from\nthe disk's own zero mark, two per opening, strictly increasing, opening edge\nfirst; `beam` is the angle from that mark to where the beam crosses the disk.\nAn edge at angle `a` is on the beam at\n\n    t(a) = delay + (beam - a) / (360 * speed)\n\nand every 1/|speed| seconds after that. Only `beam - a` appears, so a caller can\nhand over the disk's own numbers with no conversion to do. A negative `speed`\nturns the disk the other way, which does not move `delay` -- a delay is a time,\nso it means the same thing in either direction -- but does swap which side of it\nan opening arrives on.\n\nThis replaces the `angle` of chopper-lib 2.x, which could only describe one\nopening centred on the beam. `Chopper.centred` still writes one of those."

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.

__module__ class-attribute

__module__ = 'chopcal._chopper_lib_impl'

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.

aperture property

aperture

Angular width of the beam where it crosses the disk, in degrees. Widens every window by half of it at each end; 0 is a point beam.

beam property

beam

From the disk's zero mark to where the beam crosses it, in degrees

delay property

delay

When the disk point at angle beam is on the beam path, in seconds

edges property

edges

Slit edges in degrees from the zero mark, two per opening, increasing. A slit across the mark is written past 360 -- [350, 370], not [350, 10].

opening property

opening

Total opening angle in degrees, summed over the openings

openings property

openings

The (opening, closing) edge pairs, in degrees from the disk's zero mark.

path property

path

Flight path from the source to the disk, in metres

quantities property

quantities

The fields as scipp variables, each carrying its own unit.

The attributes are plain numbers in fixed units -- Hz, seconds, degrees, metres -- and this is the same values with the units attached, so that whatever reads them can convert rather than assume. Worth reaching for whenever the numbers are going somewhere else: the table above prints delays in milliseconds because that is the scale they live on, while chopper.delay is in seconds, and a scalar cannot be read the wrong way round.

edges is a list rather than a single number, so it comes back as an array variable over an edge dimension.

speed property

speed

Rotation frequency in Hz; negative turns the disk the other way

__new__ builtin

__new__(*args, **kwargs)

Create and return a new object. See help(type) for accurate signature.

__repr__

__repr__(chopper)

Unambiguous, and in the units the fields are actually stored in.

__str__

__str__(chopper)

The same disk, said out loud.

centred

centred(*args, **kwargs)

centred(speed: float = 0, delay: float = 0, width: float = 0, path: float = 0, aperture: float = 0) -> chopcal._chopper_lib_impl.Chopper

A disk of one opening width degrees across, centred on the beam.

Equivalent to beam=0 and edges=[-width/2, +width/2], which is what chopper-lib built internally from the angle of its 2.x structure.

parked_is_open method descriptor

parked_is_open()

parked_is_open(self) -> bool

Whether this disk stands open on the beam when it is not turning.

speed is not read: a turning disk stands open once a period whatever its angles are. It matters for a parked one, because the window and mask functions leave a disk parked open out of the calculation -- no period, so it constrains nothing -- while one parked shut is a beam stop and empties the answer. An empty result does not say which happened; this does.

validate method descriptor

validate()

validate(self) -> None

Raise if the edges are not an increasing, even-length list, or the aperture is negative. Called on construction; call it again after assigning to edges, which cannot check itself.

beam_aperture

beam_aperture(*args, **kwargs)

beam_aperture(radius: float, slit_height: float, window_width: float, window_height: float, at: str = 'inner') -> float

The angular width of a beam where it crosses a disk, in degrees.

This is what Chopper.aperture wants. An opening is angular and a beam is not, so a neutron crossing to one side of the beam centre meets an edge early or late; the largest such angle belongs to the inner corners of the beam window, nearest the spindle, where a given width subtends the most angle.

slit_height is the radial extent of the opening cut in the disk -- McStas DiskChopper's yheight, which puts the beam centre at radius - yheight/2. window_width and window_height describe the beam inside it, which is smaller.

All four lengths in metres; the answer is in degrees.

at is where across the window the angle is taken. 'inner', the default, is the corner nearest the spindle: the last part of the beam an edge clears, and so the edge of the region Region.transmit builds. 'centre' is the beam centre: while an edge crosses the window the fraction it has passed grows linearly at the rate the centre sets, which is the ramp Region.transmit_weighted models. Use 'centre' for the choppers handed to transmit_weighted; against McStas with the real window geometry it fits a BIFROST train to within counting error, where 'inner' leaves the tails of the arrival profile 6-18% low.

ChopperSet

Bases: dict

The choppers of an instrument, by name, in beam order.

A dict in every respect -- set['ps1'], set.values(), **set all behave as usual -- that prints itself as a table rather than as six memory addresses.

delay is when the disk point at beam is on the beam path and open how long the openings spend there each turn, so a single-opening disk centred on the beam passes neutrons from delay - open/2 to delay + open/2, and again every 1/speed after that. Both are shown in milliseconds because that is the scale they live on; the attributes themselves are in seconds.

aperture is how wide the beam is on the disk, in degrees about its spindle. It widens every window by half of it at each end, so open understates the time a real beam spends in the openings; zero is a point beam.

Use :attr:quantities to get them as scipp variables instead, which is the safer thing to hand to anything else -- the units come along and cannot be misread.

Attributes:

  • quantities –

    Every chopper's fields as scipp variables, by name.

quantities property

quantities

Every chopper's fields as scipp variables, by name.

Fenced rather than indented because ['ps1']['delay'] is also valid Markdown for a reference link, and the documentation build reads these docstrings.

>>> settings.quantities['ps1']['delay']     # doctest: +SKIP
<scipp.Variable> ()  float64  [s]  0.00595313

What a train passes

wavelength_windows

wavelength_windows(*args, **kwargs)

wavelength_windows(choppers: collections.abc.Sequence[chopcal._chopper_lib_impl.Chopper], wavelength_min: float = 0.0001, wavelength_max: float = 100.0, latest_emission: float = 0.003) -> list[tuple[float, float]]

Every wavelength band the train passes, in angstrom.

The band list rather than the envelope wavelength_limits returns, and the one to reach for when there is more than one: an envelope spanning two bands also spans the gap between them, which the train does not pass.

See wavelength_limits on why a band here may be narrower than it looks.

wavelength_limits

wavelength_limits(*args, **kwargs)

wavelength_limits(choppers: collections.abc.Sequence[chopcal._chopper_lib_impl.Chopper], wavelength_min: float = 0.0001, wavelength_max: float = 100.0, latest_emission: float = 0.003) -> tuple[int, tuple[float, float]]

The same envelope in angstrom, as (count, (low, high)).

A count above one means the envelope also spans wavelengths the train does not pass; wavelength_windows gives the bands themselves.

This and wavelength_windows are an over-approximation. chopper-lib works out each disk's admissible inverse velocities letting the emission time range over the whole pulse independently, then intersects those ranges, so it can report a band that no single emission time actually delivers -- an intersection of projections is a superset of the projection of the intersection. It is tight where the disks leave wide, overlapping emission windows, which is the usual case and includes every band an instrument is set up to pass. inverse_velocity_time_mask keeps the two coordinates coupled and does not make this error; where the two disagree, the mask is right.

inverse_velocity_windows

inverse_velocity_windows(*args, **kwargs)

inverse_velocity_windows(choppers: collections.abc.Sequence[chopcal._chopper_lib_impl.Chopper], inv_v_min: float = 2.527784135457727e-08, inv_v_max: float = 0.02527784135457727, latest_emission: float = 0.003) -> list[tuple[float, float]]

Every inverse velocity band the train passes, in s/m.

A disk parked open is left out, having no period to constrain anything; one parked shut empties the list -- see Chopper.parked_is_open.

inverse_velocity_limits

inverse_velocity_limits(*args, **kwargs)

inverse_velocity_limits(choppers: collections.abc.Sequence[chopcal._chopper_lib_impl.Chopper], inv_v_min: float = 2.527784135457727e-08, inv_v_max: float = 0.02527784135457727, latest_emission: float = 0.003) -> tuple[int, tuple[float, float]]

The envelope of those bands, as (count, (low, high)) in s/m.

A count above one means the envelope also contains inverse velocities the train does not pass; use inverse_velocity_windows for the bands themselves.

The exact transmitted region

Region

Region()

The region of (inverse velocity, emission time) a chopper train transmits.

A union of convex polygons, and exact: a neutron emitted at inverse velocity a and time t reaches path L at t + L*a, so a disc open on [lower, upper] accepts lower <= t + L*a <= upper -- a slab between two parallel lines. A train's acceptance is an intersection of unions of such slabs, and intersection distributes over union, so the region is a union of convex pieces: one per choice of which opening and which turn of each disc a neutron goes through.

This is what inverse_velocity_windows and inverse_velocity_time_mask approximate, each in its own direction. The window functions project each disc separately and intersect the projections, which can report a band no single emission time delivers. The mask quantises, losing channels thinner than a bin and keeping partly covered bins whole. This does neither.

Start from Region.rectangle, the region a source draws from, and transmit it through the train.

init(self) -> None

An empty region, transmitting nothing.

Methods:

  • __bool__ –

    bool(self) -> bool

  • __len__ –

    len(self) -> int

  • __new__ –

    Create and return a new object. See help(type) for accurate signature.

  • arrival_moments –

    The amount reaching distance in each arrival-time bin, with the mean and spread

  • arrivals –

    What a gridded source puts through the region, by arrival time at distance.

  • contains –

    contains(self, inverse_velocity: float, time: float) -> bool

  • coverage –

    The fraction of each (time, inverse velocity) cell the region covers.

  • from_wavelengths –

    A source rectangle given in angstrom rather than in inverse velocity.

  • integrate –

    How much of a gridded source the region transmits.

  • inverse_velocity_ranges –

    inverse_velocity_ranges(self) -> list[tuple[float, float]]

  • rectangle –

    rectangle(inverse_velocity_minimum: float, inverse_velocity_range: float, time_minimum: float, time_range: float) -> chopcal._chopper_lib_impl.Region

  • sampler –

    sampler(self, sampled_area: float) -> chopcal._chopper_lib_impl.RegionSampler

  • to_dict –

    The region as plain data, in the schema :meth:Region.write_json writes.

  • transmit –

    transmit(self, choppers: collections.abc.Sequence[chopcal._chopper_lib_impl.Chopper], path_spreads: collections.abc.Sequence[float] = []) -> chopcal._chopper_lib_impl.Region

  • transmit_weighted –

    transmit_weighted(self, choppers: collections.abc.Sequence[chopcal._chopper_lib_impl.Chopper], groups: collections.abc.Sequence[int] = []) -> chopcal._chopper_lib_impl.WeightedRegion

  • wavelength_ranges –

    wavelength_ranges(self) -> list[tuple[float, float]]

  • write_json –

    Write the region to path as JSON, byte for byte what the McStas component writes.

Attributes:

  • __doc__ –

    str(object='') -> str

  • __module__ –

    str(object='') -> str

  • area –

    Total area in s^2/m. A plain sum: the pieces are disjoint, because

  • polygons –

    The convex pieces, as a list

__doc__ class-attribute

__doc__ = "The region of (inverse velocity, emission time) a chopper train transmits.\n\nA union of convex polygons, and exact: a neutron emitted at inverse velocity `a`\nand time `t` reaches path `L` at `t + L*a`, so a disc open on [lower, upper]\naccepts `lower <= t + L*a <= upper` -- a slab between two parallel lines. A\ntrain's acceptance is an intersection of unions of such slabs, and intersection\ndistributes over union, so the region *is* a union of convex pieces: one per\nchoice of which opening and which turn of each disc a neutron goes through.\n\nThis is what `inverse_velocity_windows` and `inverse_velocity_time_mask`\napproximate, each in its own direction. The window functions project each disc\nseparately and intersect the projections, which can report a band no single\nemission time delivers. The mask quantises, losing channels thinner than a bin\nand keeping partly covered bins whole. This does neither.\n\nStart from `Region.rectangle`, the region a source draws from, and `transmit` it\nthrough the train."

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.

__module__ class-attribute

__module__ = 'chopcal._chopper_lib_impl'

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.

area property

area

Total area in s^2/m. A plain sum: the pieces are disjoint, because two turns of one disc cannot both pass the same neutron.

polygons property

polygons

The convex pieces, as a list

__bool__ method descriptor

__bool__()

bool(self) -> bool

__len__ method descriptor

__len__()

len(self) -> int

__new__ builtin

__new__(*args, **kwargs)

Create and return a new object. See help(type) for accurate signature.

arrival_moments

arrival_moments(region, signal, inverse_velocities, times, distance, arrival_times, period=None)

The amount reaching distance in each arrival-time bin, with the mean and spread of its wavelength -- the numbers a wavelength lookup table stores.

The same integration as :meth:arrivals, summed over inverse velocity, but each piece of the region contributes its own exact first and second moments rather than its cell's centre. So the mean and spread are exact for the gridded source however coarse its grid: coarsening loses only how well the grid describes the source.

signal, the edges, distance and period are as :meth:arrivals takes them. Returns an :class:ArrivalMoments of three numpy arrays, one value per arrival bin.

arrivals

arrivals(region, signal, inverse_velocities, times, distance, arrival_times, period=None)

What a gridded source puts through the region, by arrival time at distance.

A neutron emitted at inverse velocity a and time t reaches distance (m) at t + distance * a. That shear keeps areas, so each cell's share of the region is carried into arrival_times (s, bin edges) exactly: the density times the area between two lines of constant arrival time. distance 0 gives the emission times.

Shaped (arrival time, inverse velocity), since the shear leaves inverse velocity alone: sum over the last axis for the time profile at a monitor or the sample, or keep it for the wavelength as well. With period (s), arrival times are folded into [0, period) first, as a detector folds them into its frame -- give arrival_times in that range.

signal is read as :meth:integrate reads it. One flight path for every neutron: a guide's spread of paths smears arrivals, and is not included.

contains method descriptor

contains()

contains(self, inverse_velocity: float, time: float) -> bool

Whether a neutron emitted at this (inverse velocity, time) gets through

coverage

coverage(region, inverse_velocities, times)

The fraction of each (time, inverse velocity) cell the region covers.

Exact: each polygon is clipped to each cell it reaches, where :func:inverse_velocity_time_mask counts a cell whole or not at all. Shaped (time, inverse velocity), as the masks are; inverse_velocities (s/m) and times (s, at the source) are bin edges. Needs numpy.

from_wavelengths

from_wavelengths(wavelength_min, wavelength_max, time_minimum, time_range)

A source rectangle given in angstrom rather than in inverse velocity.

Converted the way chopper-lib converts, so the bands that come back out of :meth:Region.wavelength_ranges are on the same footing as the rectangle that went in.

integrate

integrate(region, signal, inverse_velocities, times)

How much of a gridded source the region transmits.

signal is the amount in each (time, inverse velocity) cell -- a histogram of the source, shaped as :meth:coverage is -- spread evenly over its cell. That reading is what makes this exact rather than approximate in the cells the region only partly covers, which :func:unmasked_probability counts whole.

inverse_velocity_ranges method descriptor

inverse_velocity_ranges()

inverse_velocity_ranges(self) -> list[tuple[float, float]]

The bands the region covers in inverse velocity, sorted and merged, s/m.

The projection of the intersection, where inverse_velocity_windows gives the intersection of the projections -- a superset of this one.

rectangle

rectangle(*args, **kwargs)

rectangle(inverse_velocity_minimum: float, inverse_velocity_range: float, time_minimum: float, time_range: float) -> chopcal._chopper_lib_impl.Region

The rectangle a source draws from: inverse velocities in s/m by emission times in s. Both ranges must be positive.

sampler method descriptor

sampler()

sampler(self, sampled_area: float) -> chopcal._chopper_lib_impl.RegionSampler

A sampler over this region, drawing uniformly and never rejecting.

sampled_area is the area of the region the caller draws from -- normally the area of the source rectangle this was transmitted from -- and sets the acceptance.

to_dict

to_dict(region, sampled=None)

The region as plain data, in the schema :meth:Region.write_json writes.

sampled is the source :class:Region this was transmitted from, which sets the acceptance; without it that and sampled are None, exactly as the file has them.

transmit method descriptor

transmit()

transmit(self, choppers: collections.abc.Sequence[chopcal._chopper_lib_impl.Chopper], path_spreads: collections.abc.Sequence[float] = []) -> chopcal._chopper_lib_impl.Region

What a train leaves of this region, as a new Region; this one is unchanged.

path_spreads is one extra flight path per chopper, in metres, for a guide that is not a straight line. The deviation is in path, so its effect on an arrival time is deviation * inverse_velocity -- larger for a slow neutron, and nothing at all to the inverse velocity -- so it does not grow the region evenly, it opens each slab into a wedge. It gives the support under that uncertainty rather than a distribution over it, the same bargain Chopper.aperture makes for the width of the beam.

transmit_weighted method descriptor

transmit_weighted()

transmit_weighted(self, choppers: collections.abc.Sequence[chopcal._chopper_lib_impl.Chopper], groups: collections.abc.Sequence[int] = []) -> chopcal._chopper_lib_impl.WeightedRegion

What a train leaves of this region, weighted by how likely each neutron is to get through, as a WeightedRegion; see that class. Give the choppers the aperture beam_aperture(..., at='centre') returns: that sets the ramp.

wavelength_ranges method descriptor

wavelength_ranges()

wavelength_ranges(self) -> list[tuple[float, float]]

The same bands in angstrom, converted as chopper-lib converts them.

write_json

write_json(region, path, sampled=None)

Write the region to path as JSON, byte for byte what the McStas component writes.

sampled is the source :class:Region, of which only its single polygon is passed down. :meth:Region.to_dict is the same thing without a file.

Polygon

Polygon(*args, **kwargs)

One convex piece of a transmitted region, in (inverse velocity, time).

A value, not a handle: it owns nothing and copies freely. Its vertices are the disc edges that bound it, at the precision of a double -- nothing here is sampled onto a grid.

Initialize self. See help(type(self)) for accurate signature.

Methods:

  • __len__ –

    len(self) -> int

  • __new__ –

    Create and return a new object. See help(type) for accurate signature.

  • contains –

    contains(self, inverse_velocity: float, time: float) -> bool

Attributes:

  • __doc__ –

    str(object='') -> str

  • __module__ –

    str(object='') -> str

  • area –

    Area in s^2/m -- phase space, not a count

  • vertices –

    (inverse velocity, time) pairs in order, s/m and s

__doc__ class-attribute

__doc__ = 'One convex piece of a transmitted region, in (inverse velocity, time).\n\nA value, not a handle: it owns nothing and copies freely. Its vertices are the\ndisc edges that bound it, at the precision of a double -- nothing here is sampled\nonto a grid.'

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.

__module__ class-attribute

__module__ = 'chopcal._chopper_lib_impl'

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.

area property

area

Area in s^2/m -- phase space, not a count

vertices property

vertices

(inverse velocity, time) pairs in order, s/m and s

__len__ method descriptor

__len__()

len(self) -> int

__new__ builtin

__new__(*args, **kwargs)

Create and return a new object. See help(type) for accurate signature.

contains method descriptor

contains()

contains(self, inverse_velocity: float, time: float) -> bool

Whether a point lies inside, its boundary counting as inside

RegionSampler

RegionSampler()

Draws (inverse velocity, time) pairs uniformly from a transmitted region.

The same job as MaskSampler and the same shape -- three uniform deviates, one binary search, never rejects -- over the exact region rather than a grid approximation of it. Each polygon is fanned into triangles from its first vertex, so count here is triangles, not polygons.

init(self, region: chopcal._chopper_lib_impl.Region, sampled_area: float) -> None

Methods:

  • __new__ –

    Create and return a new object. See help(type) for accurate signature.

  • draw –

    draw(self, triangle_deviate: float, first_deviate: float, second_deviate: float) -> tuple[float, float]

  • draw_many –

    draw_many(self, triangle_deviates: ndarray[dtype=float64, shape=(), order='C', writable=False], first_deviates: ndarray[dtype=float64, shape=(), order='C', writable=False], second_deviates: ndarray[dtype=float64, shape=(*), order='C', writable=False]) -> tuple[list[float], list[float]]

  • sample –

    count (inverse_velocity, time) pairs, as two numpy arrays.

Attributes:

  • __doc__ –

    str(object='') -> str

  • __module__ –

    str(object='') -> str

  • acceptance –

    The factor every drawn ray's weight must be multiplied by.

  • count –

    Triangles this draws from; 0 means nothing to draw

__doc__ class-attribute

__doc__ = 'Draws (inverse velocity, time) pairs uniformly from a transmitted region.\n\nThe same job as `MaskSampler` and the same shape -- three uniform deviates, one\nbinary search, never rejects -- over the exact region rather than a grid\napproximation of it. Each polygon is fanned into triangles from its first vertex,\nso `count` here is triangles, not polygons.'

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.

__module__ class-attribute

__module__ = 'chopcal._chopper_lib_impl'

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.

acceptance property

acceptance

The factor every drawn ray's weight must be multiplied by.

Exact: the transmitted area over the sampled area, both in closed form rather than counted in cells. MaskSampler.acceptance estimates the same number on a grid and can only over-estimate it, since a partly covered cell is weighted whole.

Everything the note on MaskSampler.acceptance says about independence still applies: the correction is right only while both coordinates are drawn uniformly and independently of each other and of everything else.

count property

count

Triangles this draws from; 0 means nothing to draw

__new__ builtin

__new__(*args, **kwargs)

Create and return a new object. See help(type) for accurate signature.

draw method descriptor

draw()

draw(self, triangle_deviate: float, first_deviate: float, second_deviate: float) -> tuple[float, float]

One (inverse_velocity, time) pair from three uniform deviates on [0, 1).

draw_many method descriptor

draw_many()

draw_many(self, triangle_deviates: ndarray[dtype=float64, shape=(), order='C', writable=False], first_deviates: ndarray[dtype=float64, shape=(), order='C', writable=False], second_deviates: ndarray[dtype=float64, shape=(*), order='C', writable=False]) -> tuple[list[float], list[float]]

The same draw over three equal-length arrays, returning two lists.

sample

sample(sampler, count, generator=None)

count (inverse_velocity, time) pairs, as two numpy arrays.

The compiled draw and draw_many take their uniform deviates as arguments, so that chopper-lib needs no generator of its own and a McStas TRACE can hand over rand01(). This is the convenience for everything else: it draws the deviates from generator -- numpy.random.default_rng() if none is given -- and calls draw_many.

The caller still owes every drawn ray's weight a factor of sampler.acceptance.

WeightedRegion

WeightedRegion(*args, **kwargs)

The region a chopper train transmits, weighted by how likely a neutron is to get through.

With a disc aperture, a Region's boundary is where transmission falls to zero. Inside, an edge sweeping a uniformly lit beam ramps the transmission linearly, and this carries that ramp: the same convex polygons, each with a product of affine factors. Discs in one group -- a pair sharing a housing -- are crossed at the same place across the beam, so the group passes the part of the beam open in all of them: the smaller transmission for a co-rotating pair, and for a counter-rotating pair a ramp starting at the half-way time and twice as steep. Different groups multiply. Pieces may overlap where two openings of one disc ramp at once, and their transmissions add.

Make one with Region.transmit_weighted.

Initialize self. See help(type(self)) for accurate signature.

Methods:

  • __bool__ –

    bool(self) -> bool

  • __len__ –

    len(self) -> int

  • __new__ –

    Create and return a new object. See help(type) for accurate signature.

  • arrival_moments –

    The amount reaching distance in each arrival-time bin, with the mean and spread

  • arrivals –

    What a gridded source puts through the region, by arrival time at distance.

  • coverage –

    The fraction of each (time, inverse velocity) cell the region covers.

  • integrate –

    How much of a gridded source the region transmits.

  • transmission –

    transmission(self, inverse_velocity: float, time: float) -> float

Attributes:

  • __doc__ –

    str(object='') -> str

  • __module__ –

    str(object='') -> str

  • integral –

    The integral of the transmission, s^2/m: the area a uniformly drawn ray

  • pieces –

    Each piece as (Polygon, factors), a factor (a, b, c) being

__doc__ class-attribute

__doc__ = "The region a chopper train transmits, weighted by how likely a neutron is to get\nthrough.\n\nWith a disc `aperture`, a Region's boundary is where transmission falls to zero.\nInside, an edge sweeping a uniformly lit beam ramps the transmission linearly, and\nthis carries that ramp: the same convex polygons, each with a product of affine\nfactors. Discs in one *group* -- a pair sharing a housing -- are crossed at the same\nplace across the beam, so the group passes the part of the beam open in all of\nthem: the smaller transmission for a co-rotating pair, and for a counter-rotating\npair a ramp starting at the half-way time and twice as steep. Different groups\nmultiply. Pieces may overlap where two openings of one disc ramp at once, and their\ntransmissions add.\n\nMake one with `Region.transmit_weighted`."

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.

__module__ class-attribute

__module__ = 'chopcal._chopper_lib_impl'

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.

integral property

integral

The integral of the transmission, s^2/m: the area a uniformly drawn ray budget effectively passes. Exact.

pieces property

pieces

Each piece as (Polygon, factors), a factor (a, b, c) being ainverse_velocity + btime + c; the transmission is their product.

__bool__ method descriptor

__bool__()

bool(self) -> bool

__len__ method descriptor

__len__()

len(self) -> int

__new__ builtin

__new__(*args, **kwargs)

Create and return a new object. See help(type) for accurate signature.

arrival_moments

arrival_moments(region, signal, inverse_velocities, times, distance, arrival_times, period=None)

The amount reaching distance in each arrival-time bin, with the mean and spread of its wavelength -- the numbers a wavelength lookup table stores.

The same integration as :meth:arrivals, summed over inverse velocity, but each piece of the region contributes its own exact first and second moments rather than its cell's centre. So the mean and spread are exact for the gridded source however coarse its grid: coarsening loses only how well the grid describes the source.

signal, the edges, distance and period are as :meth:arrivals takes them. Returns an :class:ArrivalMoments of three numpy arrays, one value per arrival bin.

arrivals

arrivals(region, signal, inverse_velocities, times, distance, arrival_times, period=None)

What a gridded source puts through the region, by arrival time at distance.

A neutron emitted at inverse velocity a and time t reaches distance (m) at t + distance * a. That shear keeps areas, so each cell's share of the region is carried into arrival_times (s, bin edges) exactly: the density times the area between two lines of constant arrival time. distance 0 gives the emission times.

Shaped (arrival time, inverse velocity), since the shear leaves inverse velocity alone: sum over the last axis for the time profile at a monitor or the sample, or keep it for the wavelength as well. With period (s), arrival times are folded into [0, period) first, as a detector folds them into its frame -- give arrival_times in that range.

signal is read as :meth:integrate reads it. One flight path for every neutron: a guide's spread of paths smears arrivals, and is not included.

coverage

coverage(region, inverse_velocities, times)

The fraction of each (time, inverse velocity) cell the region covers.

Exact: each polygon is clipped to each cell it reaches, where :func:inverse_velocity_time_mask counts a cell whole or not at all. Shaped (time, inverse velocity), as the masks are; inverse_velocities (s/m) and times (s, at the source) are bin edges. Needs numpy.

integrate

integrate(region, signal, inverse_velocities, times)

How much of a gridded source the region transmits.

signal is the amount in each (time, inverse velocity) cell -- a histogram of the source, shaped as :meth:coverage is -- spread evenly over its cell. That reading is what makes this exact rather than approximate in the cells the region only partly covers, which :func:unmasked_probability counts whole.

transmission method descriptor

transmission()

transmission(self, inverse_velocity: float, time: float) -> float

The probability a neutron emitted at this (inverse velocity, time) gets through

ArrivalMoments

Bases: NamedTuple

What reaches a distance in each arrival-time bin: how much, and at what wavelength.

amount is in the units of the signal; wavelength and sigma are the mean and standard deviation of wavelength over it, in angstrom, NaN where nothing arrives.

On a grid

inverse_velocity_time_mask

inverse_velocity_time_mask(choppers, inverse_velocities, times, grow=0)

Which (inverse velocity, time) bins a chopper train passes.

inverse_velocities (s/m) and times (s, at the source) are bin edges, so the mask is one smaller in each direction. It comes back shaped (time, inverse velocity) -- time is the slow axis, which is how the C stores it and the opposite of the argument order.

grow expands each allowed region by that many bins in every direction. It is a blunt instrument: it admits inverse velocities no disk ever passes. Prefer :attr:Chopper.aperture for a beam of finite width, which opens the windows in time only, where the width actually acts.

Returns (mask, allowed_bin_count). Needs numpy.

unmasked_probability

unmasked_probability(signal, mask)

The fraction of signal lying in the allowed bins of mask.

Both are shaped (time, inverse velocity), as inverse_velocity_time_mask returns. This is the transmission an instrument sees -- a weighted fraction -- and is not the weight correction a sampler needs; that is :attr:MaskSampler.acceptance, which counts draws rather than intensity.

MaskSampler

MaskSampler(mask, inverse_velocities, times, inverse_velocity_minimum, inverse_velocity_range, time_minimum, time_range)

Bases: MaskSampler

Draws (inverse velocity, time) pairs from the allowed cells of a finished mask.

Sampling the whole region and discarding what the mask excludes spends the ray budget to keep the fraction the choppers pass, which for a real chopper train is a fraction of a percent. Drawing from the allowed cells keeps all of it and is the same distribution, provided every drawn weight is multiplied by :attr:acceptance.

mask is shaped (time, inverse velocity) and inverse_velocities/times are its bin edges. The four bounds describe the region the caller samples uniformly, which is not in general the region the grid covers -- a grid sized with ceil runs past it in the last row and column, and those cells are clipped before they are weighted so that they do not inflate :attr:acceptance.

MaskValue

Bases: IntEnum

What a cell of an inverse_velocity_time_mask holds.

A finished mask only ever holds EXCLUDED or INCLUDED -- GROWN is the marker the growing pass leaves behind and folds back into INCLUDED before it returns -- but it is written to file as it stands, so a mask read back from one can carry it.

Conversions

wavelength_to_inverse_velocity

wavelength_to_inverse_velocity(*args, **kwargs)

wavelength_to_inverse_velocity(wavelength: float) -> float

Angstrom to s/m, the way chopper-lib does it.

inverse_velocity_to_wavelength

inverse_velocity_to_wavelength(*args, **kwargs)

inverse_velocity_to_wavelength(inverse_velocity: float) -> float

s/m to angstrom, the way chopper-lib does it.