/*******************************************************************************
*
* McStas, neutron ray-tracing package
*         Copyright 1997-2002, All rights reserved
*         Risoe National Laboratory, Roskilde, Denmark
*         Institut Laue Langevin, Grenoble, France
*
* Component: Refractor
*
* %I
* Written by: E. Farhi, B. Cubitt
* Date: Oct 2014
* Origin: ILL
*
* A refractor material/shape, which can be used to model e.g. lenses and prisms.
*
* %D
* Single bulk material shape that can be used as a prism or lens.
*
* NEUTRON INTERACTION PROCESSES:
* The bulk material can reflect, refract, scatter and absorb neutrons, depending
* on the material cross sections and incident angles.
*
* The refracting material is specified from its molar weight, density, coherent
* scattering cross section. The refractive index is computed as:
*   n   = sqrt(1-(lambda*lambda*rho*bc/PI)) is the refraction index
*
* The surface can be coated when specifying a critical wavevector Qc, with e.g.
* Qc=m*0.0219 for a super mirror coating. The mirror coating can be suppressed
* by setting Qc=0. The critical wavevector is then set to
*   Qc = 4*sqrt(PI*rho*bc);                                             with
*   rho= density*6.02214179*1e23*1e-24/weight;
*   bc = sqrt(fabs(sigma_coh)*100/4/PI)*1e-5;
*
* COMPONENT GEOMETRY:
* The component shape can be a sphere, box, cylinder, biconcave spherical lens
* or any other shape defined from an external OFF/PLY file.
*   sphere:      radius
*   cylinder:    radius, yheight
*   box:         xwidth, yheight, zdepth
*   OFF/PLY:     geometry="filename.off or ply", xwidth, yheight, zdepth (bounding box)
*   lens_sphere:   geometry="lens_sphere", radius, zdepth (thickness)
*
* The lens_sphere geometry is composed of two concave half spheres of same radius,
* separated with a minimal thickness zdepth along the Z axis.
*
* Optionally, you can specify the 'geometry' parameter as a OFF/PLY file name.
*   The complex geometry option handles any closed non-convex polyhedra.
*   It computes the intersection points of the neutron ray with the object
*   transparently, so that it can be used like a regular sample object.
*   It supports the PLY, OFF and NOFF file format but not COFF (colored faces).
*   Such files may be generated from XYZ data using:
*     qhull < coordinates.xyz Qx Qv Tv o > geomview.off
*   or
*     powercrust coordinates.xyz
*   and viewed with geomview or java -jar jroff.jar (see below).
*
* All geometries are centred. The bulk material fills the shape, but can be
* set 'outside' when density is given a negative value. In this case, the material
* outside the bulk is void (vacuum).
*
* Usually, you should stack more than one of these to get a significant effect
* on the neutron beam, so-called 'compound refractive lens'.
* The focal length for N lenses with focal 'f' is f/N, where f=R/(1-n)
*   and R = r/2   for a spherical lens with curvature radius 'r'
*
* COMMON MATERIALS:
* Should have high coherent, and low incoherent and absorption cross sections
*   Be:            density=1.85,  weight=9.0121, sigma_coh=7.63,  sigma_inc=0.0018,sigma_abs=0.0076
*   Pb:            density=11.115,weight=207.2,  sigma_coh=11.115,sigma_inc=0.003, sigma_abs=0.171
*     Pb206:                                     sigma_coh=10.68, sigma_inc=0    , sigma_abs=0.03
*     Pb208:                                     sigma_coh=11.34, sigma_inc=0    , sigma_abs=0.00048
*   Zr:            density=6.52,  weight=91.224, sigma_coh=6.44,  sigma_inc=0.02,  sigma_abs=0.185
*     Zr90:                                      sigma_coh=5.1,   sigma_inc=0    , sigma_abs=0.011
*     Zr94:                                      sigma_coh=8.4,   sigma_inc=0    , sigma_abs=0.05
*   Bi:            density=9.78,  weight=208.98, sigma_coh=9.148, sigma_inc=0.0084,sigma_abs=0.0338
*   Mg:            density=1.738, weight=24.3,   sigma_coh=3.631, sigma_inc=0.08,  sigma_abs=0.063
*   MgF2:          density=3.148, weight=62.3018,sigma_coh=11.74, sigma_inc=0.0816,sigma_abs=0.0822
*   diamond:       density=3.52,  weight=12.01,  sigma_coh=5.551, sigma_inc=0.001, sigma_abs=0.0035
*   Quartz/silica: density=2.53,  weight=60.08,  sigma_coh=10.625,sigma_inc=0.0056,sigma_abs=0.1714
*   Si:            density=2.329, weight=28.0855,sigma_coh=2.1633,sigma_inc=0.004, sigma_abs=0.171
*   Al:            density=2.7,   weight=26.98,  sigma_coh=1.495, sigma_inc=0.0082,sigma_abs=0.231
*   Ni:            density=8.908, weight=58.69,  sigma_coh=13.3,  sigma_inc=5.2,   sigma_abs=4.49
*   Mn: (bc < 0)   density=7.21,  weight=54.94,  sigma_coh=-1.75, sigma_inc=0.4,   sigma_abs=13.3
*   perfluoropolymer(PTFE/Teflon/CF2):
*                  density=2.2,   weight=50.007, sigma_coh=13.584,sigma_inc=0.0026,sigma_abs=0.0227
*   Organic molecules with C,O,H,F
*
*   Among the most commonly available and machinable materials are MgF2, SiO2, Si, and Al.
* %P
* INPUT PARAMETERS:
*
* xwidth: [m]           width of box
* yheight: [m]          height of box/cylinder
* zdepth: [m]           depth of box
* radius: [m]           radius of sphere/cylinder
* R0: [1]               Low-angle reflectivity
* Qc: [Angs-1]          critical scattering vector, e.g. Qc=0.0219 for Ni coating. Set Qc=0 to use the bulk critical grazing angles.
* sigma_coh: [barn]     coherent cross section of refracting material. Use negative value to indicate a negative coherent scattering length
* sigma_inc: [barn]     incoherent cross section
* sigma_abs: [barn]     thermal absorption cross section
* density: [g/cm3]      density of the refracting material. density < 0 means the material is outside/before the shape.
* weight: [g/mol]       molar mass of the refracting material
* geometry: [str]       OFF/PLY geometry file name, or NULL to use simple shape A spherical bi-concave lens can be obtained with geometry="lens_sphere" and given radius and zdepth
* p_interact: [1]       MC Probability for scattering the ray; otherwise transmit. Use 0 to compute true probability, or specify it as e.g. 0.05
* focus_scatter: [deg]  angle in which to scatter in bulk, with probability 'p_interact'
* RMS: [Angs]           root mean square wavyness of the surface
* verbose: [1]          flag to display detailed component behaviour
* p_scatter: [1]        flag to allow scattering in the refractor bulk
* p_reflect: [1]        flag to allow reflection (grazing angle) at the surface
* p_refract: [1]        flag to allow refraction at the refractor surface
*
* CALCULATED PARAMETERS:
* theta1: [deg]         incoming angle to the surface
* theta2: [deg]         outgoing angle to the surface
* SCATTERED: []         0=transmitted, 1=scattered in bulk, 2=refracted, 3=reflected
*
* %L
* M. L. Goldberger et al, Phys. Rev. 71, 294 - 310 (1947)
* %L
* Sears V.F. Neutron optics. An introduction to the theory of neutron optical phenomena and their applications. Oxford University Press, 1989.
* %L
* H. Park et al. Measured operational neutron energies of compound refractive lenses. Nuclear Instruments and Methods B, 251:507-511, 2006.
* %L
* J. Feinstein and R. H. Pantell. Characteristics of the thick, compound refractive lens. Applied Optics, 42 No. 4:719-723, 2001.
* %L
* <a href="http://www.geomview.org">Geomview and Object File Format (OFF)</a>
* %L
* Java version of Geomview (display only) <a href="http://www.holmes3d.net/graphics/roffview/">jroff.jar</a>
* %L
* <a hrefp="http://meshlab.sourceforge.net/">Meshlab</a> can view OFF and PLY files
* %L
* <a href="http://qhull.org">qhull</a> for points to OFF conversion
* %L
* <a href="http://www.cs.ucdavis.edu/~amenta/powercrust.html">powercrust</a> for points to OFF conversion
* %E
*******************************************************************************/

