예제 #1
0
def theta(gravity: sc.Variable, wavelength: sc.Variable, incident_beam: sc.Variable,
          scattered_beam: sc.Variable, sample_rotation: sc.Variable) -> sc.Variable:
    """
    Compute the theta angle, including gravity correction,
    This is similar to the theta calculation in SANS (see
    https://docs.mantidproject.org/nightly/algorithms/Q1D-v2.html#q-unit-conversion),
    but we ignore the horizontal `x` component.
    See the schematic in Fig 5 of doi: 10.1016/j.nima.2016.03.007.
    """
    grav = sc.norm(gravity)
    L2 = sc.norm(scattered_beam)
    y = sc.dot(scattered_beam, gravity) / grav
    y_correction = sc.to_unit(wavelength, _elem_unit(L2), copy=True)
    y_correction *= y_correction
    drop = L2**2
    drop *= grav * (m_n**2 / (2 * h**2))
    # Optimization when handling either the dense or the event coord of binned data:
    # - For the event coord, both operands have same dims, and we can multiply in place
    # - For the dense coord, we need to broadcast using non in-place operation
    if set(drop.dims).issubset(set(y_correction.dims)):
        y_correction *= drop
    else:
        y_correction = y_correction * drop
    y_correction += y
    out = sc.abs(y_correction, out=y_correction)
    out /= L2
    out = sc.asin(out, out=out)
    out -= sc.to_unit(sample_rotation, 'rad')
    return out
예제 #2
0
def _furthest_component(det_center, scipp_obj, additional):
    distances = [
        sc.norm(settings["center"] - det_center).value
        for settings in list(additional.values())
    ]
    max_displacement = sorted(distances)[-1]
    return max_displacement
예제 #3
0
파일: test_variable.py 프로젝트: yxqd/scipp
def test_norm():
    var = sp.Variable(dims=[Dim.X],
                      values=[[1, 0, 0], [3, 4, 0]],
                      unit=sp.units.m)
    expected = sp.Variable([Dim.X],
                           values=np.array([1.0, 5.0]),
                           unit=sp.units.m)
    assert sp.norm(var) == expected
예제 #4
0
def test_convert_beam_length_no_scatter():
    original = make_test_data(coords=('position', 'source_position'))
    converted = scn.convert(original,
                            origin='position',
                            target='Ltotal',
                            scatter=False)
    expected = sc.norm(make_position() - make_source_position())
    assert sc.identical(converted.coords['Ltotal'], expected)
예제 #5
0
def test_norm():
    var = sc.Variable(dims=[Dim.X],
                      values=[[1, 0, 0], [3, 4, 0]],
                      unit=sc.units.m,
                      dtype=sc.dtype.vector_3_float64)
    expected = sc.Variable([Dim.X],
                           values=np.array([1.0, 5.0]),
                           unit=sc.units.m)
    assert sc.norm(var) == expected
