Skip to content

[ESSDIFFRACTION, BEER] Adapt d-spacing binning #745

Description

@celinedurniak

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions