/*******************************************************************************
*
* McXtrace, x-ray tracing package
*         Copyright, All rights reserved
*         DTU Physics, Kgs. Lyngby, Denmark
*         Synchrotron SOLEIL, Saint-Aubin, France
*
* Component: Air
*
* %Identification
* Written by: M. B. Nielsen
* Date: 05.02.2015
* Origin: DTU Fysik, NBI
* Release: McXtrace 1.4
*
* Component simulating atmospheric air.
*
* %Description
* The component simulates air and can be inserted as if it was just some extra sample placed somewhere in the beam line.
* The air component is intended to be used in all kinds of setups where air may introduce background.
* Code structure in this component is based on the component Saxs_spheres.
* The shape of the sample may be a filled box with dimensions 
* xwidth, yheight, zdepth, a filled cylinder with dimensions radius and yheight,
* a filled sphere with radius R.
* (NB: As we assume air to be an ideal gas, the volume fractions of the elements in the gas are merely the mole fractional part of the given element.
* From this the number density of atoms/molecules is calculated)
* The air is dry and assumed to be made of nitrogen, oxygen and argon - all other constituents are neglected.
*
* So far the calculations of the scattering probability (and hence also the weight multiplier) assumes the x-ray source to be unpolarized.
* Further the component does not yet account for absorption of x-rays. I.e. absorption is simply omitted, but it may OR may NOT be negligible.
* I have not yet looked into this last question, so I can't say if the lack of absorption is a bad thing or if it is allowable.
*
* Example:
* COMPONENT air1 = Air(
*    frac = 0.4, pressure = 50000, temperature = 270,
*    xwidth = 0.5, yheight = 0.5, zdepth = 1.5, target_index = 1,
*    focus_xw = 0.5, focus_yh = 0.5)
*  AT (0, 0, 15) RELATIVE Origin
*
* %Parameters
*
* xwidth:  [m]    Width of the air volume.
* yheight: [m]    Height of the air volume. 
* zdepth:  [m]    Depth of the air volume.
* radius:  [m]    Radius of spherical or cylindrical air volume.
* target_x:[m]    X-coordinate of sampling window.
* target_y:[m]    Y-coordinate of sampling window.
* target_z:[m]    Z-coordinate of sampling window.
* target_index: [ ] Index of target component putting sampling window on a subsequent component.
* focus_xw:[m]    Width of the sampling window.
* focus_yh:[m]    Height of the sampling window.
* focus_ah:[rad]  Vertical (height) opening angle of sampling window.
* focus_aw:[rad]  Horizontal (width) opening angle of sampling window.
* focus_r: [rad]  Radius of circular sampling window. 
* frac:    [0-1]  Fraction of rays to scatter from the air
* pressure: [Pa]  Total pressure of the air gas
* temperature:[K] Absolute temperature
* 
* %End
*******************************************************************************/

DEFINE COMPONENT Air



    /* number-density (num_den) is declared and computed in TRACE !!! */
SETTING PARAMETERS (frac=0.3, pressure=101325, temperature=273.15+21.1,
        R_gas=8.3144621, bond_N=1.0976, bond_O=1.2074, Nitrogen_part=0.781, Oxygen_part=0.21, Argon_part=0.009,
        xwidth=0, yheight=0, zdepth=0, radius=0,
        target_x=0, target_y=0, target_z=6, int target_index=0, 
        focus_xw=0, focus_yh=0, focus_aw=0, focus_ah=0, focus_r=0)

DECLARE
%{
  int shape;
%}