예제 #6
0
def _frames_from_slopes(data):
    detector_pos_norm = sc.norm(data.meta["position"])

    # Get the number of WFM frames
    choppers = {
        key: data.meta[key].value
        for key in ch.find_chopper_keys(data)
    }
    nframes = ch.cutout_angles_begin(choppers["chopper_wfm_1"]).sizes["frame"]

    # Now find frame boundaries
    frames = sc.Dataset()
    frames["time_min"] = sc.zeros(dims=["frame"],
                                  shape=[nframes],
                                  unit=sc.units.us)
    frames["time_max"] = sc.zeros_like(frames["time_min"])
    frames["delta_time_min"] = sc.zeros_like(frames["time_min"])
    frames["delta_time_max"] = sc.zeros_like(frames["time_min"])
    frames["wavelength_min"] = sc.zeros(dims=["frame"],
                                        shape=[nframes],
                                        unit=sc.units.angstrom)
    frames["wavelength_max"] = sc.zeros_like(frames["wavelength_min"])
    frames["delta_wavelength_min"] = sc.zeros_like(frames["wavelength_min"])
    frames["delta_wavelength_max"] = sc.zeros_like(frames["wavelength_min"])

    frames["time_correction"] = sc.zeros(dims=["frame"],
                                         shape=[nframes],
                                         unit=sc.units.us)

    near_wfm_chopper = choppers["chopper_wfm_1"]
    far_wfm_chopper = choppers["chopper_wfm_2"]

    # Distance between WFM choppers
    dz_wfm = sc.norm(far_wfm_chopper["position"].data -
                     near_wfm_chopper["position"].data)
    # Mid-point between WFM choppers
    z_wfm = 0.5 * (near_wfm_chopper["position"].data +
                   far_wfm_chopper["position"].data)
    # Distance between detector positions and wfm chopper mid-point
    zdet_minus_zwfm = sc.norm(data.meta["position"] - z_wfm)
    # Neutron mass to Planck constant ratio
    alpha = sc.to_unit(constants.m_n / constants.h, 'us/m/angstrom')

    near_t_open = ch.time_open(near_wfm_chopper)
    near_t_close = ch.time_closed(near_wfm_chopper)
    far_t_open = ch.time_open(far_wfm_chopper)

    for i in range(nframes):
        dt_lambda_max = near_t_close["frame", i] - near_t_open["frame", i]
        slope_lambda_max = dz_wfm / dt_lambda_max
        intercept_lambda_max = sc.norm(
            near_wfm_chopper["position"].data
        ) - slope_lambda_max * near_t_close["frame", i]
        t_lambda_max = (detector_pos_norm -
                        intercept_lambda_max) / slope_lambda_max

        slope_lambda_min = sc.norm(near_wfm_chopper["position"].data) / (
            near_t_close["frame", i] -
            (data.meta["source_pulse_length"] + data.meta["source_pulse_t_0"]))
        intercept_lambda_min = sc.norm(
            far_wfm_chopper["position"].data
        ) - slope_lambda_min * far_t_open["frame", i]
        t_lambda_min = (detector_pos_norm -
                        intercept_lambda_min) / slope_lambda_min

        t_lambda_min_plus_dt = (
            detector_pos_norm -
            (sc.norm(near_wfm_chopper["position"].data) -
             slope_lambda_min * near_t_close["frame", i])) / slope_lambda_min
        dt_lambda_min = t_lambda_min_plus_dt - t_lambda_min

        # Compute wavelength information
        lambda_min = (t_lambda_min + 0.5 * dt_lambda_min -
                      far_t_open["frame", i]) / (alpha * zdet_minus_zwfm)
        lambda_max = (t_lambda_max - 0.5 * dt_lambda_max -
                      far_t_open["frame", i]) / (alpha * zdet_minus_zwfm)
        dlambda_min = dz_wfm * lambda_min / zdet_minus_zwfm
        dlambda_max = dz_wfm * lambda_max / zdet_minus_zwfm

        frames["time_min"]["frame", i] = t_lambda_min
        frames["delta_time_min"]["frame", i] = dt_lambda_min
        frames["time_max"]["frame", i] = t_lambda_max
        frames["delta_time_max"]["frame", i] = dt_lambda_max
        frames["wavelength_min"]["frame", i] = lambda_min
        frames["wavelength_max"]["frame", i] = lambda_max
        frames["delta_wavelength_min"]["frame", i] = dlambda_min
        frames["delta_wavelength_max"]["frame", i] = dlambda_max
        frames["time_correction"]["frame", i] = far_t_open["frame", i]

    frames["wfm_chopper_mid_point"] = z_wfm
    return frames
