scipp / scipp/ess

[ESSDIFFRACTION, BEER] Adapt d-spacing binning

Open
#745 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
1
Forks
3
Avg merge
2d 11h
Merged PRs (30d)
17

Description

For binning in d-spacing, provide only the number of bins and calculate the min and max in the notebook for the different banks using Premek’s code.


def read_info_and_get_bins(wf: BeerPowderMcStasWorkflow, size_d: int = 3000, size_tth: int = 500) -> dict:
  """
  Function that loads Sciline workflow upto RawDetector and return important 
  information used for further processing and creates d-spacing, two theta 
  and Q bins. Number of bins can be modified for d and Q and or two theta.  

  Parameters
  ----------
  data_path : str
    Path to the NeXus file without the file name but with the directory delimiter  
  size_d : int, optional
    Number of the d-spacing and Q bins. Default is 3000.     
  size_tth : int, optional
    Number of the two theta bins. Default is 500.

  Returns
  -------
  dict
    Dictionary containing the following variables
    - 'wave_min/max' - values of accepted wavwlengthes
    - 't_det_min/max' - values of accepted TOF
    - 'd_lamda' - size of the accepted wavelength range
    and bank ['north', 'south'] decicated 
    - 'tth' - value of two theta angle of the bank
    - 'd_bank' - position name of the bank S1-3, N1-3
    - 'd_bins' - scipp.DataArray of d-spacing bins
    - 'tth_bins' - scipp.DataArray of two theta bins
    - 'q_bins' - scipp.DataArray of Q bins

  """
  # setup of the result dictionary
  result = {}

  # reading the Nexus file 
  print('Loading "RawDetector" for the Nexus file ... ', end='', flush=True)
  start = time.perf_counter() 
  bank = DetectorBank.north
  wf[DetectorBank] = bank
  data = wf.compute(RawDetector[SampleRun])
  duration = time.perf_counter() - start
  print(f'DONE in {_format_time(duration)}')

  print('')
  print('Measurement information summary')
  print('-------------------------------')

  # adjsting the common variables
  t = data.coords['nominal_time_at_chopper'] - sc.scalar(1.6, unit='ms').to(unit='s')
  dist = data.coords['source_to_wavelength_definition_chopper_distance']
  wave = (t * sc.constants.h / sc.constants.m_n / dist).to(unit='angstroms')
  print(f'Source chopper distance: {dist.value:0.3f} [{dist.unit}]')
  print(f'Nominal wavelength: {wave.value:0.1f} [{wave.unit}]')
  print(f'Time when neutrons at chopper distance: {t.value:0.5f} [{t.unit}]')

  det = sc.mean(data.coords['moderator_to_detector_distance'])-dist
  d_lambda = 1.73*sc.Unit('angstrom')
  result['d_lambda'] = d_lambda
  print(f'Acceptance wavelength range fixed to: {d_lambda.value} [{d_lambda.unit}]')

  wave_min = wave - d_lambda/2
  wave_max = wave + d_lambda/2
  t_det_min = (sc.constants.m_n / sc.constants.h * det * wave_min).to(unit='ms')
  t_det_max = (sc.constants.m_n / sc.constants.h * det * wave_max).to(unit='ms')
  L2 = sc.norm(sc.mean(data.coords['position'])-data.coords['sample_position'])
  L1 = det - L2
  result['wave_min'] = wave_min
  result['wave_max'] = wave_max
  result['t_det_min'] = t_det_min
  result['t_det_max'] = t_det_max
  print(f'Source-sample distance (L1): {L1.value:0.3f} [{L1.unit}]')
  print(f'Sample-detector distance (L2): {L2.value:0.3f} [{L2.unit}]')
  print(f'Source-detector distance (L1+L2): {det.value:0.3f} [{det.unit}]')
  print(f'Minimal wavelength accepted: {wave_min.value:0.2f} [{wave_min.unit}]')
  print(f'Maximal wavelength accepted: {wave_max.value:0.2f} [{wave_max.unit}]')
  print(f'Wave_min travel time: {t_det_min.value:0.5f} [{t_det_min.unit}]')
  print(f'Wave_max travel time: {t_det_max.value:0.5f} [{t_det_max.unit}]')

  # setting the wavelength mask to get rid of the frame overlap
  wf[WavelengthMask] = lambda wave: (wave_min > wave) | (wave_max < wave)

  # getting info about the detector positions, expecting that the McStas model
  # simulate the detectors in 180 deg alignement, ie. is one 45 deg then other -135 deg,
  # when one 90 the second -90, etc.
  det_pos = (sc.norm(data.coords['sample_position']) - 
        sc.norm(sc.mean(data.coords['detector_position']))).value
  if det_pos < -0.5: 
    tth = {'north': 45, 'south': 135}
    d_bank = {'north': 'N1', 'south': 'S3'}
  elif det_pos > 0.5: 
    tth = {'north': 135, 'south': 45}
    d_bank = {'north': 'N3', 'south': 'S1'}
  else: 
    tth = {'north': 90, 'south': 90}
    d_bank = {'north': 'N2', 'south': 'S2'}

  print('')
  print('Individual detector information')
  d_min = {}
  d_max = {}
  q_min = {}
  q_max = {}
  d_bins = {}
  tth_bins = {}
  q_bins = {}
  tth_bins = {}
  for bank in [DetectorBank.north, DetectorBank.south]:
    print('-------------------------------')
    n = bank.name
    print(f'"{n}" detector is {d_bank[n]} at {tth[n]} [deg]')

    d_min[n] = wave_min / 2 / sc.sin(sc.scalar((tth[n] + 15)/2, unit='deg'))
    d_max[n] = wave_max / 2 / sc.sin(sc.scalar((tth[n] - 15)/2, unit='deg'))
     
    d_min[n].value = _truncate(d_min[n].value, 2)
    d_max[n].value = _truncate(d_max[n].value, 2)

    print(f'Minimal d-spacing accepted: {d_min[n].value:0.2f} [{d_min[n].unit}]')
    print(f'Maximal d-spacing accepted: {d_max[n].value:0.2f} [{d_max[n].unit}]')

    d_bins[n] = sc.linspace(dim='dspacing', start=d_min[n], stop=d_max[n], num=size_d, unit='angstrom')
   
    print(f'D-spacing bins [{len(d_bins[n])}] between {d_min[n].value} and {d_max[n].value} ' + 
       f'with step {(d_bins[n][1]-d_bins[n][0]).value:0.6f} [{d_max[n].unit}]')
     
    tth_bins[n] = sc.linspace(dim='two_theta', start=tth[n] - 15, stop=tth[n] + 15, num=size_tth, unit='deg')
    print(f'TwoTheta bins [{len(tth_bins[n])}] between {tth_bins[n][0].value:0.1f} and {tth_bins[n][-1].value:0.1f} ' + 
       f'with step {(tth_bins[n][1]-tth_bins[n][0]).value:0.4f} [{tth_bins[n].unit}]')

    # QBins is not part of the BEER Sciline workflow
    # The calculations related to Q below will be used for the final 1D Q-plots
    q_min[n] = 2 * np.pi / d_max[n]
    q_max[n] = 2 * np.pi / d_min[n]

    q_min[n].value = _truncate(q_min[n].value, 2)
    q_max[n].value = _truncate(q_max[n].value, 2)

    q_bins[n] = sc.linspace(dim='Q', start=q_min[n], stop=q_max[n], num=size_d, unit='1/angstrom')
    print(f'Q bins [{len(q_bins[n])}] between {q_min[n].value} and {q_max[n].value} ' + 
       f'with step {(q_bins[n][1]-q_bins[n][0]).value:0.6f} [{q_max[n].unit}]')

  print('-------------------------------')

  result['d_bins'] = d_bins
  result['tth_bins'] = tth_bins
  result['q_bins'] = q_bins
  result['d_bank'] = d_bank
  result['tth'] = tth
  return result

This function describes all necessary steps needed before starting the full workflow.
We have to set up the mask, get proper bins depending on the detector position…. All this could also be part of the workflow.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the read_info_and_get_bins function shown in the issue and trace how the full BEER Sciline workflow consumes its mask and d-spacing bins. Verify how detector banks and positions are represented before changing the interface to accept only bin counts. Done means the wavelength mask and per-bank d-spacing bounds are calculated in the notebook or workflow, with both banks receiving the requested number of bins.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
data
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.