DEFINE COMPONENT Refractor

SETTING PARAMETERS (
xwidth=0, yheight=0, zdepth=0, radius=0,
string geometry="NULL",
R0=0.99, sigma_coh=11.74, density=3.148, weight=62.3018,
sigma_inc=0, sigma_abs=0, Qc=0,
p_interact=0.05, RMS=0, focus_scatter=10, verbose=0,
p_scatter=1, p_reflect=1, p_refract=1)


/* Neutron parameters: (x,y,z,vx,vy,vz,t,sx,sy,sz,p) */

SHARE
%{


%include "read_table-lib"
%include "interoff-lib"
%include "ref-lib"


  /* wrappers to intersection routines which also return the normal vector */
  int box_intersect_n(double *t0, double *t1,
    double x, double y, double z,
    double vx, double vy, double vz,
    double dx, double dy, double dz,
    double *nx, double *ny, double *nz) {

    double dt=0;
    int    intersect = box_intersect(t0, t1, x,y,z, vx,vy,vz, dx,dy,dz);
    /* determine normal vector depending on hit face */
    if (intersect) {
      dt = *t0 <= 0 || *t0 > *t1 ? *t1 : *t0;
      if (dt < 0) intersect = 0;
    }
    if (intersect && dx && dy && dz) {
      x += vx*dt; y += vy*dt; z += vz*dt;
      /* determine hit face: difference to plane is closest to 0 */
      *nx = trunc(2.002*x/dx);
      *ny = trunc(2.002*y/dy);
      *nz = trunc(2.002*z/dz);
    }
    return(intersect);
  } // box_intersect_n

  /* sphere: when radius < 0 we always use the further intersection point to determine the normal vector */
  int sphere_intersect_n(double *t0, double *t1,
    double x, double y, double z,
    double vx, double vy, double vz,
    double r,
    double *nx, double *ny, double *nz) {

    double dt=0;
    int    intersect = sphere_intersect(t0, t1, x,y,z, vx,vy,vz, r);
    if (intersect) {
      if (r >0) /* First intersection in positive time */
        dt = *t0 <= 0 || *t0 > *t1 ? *t1 : *t0;
      else      /* always second intersection */
        dt = *t1;
      if (dt < 0) intersect = 0;
    }
    if (intersect && r) {
      /* must propagate locally to determine normal vector. */
      x += vx*dt; y += vy*dt; z += vz*dt;
      *nx  = x;
      *ny  = y;
      *nz  = z;
    }
    return(intersect);
  } // sphere_intersect_n

  int cylinder_intersect_n(double *t0, double *t1,
    double x, double y, double z,
    double vx, double vy, double vz,
    double r, double h,
    double *nx, double *ny, double *nz) {

    double dt=0;
    int    intersect = cylinder_intersect(t0, t1,
      x,y,z, vx,vy,vz, r,h);
    if (intersect) {
      if (r >0) /* First intersection in positive time */
        dt = *t0 <= 0 || *t0 > *t1 ? *t1 : *t0;
      else      /* always second intersection */
        dt = *t1;
      if (dt < 0) intersect = 0;
    }
    if (intersect && r) {
      /* must propagate locally to determine normal vector */
      x += vx*dt; z += vz*dt;
      *nx  = x;
      *ny  = 0; /* cylinder is vertical */
      *nz  = z;
    }
    return(intersect);
  } // cylinder_intersect_n

// two sphere separated with a given thickness
int lens_sphere_intersect_n(double *t0, double *t1,
double x, double y, double z,
double vx, double vy, double vz,
double r, double dz,
double *nx, double *ny, double *nz) {
    /* two spherical lenses, concave, with given radius, thickness=zdepth at meniscus,
       plus cross section=xwidth*yheight */
    int    intersect1 = 0, intersect2 = 0;
    double nx1=0,nx2=0,ny1=0,ny2=0,nz1=0,nz2=0;
    double t2=0,t3=0;

    *t0=*t1=0;

    intersect1 = sphere_intersect_n(t0, t1, x,y,z+r+dz/2,
      vx,vy,vz, -r, &nx1,&ny1,&nz1); /* -r: use the further intersection with sphere */

    intersect2 = sphere_intersect_n(&t2, &t3, x,y,z-r-dz/2,
      vx,vy,vz, r, &nx2,&ny2,&nz2);

    /* usual case with 4 intersections: return t1 and t2 */
    if (intersect1 || intersect2) {
      if (intersect1) *t0=*t1;
      if (intersect2) *t1= t2;
    }

    if (!intersect1 && !intersect2)
      return(0);

    if (!intersect1 || !intersect2) {
      if (!intersect1) {
        /* intersection with the 2st sphere: return t2 and some other external
           intersection (container box entrance) t0 */
        intersect1 = box_intersect_n(t0,&t3, x,y,z, vx,vy,vz,
          2*r, 2*r, 2*r+dz, &nx1,&ny1,&nz1);
      } else {
        /* intersection with the 1st sphere: return t1 and some other external
           intersection (container box exit) t3 */
        intersect2 = box_intersect_n(&t2,t1, x,y,z, vx,vy,vz,
          2*r, 2*r, 2*r+dz, &nx2,&ny2,&nz2);
      }
    }

    /* the normal vector corresponds to the first forward intersection */
    if (intersect1 || intersect2) {
      if (*t0 < 0) {
        *nx = nx2; *ny = ny2; *nz = nz2;
      } else {
        *nx = nx1; *ny = ny1; *nz = nz1;
      }
    }

    /* no intersection: use intersection with container box */
    return(intersect1+intersect2);
  } // lens_sphere_intersect_n
  
  /* Surface_wavyness: function to rotate normal vector around axis for roughness
   *                 with specified tilt angle (rad)
   * RETURNS: rotated nx,ny,nz coordinates
   */
 #pragma acc routine
 void Surface_wavyness(double *nx, double *ny, double *nz, double tilt, _class_particle *_particle)
  {
    double nt_x, nt_y, nt_z;  /* transverse vector */
    double n1_x, n1_y, n1_z;  /* normal vector (tmp) */

    /* normal vector n_z = [ 0,1,0], n_t = n x n_z; */
    vec_prod(nt_x,nt_y,nt_z, *nx,*ny,*nz, 0,1,0);

    /* rotate n with angle wav_z around n_t -> n1 */
    tilt  /= (sqrt(8*log(2)))*randnorm();
    rotate(n1_x,n1_y,n1_z, *nx,*ny,*nz, tilt, nt_x,nt_y,nt_z);

    /* rotate n1 with angle phi around n -> nt */
    rotate(nt_x,nt_y,nt_z, n1_x,n1_y,n1_z, 2*PI*rand01(), *nx,*ny,*nz);

    *nx=nt_x;
    *ny=nt_y;
    *nz=nt_z;
  }
%}