예제 #7
0
파일: plot.py 프로젝트: scipp/ess
def time_distance_diagram(data: sc.DataArray, **kwargs) -> plt.Figure:
    """
    Plot the time-distance diagram for a WFM beamline.
    The expected input is a Dataset or DataArray containing the chopper cascade
    information as well as the description of the source pulse.
    This internally calls the `get_frames` method which is used to compute the
    frame properties for stitching.
    """

    # Get the frame properties
    frames = get_frames(data, **kwargs)

    # Find detector pixel furthest away from source
    source_pos = data.meta["source_position"]
    furthest_detector_pos = sc.max(sc.norm(data.meta["position"] -
                                           source_pos)).value
    pulse_rectangle_height = furthest_detector_pos / 50.0
    tmax_glob = sc.max(frames["time_max"].data).value

    # Create figure and axes
    fig, ax = plt.subplots(1, 1, figsize=(9, 7))
    ax.grid(True, color='lightgray', linestyle="dotted")
    ax.set_axisbelow(True)

    # Draw a light grey rectangle from the origin to t_0 + pulse_length + t_0
    # The second t_0 should in fact be the end of the pulse tail, but since this
    # information is not needed for computing the frame properties, it may
    # not be present in the description of the beamline.
    # So we fake this by simply using t_0 again at the end of the pulse.
    ax.add_patch(
        Rectangle((0, 0),
                  (2.0 * data.meta["source_pulse_t_0"] +
                   data.meta["source_pulse_length"]).value,
                  -pulse_rectangle_height,
                  lw=1,
                  fc='lightgrey',
                  ec='k',
                  zorder=10))
    # Draw a dark grey rectangle from t_0 to t_0 + pulse_length to represent the usable
    # pulse.
    ax.add_patch(
        Rectangle((data.meta["source_pulse_t_0"].value, 0),
                  data.meta["source_pulse_length"].value,
                  -pulse_rectangle_height,
                  lw=1,
                  fc='grey',
                  ec='k',
                  zorder=11))
    # Indicate source pulse and add the duration.
    ax.text(data.meta["source_pulse_t_0"].value,
            -pulse_rectangle_height,
            "Source pulse ({} {})".format(
                data.meta["source_pulse_length"].value,
                data.meta["source_pulse_length"].unit),
            ha="left",
            va="top",
            fontsize=6)

    # Plot the chopper openings as segments
    # for name, chopper in data.meta["choppers"].value.items():
    for name in ch.find_chopper_keys(data):
        chopper = data.meta[name].value
        yframe = sc.norm(chopper["position"].data - source_pos).value
        time_open = ch.time_open(chopper).values
        time_close = ch.time_closed(chopper).values
        tmin = 0.0
        for fnum in range(len(time_open)):
            tmax = time_open[fnum]
            ax.plot([tmin, tmax], [yframe] * 2, color='k')
            tmin = time_close[fnum]
        ax.plot([tmin, tmax_glob], [yframe] * 2, color='k')
        ax.text(2.0 * time_close[-1] - time_open[-1],
                yframe,
                name,
                ha="left",
                va="bottom")

    # Plot the shades of possible neutron paths
    for i in range(frames.sizes["frame"]):

        col = "C{}".format(i)
        frame = frames["frame", i]
        for dim in data.meta["position"].dims:
            frame = frame[dim, 0]

        # Minimum wavelength
        lambda_min = np.array(
            [[(data.meta["source_pulse_t_0"] +
               data.meta["source_pulse_length"] -
               frame['delta_time_min']).value, 0],
             [(data.meta["source_pulse_t_0"] +
               data.meta["source_pulse_length"]).value, 0],
             [(frame["time_min"] + frame["delta_time_min"]).value,
              furthest_detector_pos],
             [frame["time_min"].value, furthest_detector_pos]])

        # Maximum wavelength
        lambda_max = np.array(
            [[data.meta["source_pulse_t_0"].value, 0],
             [(data.meta["source_pulse_t_0"] + frame['delta_time_max']).value,
              0], [frame["time_max"].value, furthest_detector_pos],
             [(frame["time_max"] - frame["delta_time_max"]).value,
              furthest_detector_pos]])

        ax.plot(np.concatenate((lambda_min[:, 0], lambda_min[0:1, 0])),
                np.concatenate((lambda_min[:, 1], lambda_min[0:1, 1])),
                color=col,
                lw=1)

        ax.plot(np.concatenate((lambda_max[:, 0], lambda_max[0:1, 0])),
                np.concatenate((lambda_max[:, 1], lambda_max[0:1, 1])),
                color=col,
                lw=1)

        ax.fill([
            lambda_max[0, 0], lambda_max[-1, 0], lambda_min[2, 0],
            lambda_min[1, 0]
        ], [
            lambda_max[0, 1], lambda_max[-1, 1], lambda_min[2, 1],
            lambda_min[1, 1]
        ],
                alpha=0.3,
                color=col,
                zorder=-5)

        ax.fill(lambda_min[:, 0], lambda_min[:, 1], color='w', zorder=-4)
        ax.fill(lambda_max[:, 0], lambda_max[:, 1], color='w', zorder=-4)

        ax.text(0.5 * (frame["time_min"] + frame["delta_time_min"] +
                       frame["time_max"] - frame["delta_time_max"]).value,
                furthest_detector_pos,
                "Frame {}".format(i + 1),
                ha="center",
                va="top")

    # Add thick solid line for the detector position, spanning the entire width
    ax.plot([0, tmax_glob], [furthest_detector_pos] * 2, lw=3, color='grey')
    ax.text(0.0, furthest_detector_pos, "Detector", va="bottom", ha="left")

    # Set axis labels
    ax.set_xlabel("Time [microseconds]")
    ax.set_ylabel("Distance [m]")

    return fig