INITIALIZE
%{
  shape = -1; /* -1:no shape, 0:cyl, 1:box, 2:sphere  */
  if (xwidth && yheight && zdepth)
    shape = 1; /* box */
  else if (radius > 0 && yheight)
    shape = 0; /* cylinder */
  else if (radius > 0 && !yheight)
    shape = 2; /* sphere */

  if (shape < 0) {
    fprintf (stderr, "ERROR: %s: Air volume has invalid dimensions, please check parameter values.\n", NAME_CURRENT_COMP);
    exit (-1);
  }

  /* now compute target coords if a component index is supplied */
  if (!target_index && !target_x && !target_y && !target_z)
    target_index = 1;

  if (target_index) {
    Coords ToTarget;
    ToTarget = coords_sub (POS_A_COMP_INDEX (INDEX_CURRENT_COMP + target_index), POS_A_CURRENT_COMP);
    ToTarget = rot_apply (ROT_A_CURRENT_COMP, ToTarget);
    coords_get (ToTarget, &target_x, &target_y, &target_z);
  }

  if (!(target_x || target_y || target_z)) {
    printf ("INFO %s: The target is not defined. Using direct beam (Z-axis).\n", NAME_CURRENT_COMP);
    target_z = 1;
  }
%}
TRACE
%{
  double l0, l1, l_full, l, dl, ran_var;
  double aim_x = 0, aim_y = 0, aim_z = 1, axis_x, axis_y, axis_z;
  double f0, solid_angle, sc_angle, kx_i, ky_i, kz_i, k, q, qx, qy, qz, polarization, Ptot_sc_unpolarized;
  char intersect = 0;
  /*material constants for the constituent gases*/
  double an[4] = { 12.2126, 3.1322, 2.0125, 1.1663 };
  double ao[4] = { 3.0485, 2.2868, 1.5463, 0.867 };
  double aar[4] = { 7.4845, 6.7723, 0.6539, 1.6442 };
  double bn[4] = { 0.0057, 9.8933, 28.9975, 0.5826 };
  double bo[4] = { 13.2771, 5.7011, 0.3239, 32.9089 };
  double bar[4] = { 0.9072, 14.8407, 43.8983, 33.3929 };
  double cn = -11.529;
  double co = 0.2508;
  double car = 1.4445;

  /* Intersection X_ray trajectory / sample (sample surface) */
  if (shape == 0)
    intersect = cylinder_intersect (&l0, &l1, x, y, z, kx, ky, kz, radius, yheight);
  else if (shape == 1)
    intersect = box_intersect (&l0, &l1, x, y, z, kx, ky, kz, xwidth, yheight, zdepth);
  else if (shape == 2)
    intersect = sphere_intersect (&l0, &l1, x, y, z, kx, ky, kz, radius);

  if (intersect) {

    if (l0 < 0) {
      fprintf (stderr, "photon already inside sample %s - absorbing\n", NAME_CURRENT_COMP);
      ABSORB;
    }

    /* Xray enters at l=l0. */
    l_full = (l1 - l0); /* Length of full path through the air */
    k = sqrt (kx * kx + ky * ky + kz * kz);
    dl = rand01 () * (l1 - l0) + l0; /* Time of scattering */
    PROP_DL (dl);                    /* Point of scattering */
    l = (dl - l0);                   /* Penetration in the air */

    /* The total probability for the (unpolarized) X-ray to interact with the air as a function of k is approximated
       with a 9th degree polynomium to the logarithm
       of the total probability. This is done because the real expression had to be solved numerically.
       The exp-polynomium has units of squared Angstrom and to become the actual total scattering probabillity it has
       to be multiplied by l_full*P*NA/(R*T). The factor of P*NA/(R*T) does however have units of reciprocal cubed meters so we have
       to scale by 10^-30 [m^2/AA^2] to match the polynomium. NB: The factor of (n_i/n)*P*NA/(R*T) constitues the particle number
       density; while the factor of (n_i/n) is contained in the polynomium, the factor of P*NA/(R*T) has to be multiplied below.
       The variable l_full has units of meters so it too has to be rescaled to Angstrom - hence we multiply by 10^10 [AA/m].*/

    Ptot_sc_unpolarized = exp (-13.239209120968501922 + 7.7523973158902582507e-1 * k - 1.9753647234751300397 * k * k + 1.1395382484098412215 * k * k * k
                               - 3.5117800398913380701e-1 * k * k * k * k + 6.493474609841591379e-2 * k * k * k * k * k
                               - 7.40740935645497008e-3 * k * k * k * k * k * k + 5.1039561005291247e-4 * k * k * k * k * k * k * k
                               - 1.947399876590436e-5 * k * k * k * k * k * k * k * k + 3.1587814532171995885e-7 * k * k * k * k * k * k * k * k * k)
                          * l_full * pressure * NA / (R_gas * temperature) * 1e-30 * 1e10;

    /* The mc-choice to scatter from the air or not is governed by the variable 'frac'*/
    /* NB: The variable 'Ptot_sc' has to be evaluated BEFORE we make the mc-choice because we also need to be able to weight the non-interacting rays.*/
    /* This non-interaction weight is p *= (1 - Ptot_sc_unpolarized)/(1 - frac)*/
    ran_var = rand01 ();
    if (ran_var < frac) {
      p *= Ptot_sc_unpolarized / frac;
      kx_i = kx;
      ky_i = ky;
      kz_i = kz;

      /* Vector pointing at target (anal./det.) */
      if ((target_x || target_y || target_z)) {
        aim_x = target_x;
        aim_y = target_y;
        aim_z = target_z;
      }

      if (focus_aw && focus_ah) {
        randvec_target_rect_angular (&kx, &ky, &kz, &solid_angle, aim_x, aim_y, aim_z, focus_aw, focus_ah, ROT_A_CURRENT_COMP);
      } else if (focus_xw && focus_yh) {
        randvec_target_rect_real (&kx, &ky, &kz, &solid_angle, aim_x, aim_y, aim_z, focus_xw, focus_yh, ROT_A_CURRENT_COMP, x, y, z, 2);
      } else {
        randvec_target_circle (&kx, &ky, &kz, &solid_angle, aim_x, aim_y, aim_z, focus_r);
      }

      NORM (kx, ky, kz);
      kx *= k;
      ky *= k;
      kz *= k;
      qx = (kx_i - kx);
      qy = (ky_i - ky);
      qz = (kz_i - kz);
      q = sqrt (qx * qx + qy * qy + qz * qz);
      sc_angle = acos ((kx_i * kx + ky_i * ky + kz_i * kz) / (k * k));
      polarization = (1 + cos (sc_angle) * cos (sc_angle)) / 2;

      /* Now we pick out which type of particle in the gas to scatter from assuming that air behaves as an ideal gas.
         This approximation is valid only when 0 < q < 25 [AA^-1] and hence 0<k<12.5 [AA^-1] ensures this range for elastic scattering*/
      ran_var = rand01 ();
      if (ran_var < 1 / 3) {
        f0 = an[0] * exp (-bn[0] * (q / (4 * PI)) * (q / (4 * PI))) + an[1] * exp (-bn[1] * (q / (4 * PI)) * (q / (4 * PI)))
             + an[2] * exp (-bn[2] * (q / (4 * PI)) * (q / (4 * PI))) + an[3] * exp (-bn[3] * (q / (4 * PI)) * (q / (4 * PI))) + cn;

        p *= (3 * solid_angle) * (Nitrogen_part * pressure * NA / (R_gas * temperature)) * l_full * RE * RE * f0 * f0 * polarization
             * (2 + 2 * sin (q * bond_N) / (q * bond_N)) * 1e-30 * 1e10;
      } else if (1 / 3 <= ran_var < 2 / 3) {
        f0 = ao[0] * exp (-bo[0] * (q / (4 * PI)) * (q / (4 * PI))) + ao[1] * exp (-bo[1] * (q / (4 * PI)) * (q / (4 * PI)))
             + ao[2] * exp (-bo[2] * (q / (4 * PI)) * (q / (4 * PI))) + ao[3] * exp (-bo[3] * (q / (4 * PI)) * (q / (4 * PI))) + co;

        p *= (3 * solid_angle) * (Oxygen_part * pressure * NA / (R_gas * temperature)) * l_full * RE * RE * f0 * f0 * polarization
             * (2 + 2 * sin (q * bond_O) / (q * bond_O)) * 1e-30 * 1e10;
      } else {
        f0 = aar[0] * exp (-bar[0] * (q / (4 * PI)) * (q / (4 * PI))) + aar[1] * exp (-bar[1] * (q / (4 * PI)) * (q / (4 * PI)))
             + aar[2] * exp (-bar[2] * (q / (4 * PI)) * (q / (4 * PI))) + aar[3] * exp (-bar[3] * (q / (4 * PI)) * (q / (4 * PI))) + car;

        /* Argon is monoatomic so the weight multiplier is different */
        p *= (3 * solid_angle) * (Argon_part * pressure * NA / (R_gas * temperature)) * l_full * RE * RE * f0 * f0 * polarization * 1e-30 * 1e10;
      }
      SCATTER;
    } else {
      PROP_DL (l_full);
      /* When no interaction between our ray and the air, we adjust the weight by: */
      p *= (1 - Ptot_sc_unpolarized) / (1 - frac);
    }
  }
%}

MCDISPLAY
%{

  if (shape == 0) { /* cylinder */
    circle ("xz", 0, yheight / 2.0, 0, radius);
    circle ("xz", 0, -yheight / 2.0, 0, radius);
    line (-radius, -yheight / 2.0, 0, -radius, +yheight / 2.0, 0);
    line (+radius, -yheight / 2.0, 0, +radius, +yheight / 2.0, 0);
    line (0, -yheight / 2.0, -radius, 0, +yheight / 2.0, -radius);
    line (0, -yheight / 2.0, +radius, 0, +yheight / 2.0, +radius);
  } else if (shape == 1) { /* box */
    box (0, 0, 0, xwidth, yheight, zdepth, 0, 0, 1, 0);
  } else if (shape == 2) { /* sphere */
    circle ("xy", 0, 0.0, 0, radius);
    circle ("xz", 0, 0.0, 0, radius);
    circle ("yz", 0, 0.0, 0, radius);
  }
%}
END