DECLARE
%{
  double rho;
  double bc;
  off_struct offdata;
  double mean_n;
  double events;
  double theta1;
  double theta2;
%}

INITIALIZE
%{
  mean_n=0; events=0;
  theta1=0; theta2=0;
  /* set geometry from input geometry parameters */
  if (!geometry || !strlen(geometry) || !strcmp(geometry, "NULL") || !strcmp(geometry, "0")) {
    if (radius && yheight && !xwidth)                  strcpy(geometry, "cylinder");
    else if (xwidth && yheight && zdepth && !radius)   strcpy(geometry, "box");
    else if (radius && !yheight && !xwidth && !zdepth) strcpy(geometry, "sphere");
  } else if (radius && zdepth && !strcmp(geometry, "lens_sphere")) {
    /* NOP: OK */
  } else if (strcmp(geometry, "NULL") && strcmp(geometry, "0") && xwidth && yheight && zdepth) {
    #ifndef USE_OFF
    fprintf(stderr,"Error: You are attempting to use an OFF geometry without -DUSE_OFF. You will need to recompile with that define set!\n");
    exit(-1);
    #else
    /* off/ply case */
    if (!off_init(geometry, xwidth, yheight, zdepth, 0, &offdata))
      exit(printf("Refractor: %s: FATAL: invalid OFF/PLY geometry specification for file '%s'.\n",
      NAME_CURRENT_COMP, geometry));
    #endif
  }
  else exit(printf("Refractor: %s: FATAL: invalid geometry specification.\n"
                   "  Check geometry,xwidth,yheight,zdepth,radius\n",
      NAME_CURRENT_COMP));

  /* compute refraction parameters, if needed */
  if (density==0 || weight<=0)
    exit(printf("Refractor: %s: FATAL: invalid material density or molar weight: density=%g weight=%g\n",
      NAME_CURRENT_COMP, density, weight));
  rho= fabs(density)*6.02214179*1e23*1e-24/weight; /* per at/Angs^3 */
  if (sigma_coh==0)
    exit(printf("Refractor: %s: FATAL: invalid material coherent cross section: sigma_coh=%g\n",
      NAME_CURRENT_COMP, sigma_coh));
  bc=sqrt(fabs(sigma_coh)*100/4/PI)*1e-5;      /* bound coherent scattering length */
  if (sigma_coh<0) bc *= -1;

  /* use bulk to compute reflectivity critical angle/wavevector */
  if (Qc <=0)
    Qc = 4*sqrt(PI*rho*fabs(bc));
  MPI_MASTER(
  printf("Refractor: %s: rho.bc=%g [10-6 Angs-2] Qc=%g [Angs-1]. geometry=%s\n",
    NAME_CURRENT_COMP, rho*bc*1e6, Qc, geometry);
  );
%}