예제 #8
0
def frames_analytical(data: Union[sc.DataArray, sc.Dataset]) -> sc.Dataset:
    """
    Compute analytical frame boundaries and shifts based on chopper
    parameters and detector pixel positions.
    A set of frame boundaries is returned for each pixel.
    The frame shifts are the same for all pixels.
    See Schmakat et al. (2020);
    https://www.sciencedirect.com/science/article/pii/S0168900220308640
    for a description of the procedure.

    TODO: This currently ignores scattering paths, only the distance from
    source to pixel.
    For imaging, this is what we want, but for scattering techniques, we should
    use l1 + l2 in the future.
    """

    # Identify the WFM choppers based on their `kind` property
    wfm_choppers = {}
    for name in ch.find_chopper_keys(data):
        chopper = data.meta[name].value
        if chopper["kind"].value == "wfm":
            wfm_choppers[name] = chopper
    if len(wfm_choppers) != 2:
        raise RuntimeError("The number of WFM choppers is expected to be 2, "
                           "found {}".format(len(wfm_choppers)))
    # Find the near and far WFM choppers based on their positions relative to the source
    wfm_chopper_names = list(wfm_choppers.keys())
    if (sc.norm(wfm_choppers[wfm_chopper_names[0]]["position"].data -
                data.meta["source_position"]) <
            sc.norm(wfm_choppers[wfm_chopper_names[1]]["position"].data -
                    data.meta["source_position"])).value:
        near_index = 0
        far_index = 1
    else:
        near_index = 1
        far_index = 0
    near_wfm_chopper = wfm_choppers[wfm_chopper_names[near_index]]
    far_wfm_chopper = wfm_choppers[wfm_chopper_names[far_index]]

    # Compute distances for each detector pixel
    detector_positions = data.meta["position"] - data.meta["source_position"]

    # Container for frames information
    frames = sc.Dataset()

    # Distance between WFM choppers
    dz_wfm = sc.norm(far_wfm_chopper["position"].data -
                     near_wfm_chopper["position"].data)
    # Mid-point between WFM choppers
    z_wfm = 0.5 * (
        near_wfm_chopper["position"].data +
        far_wfm_chopper["position"].data) - data.meta["source_position"]
    # Ratio of WFM chopper distances
    z_ratio_wfm = (sc.norm(far_wfm_chopper["position"].data -
                           data.meta["source_position"]) /
                   sc.norm(near_wfm_chopper["position"].data -
                           data.meta["source_position"]))
    # Distance between detector positions and wfm chopper mid-point
    zdet_minus_zwfm = sc.norm(detector_positions - z_wfm)

    # Neutron mass to Planck constant ratio
    alpha = sc.to_unit(constants.m_n / constants.h, 'us/m/angstrom')

    # Frame time corrections: these are the mid-time point between the WFM choppers,
    # which is the same as the opening edge of the second WFM chopper in the case
    # of optically blind choppers.
    frames["time_correction"] = ch.time_open(far_wfm_chopper)

    # Find delta_t for the min and max wavelengths:
    # dt_lambda_max is equal to the time width of the WFM choppers windows
    dt_lambda_max = ch.time_closed(near_wfm_chopper) - ch.time_open(
        near_wfm_chopper)

    # t_lambda_max is found from the relation between t and delta_t: equation (2) in
    # Schmakat et al. (2020).
    t_lambda_max = (dt_lambda_max / dz_wfm) * zdet_minus_zwfm

    # t_lambda_min is found from the relation between lambda_N and lambda_N+1,
    # equation (3) in Schmakat et al. (2020).
    t_lambda_min = t_lambda_max * z_ratio_wfm - data.meta[
        "source_pulse_length"] * (zdet_minus_zwfm / sc.norm(
            near_wfm_chopper["position"].data - data.meta["source_position"]))

    # dt_lambda_min is found from the relation between t and delta_t: equation (2)
    # in Schmakat et al. (2020), and using the expression for t_lambda_max.
    dt_lambda_min = dt_lambda_max * z_ratio_wfm - data.meta[
        "source_pulse_length"] * dz_wfm / sc.norm(
            near_wfm_chopper["position"].data - data.meta["source_position"])

    # Compute wavelength information
    lambda_min = t_lambda_min / (alpha * zdet_minus_zwfm)
    lambda_max = t_lambda_max / (alpha * zdet_minus_zwfm)
    dlambda_min = dz_wfm * lambda_min / zdet_minus_zwfm
    dlambda_max = dz_wfm * lambda_max / zdet_minus_zwfm

    # Frame edges and resolutions for each pixel.
    # The frames do not stop at t_lambda_min and t_lambda_max, they also include the
    # fuzzy areas (delta_t) at the edges.
    frames["time_min"] = t_lambda_min - (
        0.5 * dt_lambda_min) + frames["time_correction"]
    frames["delta_time_min"] = dt_lambda_min

    frames["time_max"] = t_lambda_max + (
        0.5 * dt_lambda_max) + frames["time_correction"]
    frames["delta_time_max"] = dt_lambda_max
    frames["wavelength_min"] = lambda_min
    frames["wavelength_max"] = lambda_max
    frames["delta_wavelength_min"] = dlambda_min
    frames["delta_wavelength_max"] = dlambda_max

    frames["wfm_chopper_mid_point"] = 0.5 * (
        near_wfm_chopper["position"].data + far_wfm_chopper["position"].data)

    return frames