TRACE
%{
  int    intersect = 0;
  int    iterations= 0;
  char   event[256];

  theta1=theta2=0;

  #ifdef OPENACC
  #ifdef USE_OFF
  off_struct thread_offdata = offdata;
  #endif
  #else
  #define thread_offdata offdata
  #endif
  
  do {
    double nx=0, ny=0, nz=0;
    double t0=0, t1=0, dt=0;

    intersect = 0;
    iterations++;
    #ifndef OPENACC
    strcpy(event, "                                               ");
    #endif
    /* determine intersection times and normal vector with geometry */
    if (!strcmp(geometry, "sphere")) {
      intersect = sphere_intersect_n(&t0, &t1, x,y,z, vx,vy,vz,
        radius, &nx,&ny,&nz);
    } else if (!strcmp(geometry, "lens_sphere")) {
      intersect = lens_sphere_intersect_n(&t0, &t1, x,y,z, vx,vy,vz,
        radius, zdepth, &nx,&ny,&nz);
    } else if (!strcmp(geometry, "cylinder")) {
      intersect = cylinder_intersect_n(&t0, &t1, x,y,z, vx,vy,vz,
        radius, yheight, &nx,&ny,&nz);
    } else if (!strcmp(geometry, "box")) {
      intersect = box_intersect_n(&t0, &t1, x,y,z, vx,vy,vz,
        xwidth, yheight, zdepth, &nx,&ny,&nz);
    }
    #ifdef USE_OFF
    else if (geometry && strcmp(geometry, "NULL") && strcmp(geometry, "0")) {
      Coords n0, n1;
      intersect = off_intersect(&t0, &t1, &n0, &n1, x, y, z, vx, vy, vz, 0, 0, 0, thread_offdata );
      if (intersect) {
        coords_get(t0 <= 0 || t0 > t1 ? n1 : n0, &nx, &ny, &nz);
      }
    }
    #endif

    if (intersect) {
      if (t1 < t0) { double tmp=t0; t0=t1; t1=tmp; }
      dt = t0 <= 1e-10 ? t1 : t0; /* full propagation time inside geometry */
      if (dt < 0) dt=0;
    }
    if (dt<=0) break;
    #ifndef OPENACC
    strcpy(event, "none");
    #endif
    if (verbose)
    printf("%s:%i: [neutron=%li iteration=%i] intersect %s=%i t0=%g t1=%g dt=%g event=(intersect) %s\n",
      NAME_CURRENT_COMP, __LINE__, mcget_run_num(), iterations, geometry, intersect, t0,t1, dt, event);

    /* scattering/absorption in bulk, refraction/reflection at interface */
    if (intersect) {
      char   scatter_me=0, refract_me=0;
      double my_t=0, d_path=0;
      double v=0,lambda=0;
      char   inbulk = t0 < 0 && t1 > 0;

      /* when density is given negative, we assume bulk refractive material is
         'outside' or 'before' the shape */
      if (density < 0) inbulk = !inbulk;

      v      = sqrt(vx*vx+vy*vy+vz*vz);
      if (!v) ABSORB;
      lambda = 3956.0032/v;
      d_path = v*dt;         /* full length in material */

      /* compute probability to scatter/absorb inside bulk */
      if (inbulk) { /* scatter inside bulk */
        double ws     = 0;
        double p_trans= 0, p_scatt=0, mc_trans=0, mc_scatt=0;
        int    flag   = 0;
        double my_a_v = (rho * 100 * sigma_abs);
        double my_a   = my_a_v*2200/v;
        double my_s   = rho * 100 *(sigma_inc+sigma_coh);

        my_t = my_a + my_s;
        ws   = my_s/my_t;  /* (inc+coh)/(inc+coh+abs) */

        /* Proba of transmission along length d_path */
        p_trans = exp(-my_t*d_path);
        p_scatt = 1 - p_trans;

        flag = 0; /* flag used for propagation to exit point before ending */
        /* are we next to the exit ? probably no scattering (avoid rounding errors) */
        if (my_s*d_path <= 4e-7) {
          flag = 1;           /* No interaction before the exit */
        }

        /* force a given fraction of the beam to scatter */
        if (p_interact>0 && p_interact<=1) {
          /* we force a portion of the beam to interact */
          /* This is used to improve statistics on single scattering (and multiple) */
          mc_trans = 1-p_interact;
        } else {
          mc_trans = p_trans; /* 1 - p_scatt */
        }

        mc_scatt = 1 - mc_trans; /* portion of beam to scatter (or force to) */
        if (mc_scatt <= 0 || mc_scatt>1) flag=1;

        /* MC choice: Interaction or transmission ? */
        scatter_me = !flag && ws && p_scatt && mc_scatt > 0 && (mc_scatt >= 1 || rand01() < mc_scatt);

        /* account for absorption and retain scattered fraction */
        /* we have chosen portion mc_scatt of beam instead of p_scatt, so we compensate */
        if (scatter_me)
          p *= ws * fabs(p_scatt/mc_scatt);
        else if (p_trans && mc_trans)
          p *= fabs(p_trans/mc_trans);  /* attenuate beam by portion which is scattered (and left along) */

        if (verbose)
        printf("%s:%i: [neutron=%li iteration=%i] intersect %s=%i t0=%g t1=%g dt=%g event=(inbulk) %s scatter_me=%i\n",
          NAME_CURRENT_COMP, __LINE__, mcget_run_num(), iterations, geometry, intersect, t0,t1, dt, event, scatter_me);
      } /* if inbulk */

      /* handle absorption and scattering in material */
      /* req: my_t, d_path, focus_ah, focus_aw */
      if (p_scatter && scatter_me) {
        double dl=0, solid_angle=0;
        double vx2=0, vy2=0, vz2=0;

        if (my_t*d_path < 1e-6){
          /* For very weak scattering, use simple uniform sampling of scattering
             point to avoid rounding errors. */
          dl = rand0max(d_path); /* length */
        } else {
          double a = rand0max((1 - exp(-my_t*d_path)));
          dl = -log(1 - a) / my_t; /* length */
        }

        PROP_DT(dl/v); dt = 0;  // we propagate in the bulk
        SCATTER; /* 1 */

        /* scatter randomly in cone to take into account material sigma */
        randvec_target_circle(&vx2, &vy2, &vz2,
          &solid_angle, vx,vy,vz, focus_scatter*DEG2RAD);
        vx=vx2; vy=vy2; vz=vz2;

        if (solid_angle) {
          p *= solid_angle/4/PI;
          NORM(vx, vy, vz);
          vx*=v; vy*=v; vz*=v; /* scattering is elastic */
        }
        /* force to recompute intersection with new neutron direction/position */
        refract_me = 0;
	#ifndef OPENACC
        strcpy(event, "scatter");
	#endif
        if (verbose)
        printf("%s:%i: [neutron=%li iteration=%i] intersect %s=%i t0=%g t1=%g dt=%g event=(scatter) %s\n",
          NAME_CURRENT_COMP, __LINE__, mcget_run_num(), iterations, geometry, intersect, t0,t1, dl/v, event);

      } else {
        refract_me = 1;
      } /* if scatter */

      if ((p_refract||p_reflect) && refract_me) { // refract or reflect on the surface
        double n=0; /* refractive index */
        double n1=0, n2=0;

        double q=0, R=0;
        double par[]  = {R0, Qc, 6.0, 1.0, 1.0/300.0};


        /* propagate to surface */
        PROP_DT(dt); dt=0;
        SCATTER; SCATTER; /* 2 */

        /* compute refraction index */
        n   = sqrt(1-(lambda*lambda*rho*bc/PI));
        mean_n += n;
        events++;

        /* compute incoming angle */
        if (inbulk) { n1=n; n2=1; } /* from bulk to void */
        else        { n1=1; n2=n; } /* from void to bulk */

        /* compute reflectivity */
        q = fabs(2*vz*V2Q);

        /* Reflectivity (see component Guide). */
        StdReflecFunc(q, par, &R);

        /* tilt normal vector for roughness, in cone theta_RMS */
        NORM(nx,ny,nz);
        /* cone angle from RMS roughness = atan(2*RMS/lambda) */
        if (RMS>0) Surface_wavyness(&nx, &ny, &nz, atan(2*RMS/lambda), _particle);

        /* Snell-Descartes formula for refraction n1 sin(theta1) = n2 sin(theta2) */
        /*   https://en.wikipedia.org/wiki/Snell's_law                            */
        Coords N = coords_set(nx,ny,nz);  // normal vector to surface
        Coords V = coords_set(vx,vy,vz);  // incoming velocity
        Coords I = coords_scale(V, 1/v);  // normalised ray = v/|v|

        // theta1: incident angle to the surface normal
        double cos_theta1 = -coords_sp(N,I); // cos(theta1) = -N.I

        if (fabs(cos_theta1) > 1) ABSORB; // should never occur...
        theta1 = acos(cos_theta1)*RAD2DEG;

        // reflected ray: probability R
        // reflected beam:     I + 2cos(theta1).N
        Coords I_reflect = coords_add(I, coords_scale(N, 2*cos_theta1));
        // reflected velocity: I_reflect.v
        Coords V_reflect = coords_scale(I_reflect, v);

        // compute refracted angle theta2...
        double sqr_cos_theta2 = 1-(n1/n2)*(n1/n2)*(1-cos_theta1*cos_theta1);

        // now choose which one to use, and compute outgoing velocity
        if (0 < sqr_cos_theta2 && sqr_cos_theta2 < 1) {
          // refraction is possible

          // theta2: refracted angle to the surface normal
          double cos_theta2= sqrt(sqr_cos_theta2);

          // select reflection (or refraction) from Monte-Carlo choice with probability R
          // in this case we expect R to be small (q > Qc)
          if (p_reflect && 0 < R && R < 1 && rand01() < R) {
            // choose reflection from MC
            theta2 = theta1;
            coords_get(V_reflect, &vx, &vy, &vz);
	    #ifndef OPENACC
            strcpy(event, "reflect");
	    #endif
          } else if (p_refract) {
            // compute refracted ray
            theta2 = acos(cos_theta2)*RAD2DEG;

            Coords I_refract = coords_add(coords_scale(I, n1/n2),
                                          coords_scale(N, n1/n2*cos_theta1 + (cos_theta1  < 0 ? cos_theta2 : -cos_theta2) ));
            Coords V_refract = coords_scale(I_refract, v);

            coords_get(V_refract, &vx, &vy, &vz);
	    #ifndef OPENACC
            strcpy(event, "refract");
	    #endif
            SCATTER; /* 3 */
          }
        } else if (p_reflect) {
          // only reflection: below total reflection
          theta2 = theta1;
          if (0 < R && R < 1) p *= R; // should be R0
          coords_get(V_reflect, &vx, &vy, &vz);
	  #ifndef OPENACC
          strcpy(event, "reflect");
	  #endif
        }

        /* propagate by a small time so that we leave the surface */
        PROP_DT(1e-9);

        if (verbose)
        printf("%s:%i: [neutron=%li iteration=%i] intersect %s=%i t0=%g t1=%g dt=%g event=(reflect/refract) %s theta1=%g theta2=%g 1-n=%g |xy|=%g nz=%g\n",
          NAME_CURRENT_COMP, __LINE__, mcget_run_num(), iterations, geometry, intersect, t0,t1, dt, event,
          theta1, theta2, 1-n, sqrt(x*x+y*y), nz);
      } /* if reflect/refract */

    } /* if intersect */
    else break;

    if (verbose)
    printf("%s:%i: [neutron=%li iteration=%i] intersect %s=%i t0=%g t1=%g dt=%g event=%s\n",
      NAME_CURRENT_COMP, __LINE__, mcget_run_num(), iterations, geometry, intersect, t0,t1, dt, event);
  } while (intersect && iterations<100);

%}