예제 #9
0
def make_fake_beamline(
        chopper_wfm_1_position=sc.vector(value=[0.0, 0.0, 6.775], unit='m'),
        chopper_wfm_2_position=sc.vector(value=[0.0, 0.0, 7.225], unit='m'),
        frequency=sc.scalar(56.0, unit=sc.units.one / sc.units.s),
        lambda_min=sc.scalar(1.0, unit='angstrom'),
        pulse_length=sc.scalar(2.86e-03, unit='s'),
        pulse_t_0=sc.scalar(1.3e-4, unit='s'),
        nframes=2):
    """
    Fake chopper cascade with 2 optically blind WFM choppers.
    Based on mathematical description in Schmakat et al. (2020);
    https://www.sciencedirect.com/science/article/pii/S0168900220308640
    """

    dim = 'frame'
    # Neutron mass to Planck constant ratio
    alpha = sc.to_unit(m_n / h, 's/m/angstrom')
    omega = (2.0 * np.pi * sc.units.rad) * frequency

    cutout_angles_center_wfm_1 = sc.empty(dims=[dim], shape=[nframes], unit='rad')
    cutout_angles_center_wfm_2 = sc.empty_like(cutout_angles_center_wfm_1)
    cutout_angles_width = sc.empty_like(cutout_angles_center_wfm_1)

    for i in range(nframes):
        # Equation (3) in Schmakat et al. (2020)
        lambda_max = (pulse_length +
                      alpha * lambda_min * sc.norm(chopper_wfm_1_position)) / (
                          alpha * sc.norm(chopper_wfm_2_position))
        # Equation (4) in Schmakat et al. (2020)
        theta = omega * (pulse_length + alpha *
                         (lambda_min - lambda_max) * sc.norm(chopper_wfm_1_position))
        # Equation (5) in Schmakat et al. (2020)
        phi_wfm_1 = omega * (
            pulse_t_0 + 0.5 * pulse_length + 0.5 * alpha *
            (lambda_min + lambda_max) * sc.norm(chopper_wfm_1_position))
        # Equation (6) in Schmakat et al. (2020)
        phi_wfm_2 = omega * (pulse_t_0 + 1.5 * pulse_length + 0.5 * alpha * (
            (3.0 * lambda_min) - lambda_max) * sc.norm(chopper_wfm_1_position))

        cutout_angles_width[dim, i] = theta
        cutout_angles_center_wfm_1[dim, i] = phi_wfm_1
        cutout_angles_center_wfm_2[dim, i] = phi_wfm_2

        lambda_min = lambda_max

    return {
        "chopper_wfm_1":
        sc.scalar(
            make_chopper(frequency=frequency,
                         phase=sc.scalar(0.0, unit='deg'),
                         position=chopper_wfm_1_position,
                         cutout_angles_center=cutout_angles_center_wfm_1,
                         cutout_angles_width=cutout_angles_width,
                         kind=sc.scalar('wfm'))),
        "chopper_wfm_2":
        sc.scalar(
            make_chopper(frequency=frequency,
                         phase=sc.scalar(0.0, unit='deg'),
                         position=chopper_wfm_2_position,
                         cutout_angles_center=cutout_angles_center_wfm_2,
                         cutout_angles_width=cutout_angles_width,
                         kind=sc.scalar('wfm'))),
        'position':
        sc.vector(value=[0., 0., 60.], unit='m'),
        "source_pulse_length":
        sc.to_unit(pulse_length, 'us'),
        "source_pulse_t_0":
        sc.to_unit(pulse_t_0, 'us'),
        "source_position":
        sc.vector(value=[0.0, 0.0, 0.0], unit='m')
    }