FINALLY %{
/*
 * The focal length for N lenses with focal 'f' is f/N, where f=R/(1-n)
 *   and R = r/2   for a spherical lens with curvature radius 'r'
 */

  if (radius && !strcmp(geometry, "lens_sphere") ) {

    double focus, R;
    mean_n /= events;
    R       = radius/2;
    focus   = R/(1-mean_n);
    MPI_MASTER(
    printf("Refractor: %s: %s focal length f=%g [m]. Focal length for N lenses is f/N.\n"
        "mean n=%g, events=%g\n", NAME_CURRENT_COMP, geometry, focus, mean_n , events);
    );
  }

%}

MCDISPLAY
%{
  /* show geometry */
  if (!strcmp(geometry, "sphere")) {
    circle("xy", 0, 0, 0, radius);
    circle("xz", 0, 0, 0, radius);
    circle("yz", 0, 0, 0, radius);
  } else if (!strcmp(geometry, "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 (!strcmp(geometry, "box")) {
    box(0,0,0, xwidth, yheight, zdepth,0, 0, 1, 0);
  } else if (!strcmp(geometry, "lens_sphere")) {
    // sphere: x^2+y^2+z^2=radius. In 2D: radius^2=z^2+r^2
    int index;
    for (index=0; index<=3; index++) {
      double z=radius*(index == 3 ? 0.95 : index/3.0);
      double r=sqrt(radius*radius-z*z);
      circle("xy", 0, 0, z-(radius+zdepth/2), r);
      circle("xy", 0, 0,-z+(radius+zdepth/2), r);
    }
    box(0,0,0, 2*radius, 2*radius, 2*radius+zdepth,0, 0, 1, 0);
  } else if (!strcmp(geometry, "lens_parabola")) {
    // parabola: z  = (x^2+y^2)/4/radius. In 2D: z = r^2/4/radius
    int index;
    for (index=0; index<=3; index++) {
      double z=radius*(index == 3 ? 0.95 : index/3.0);
      double r=sqrt(z*4*radius);
      circle("xy", 0, 0,-z-(zdepth/2), r);
      circle("xy", 0, 0, z+(zdepth/2), r);
    }
    box(0,0,0, 4*radius, 4*radius, 2*radius+zdepth,0, 0, 1, 0);
  } else if (geometry && strcmp(geometry, "NULL") && strcmp(geometry, "0")) {
    off_display(offdata);
  }
%}
END