예제 #10
0
파일: stitch_test.py 프로젝트: scipp/ess
def _do_stitching_on_beamline(wavelengths, dim, event_mode=False):
    # Make beamline parameters for 6 frames
    coords = wfm.make_fake_beamline(nframes=6)

    # They are all created half-way through the pulse.
    # Compute their arrival time at the detector.
    alpha = sc.to_unit(constants.m_n / constants.h, 's/m/angstrom')
    dz = sc.norm(coords['position'] - coords['source_position'])
    arrival_times = sc.to_unit(alpha * dz * wavelengths,
                               'us') + coords['source_pulse_t_0'] + (
                                   0.5 * coords['source_pulse_length'])
    coords[dim] = arrival_times

    # Make a data array that contains the beamline and the time coordinate
    tmin = sc.min(arrival_times)
    tmax = sc.max(arrival_times)
    dt = 0.1 * (tmax - tmin)

    if event_mode:
        num = 2
    else:
        num = 2001
    time_binning = sc.linspace(dim=dim,
                               start=(tmin - dt).value,
                               stop=(tmax + dt).value,
                               num=num,
                               unit=dt.unit)
    events = sc.DataArray(data=sc.ones(dims=['event'],
                                       shape=arrival_times.shape,
                                       unit=sc.units.counts,
                                       with_variances=True),
                          coords=coords)
    if event_mode:
        da = sc.bin(events, edges=[time_binning])
    else:
        da = sc.histogram(events, bins=time_binning)

    # Find location of frames
    frames = wfm.get_frames(da)

    stitched = wfm.stitch(frames=frames, data=da, dim=dim, bins=2001)

    wav = scn.convert(stitched,
                      origin='tof',
                      target='wavelength',
                      scatter=False)
    if event_mode:
        out = wav
    else:
        out = sc.rebin(wav,
                       dim='wavelength',
                       bins=sc.linspace(dim='wavelength',
                                        start=1.0,
                                        stop=10.0,
                                        num=1001,
                                        unit='angstrom'))

    choppers = {key: da.meta[key].value for key in ch.find_chopper_keys(da)}
    # Distance between WFM choppers
    dz_wfm = sc.norm(choppers["chopper_wfm_2"]["position"].data -
                     choppers["chopper_wfm_1"]["position"].data)
    # Delta_lambda  / lambda
    dlambda_over_lambda = dz_wfm / sc.norm(
        coords['position'] - frames['wfm_chopper_mid_point'].data)

    return out, dlambda_over_lambda
예제 #11
0
def make_L2():
    return sc.norm(make_scattered_beam())
예제 #12
0
def make_L1():
    return sc.norm(make_incident_beam())