/*******************************************************************************
*
* McXtrace, x-ray tracing package
*         Copyright, All rights reserved
*         DTU Physics, Kgs. Lyngby, Denmark
*         Synchrotron SOLEIL, Saint-Aubin, France
*
* Component: Bragg_crystal_bent
* 
* %Identification
* Written by: Marcus H Mendenhall, NIST <marcus.mendenhall@nist.gov>
* Based on: Perfect_crystal.comp written by Anette Vickery, Andrea Prodi, Erik Knudsen
* Date: December 1, 2016
* Version: 2.0-a
* Release: McXtrace 1.4
* Origin: Marcus H. Mendenhall, NIST, Gaithersburg, MD, USA
*
* Bent, perfect, reflecting crystal with common cubic structures (diamond, fcc, or bcc, and others if symmetry form factor multipliers provided explicitly)
*
* %Description
* Bragg_crystal_bent.comp supercedes Perfect_Crystal_bent.comp with major edits and corrections.
*
* Reads atomic formfactors from a data input file.
*
* The crystal code reflects ray in an ideal geometry, does not include surface imperfections or mosaicity
* The crystal planes from which the reflection is made must lie in the X-Z plane on the unbent crystal rotated
* by an angle alpha about the x axis with respect to the crystal surface.
*
* The external geometry of the crystal follows that of Elliptic_mirror.comp.
* I.e. the crystal is positioned such that the a-axis of the ellipsoid is on the
* z-axis, the b-axis is along the y-axis and the c is along the x-axis.
* The reference point of the crystal is the ellipsoid centre, offset by one half-axis along the y-axis.
* (See the component manual for Elliptic_mirror for a drawing).
*
* N.B. The component does not work for negative curvature, nor for rays hitting the back of the monochromator.
*
* Notation follows Tadashi Matsushita and Hiro-O Hashizume, X-RAY MONOCHROMATORS. Handbook on Synchrotron Radiation,North-Holland Publishing Company, 1:263–274, 1983.
*
* Non-copyright notice:
* Contributed by the National Institute of Standards and Technology; not subject to copyright in the United States. 
* This is not an official contribution, in that the results are in no way certified by NIST.
*
* NOTE: elliptical coordinate code and documentation taken from Mirror_elliptic.comp distributed in McXtrace v1.2
* However, the coordinates are rotated to be consistent with Bragg_crystal_flat.comp and Perfect_Crystal.comp.
* Idealized elliptic mirror with surface ellipse and lattice ellipses independent, to allow construction of
* Johansson optics, for example.
*
* Example: Bragg_crystal_bent(
*       length=0.05, width=0.02, V=160.1826, h=1, k=1, l=1, alpha=0,y_b=1, lattice_y_b=1, z_c=1, lattice_z_c=1)
* 
* %Parameters
* INPUT PARAMETERS
* x_a:            [m]   1st short half axis (along x). Commonly set to zero, which really implies infinite value, so crystal is an elliptic cylinder.
* y_b:            [m]   2nd short half axis (along y), which is also the presumed near-normal direction, reflection near the y-z plane.
* z_c:            [m]   Long  half axis (along z). Commonly a=0. b=c, which creates a circular cylindrical surface.
* width:          [m]   x width of the crystal.
* length:         [m]   z depth (length) of the crystal.
* lattice_x_a:    [m]   Curvature matrix for underlying lattice, for bent/ground/rebent crystals THERE HAS BEEN NO TESTING for the case in which lattice_x_a != x_a.
* lattice_y_b:    [m]   Curvature matrix for underlying lattice, for bent/ground/rebent crystals
* lattice_z_c:    [m]   Curvature matrix for underlying lattice, for bent/ground/rebent crystals
* material:       [str] Si, Ge (maybe also GaAs?)
* V:              [AA^3] Unit cell volume
* h:              [1]   Miller index of reflection
* k:              [1]   Miller index of reflection
* l:              [1]   Miller index of reflection
* alpha:          [rad] Asymmetry angle (alpha=0 for symmetric reflection, ie the Bragg planes are parallel to the crystal surface); alpha is defined so that positive alpha reduces the Bragg angle to the plane i.e. alpha=Thetain grazes the planes; if alpha!=0,  one should restrict to rays which have small kx values, since otherwise the alpha rotation is not around the diffraction axis.
* R0:             [0-1] Reflectivity. Overrides the computed Darwin reflectivity. Probably only useful for debugging.
* debye_waller_B: [AA^2] Debye-Waller temperature factor, M=B*(sin(theta)/lambda)^2*(2/3), default=silicon at room temp.
* crystal_type:   [1] 1 => Bragg_crystal_explicit: provide explicit real and imaginary form factor multipliers structure_factor_scale_r, structure_factor_scale_i; 2 => Bragg_crystal_diamond: diamond; 3 => Bragg_crystal_fcc: fcc; 4 => Bragg_crystal_fcc: bcc
* form_factors:   [str] File for X-ray form factors
* structure_factor_scale_r: [1] real      form factor multiplier
* structure_factor_scale_i: [1] imaginary form factor multiplier
*
* %Link
* material datafile obtained from http://physics.nist.gov/cgi-bin/ffast/ffast.pl
*
* %End
*******************************************************************************/

DEFINE COMPONENT Bragg_crystal_bent

SETTING PARAMETERS (x_a=0, y_b=1.0, z_c=1.0, lattice_x_a=0, lattice_y_b=1.0, lattice_z_c=1.0,
        length=0.05, width=0.02, V=160.1826, string form_factors="FormFactors.txt", string material="Si.txt", alpha=0.0,
        R0=0, debye_waller_B=0.4632, int crystal_type=1, int h=1, int k=1, int l=1 ,
        structure_factor_scale_r=0.0, structure_factor_scale_i=0.0)
 
/* X-ray parameters: (x,y,z,kx,ky,kz,phi,t,Ex,Ey,Ez,p) */

SHARE
%{
    %include "perfect_crystals-lib"
%}

DECLARE
%{
  int Z;
  double rho;
  double At;
  double f_rel;
  double f_nt;
  t_Table m_t;
  t_Table f0_t;
  double a2inv;
  double b2inv;
  double c2inv; /* 1/r^2 for physical ellipse */
  double l_a2inv;
  double l_b2inv;
  double l_c2inv; /* 1/r^2 for lattice ellipse */
%}

INITIALIZE
%{
    int status;
    if (material){
        if ((status=Table_Read(&(m_t),material,0))==-1){
            fprintf(stderr,"Error(%s): Could not parse file \"%s\"\n",NAME_CURRENT_COMP,material);
            exit(-1);
        }
        char **header_parsed;
        header_parsed=Table_ParseHeader(m_t.header,"Z","A[r]","rho","Z/A","sigma[a]",NULL);
        if(header_parsed[2]){rho=strtod(header_parsed[2],NULL);}
        if(header_parsed[0]){Z=strtod(header_parsed[0],NULL);}
        if(header_parsed[1]){At=strtod(header_parsed[1],NULL);}
    }else{
        fprintf(stderr,"Error(%s): No material file specified\n",NAME_CURRENT_COMP);
    }
    if(form_factors){
        if ((status=Table_Read(&(f0_t),form_factors,0))==-1){
            fprintf(stderr,"Error(%s): Could not parse file \"%s\"\n",NAME_CURRENT_COMP,form_factors);
            exit(-1);
        }
    }
    a2inv=(x_a)?1/(x_a*x_a):0; /* 0 really means infinity for x direction */
    b2inv=1/(y_b*y_b);
    c2inv=1/(z_c*z_c);

    l_a2inv=(lattice_x_a)?1/(lattice_x_a*lattice_x_a):0; /* 0 really means infinity for x direction */
    l_b2inv=1/(lattice_y_b*lattice_y_b);
    l_c2inv=1/(lattice_z_c*lattice_z_c);
%}

TRACE
%{
    double E;				// (keV) x-ray energy
    double K; 				// length of k-vector
    double kxu,kyu,kzu;			// unit vector in the direction of k-vector.
    double x_int,y_int,z_int;		// intersection with the y=0 plane
    double f00, f0h, fp, fpp;		// atomic form factors for Q=0 is (f00 + fp + i*fpp) and for Q= ha*+kb*+lc* it is (f0h + fp + i*fpp).
    double Thetain;			// (rad) angle between the crystal surface and the incident ray
    double Theta0;			// (rad) angle between the Bragg planes and the incident ray
    double Thetah;			// (rad) angle between the Bragg planes and the reflected ray
    double DeltaTheta0;			// (rad) the center of the reflectivity curve is at asin(n*lambda/(2*d)) + DeltaTheta0
    double Rpi, Rsig, R;          // Reflectivity value calculated by DarwinReflectivity() function for each incoming photon
    
    /* get the photon's kvector and energy */
    K=sqrt(kx*kx+ky*ky+kz*kz);
    E = K2E*K; /* use built-in constants for consistency */
    /* make unit vector in the direction of k :*/
    kxu = kx; kyu = ky; kzu = kz;
    NORM(kxu,kyu,kzu);
    /* printf("incoming kx,ky,kz, Ex, Ey, Ez, k.E: %f %f %f %g %g %g %g\n", kx,ky,kz,Ex,Ey,Ez, kxu*Ex+kyu*Ey+kzu*Ez); */

    /* this intersection code copied from Mirror_elliptic.comp, with coordinates modified */
    double A,B,C, xt, yt, zt;
    double t0,t1;
    /*an offset to the mirror parameters perhaps*/

    xt=x;
    zt=z;
    /*the reference point is on the ellipsoid surface such that the ellipsoid mass lies on the positive y-side of the zy-plane*/
    yt=y-y_b;

    C=xt*xt*a2inv + yt*yt*b2inv + zt*zt*c2inv -1;
    B=2*(kxu*xt*a2inv + kyu*yt*b2inv + kzu*zt*c2inv);
    A=kxu*kxu*a2inv + kyu*kyu*b2inv + kzu*kzu*c2inv;

    if(solve_2nd_order(&t0,&t1,A,B,C)){
        double xx0, xx1, yy0, yy1, zz0, zz1; /* we will have to tentatively propagate twice to see which surface we hit */
        xx0=x+kxu*t0; yy0=y+kyu*t0; zz0=z+kzu*t0;
        xx1=x+kxu*t1; yy1=y+kyu*t1; zz1=z+kzu*t1;

        int hit0=(fabs(xx0)<width/2.0) && (fabs(zz0)<length/2.0);
        int hit1=(fabs(xx1)<width/2.0) && (fabs(zz1)<length/2.0);
        int doit=hit0 || hit1;
        if(hit0 && !hit1) PROP_DL(t0); /* only one intersection actually on mirror */
        else if (hit1 && !hit0) PROP_DL(t1); /* other intersection */
        else if (hit0 && hit1) { /* both, take first strike (which may be back of mirror) */
            PROP_DL(t0<t1?t0:t1);	
        } else {
            RESTORE_XRAY(INDEX_CURRENT_COMP, x, y, z, kx, ky, kz, phi, t, Ex, Ey, Ez, p);
        }
#ifdef MCDEBUG
        printf("%s: solving for crystal hit, l0=%f, l1=%f, x=(%f %f %f) doit=%d, hit0=%d, hit1=%d\n", NAME_CURRENT_COMP,
            t0, t1, x, y, z, doit, hit0, hit1);
#endif
        if ( doit ){
            SCATTER;
            xt=x; yt=y-y_b; zt=z; /* update shifted coordinates to intersection point */
            double grad_x, grad_y, grad_z;
            /* grad is the inner normal to the surface at this point */
            grad_x=-xt*a2inv; grad_y=-yt*b2inv; grad_z=-zt*c2inv; NORM(grad_x, grad_y, grad_z);
            double sintheta=fabs(kxu*grad_x+kyu*grad_y+kzu*grad_z); /* both vectors are already normalized so this gives angle from surface */

            /* compute angle of crystal planes based on lattice ellipse, not rotated by alpha */
            double l_grad_x, l_grad_y, l_grad_z;
            /* l_grad is the inner normal to the lattice ellipse at this point, assuming that at the center of the
              mirror, the two ellipses coincide */
            l_grad_x=-xt*l_a2inv; l_grad_y=-(y-lattice_y_b)*l_b2inv; l_grad_z=-zt*l_c2inv; NORM(l_grad_x, l_grad_y, l_grad_z);

            /* compute the sin of the angle in the y-z plane between the two ellipses, which is an adjustment to alpha */
            double sin_alpha_adjust=(grad_y*l_grad_z-grad_z*l_grad_y)/
                sqrt((grad_y*grad_y+grad_z*grad_z)*(l_grad_y*l_grad_y+l_grad_z*l_grad_z));
            double delta_alpha=asin(sin_alpha_adjust);

#ifdef MCDEBUG
            printf("%s: gradient, sintheta, delta_alpha=(%f %f %f) %f %f\n", NAME_CURRENT_COMP,
                grad_x, grad_y, grad_z, sintheta, delta_alpha*180/PI);
#endif
            /*Check which quadrant the k vector is in to determine sense of alpha. This to allow for hitting the crystal from behind.*/
            int quadrant;
            Thetain=asin(sintheta); /* this does not include any effect of strong curvature interacting with alpha! */
            double d=cbrt(V)/(sqrt(h*h+k*k+l*l));/*this is valid only for cubic structures*/
            f00 = Z;
            f0h = Table_Value(f0_t,1/(2*d),Z);
            fp  = Table_Value(m_t,E,1)-Z;
            fpp = Table_Value(m_t,E,2);

            /* the mirror bending assumes that any off-axis cut angle alpha follows the bend in the y-z plane,
                so the crystal is being bent along the same axis as the off-axis cut.
                alpha is thus unaffected by the curved geometry.
            */

            double alpha1=alpha+delta_alpha; /* the angle between the lattice planes and the surface, including curvature */
            /* check for 3rd & 1st quadrant hits, backward hit from above or forward hit from below and reverse sense of alpha */
            if( (ky<0 && kz<0) || (ky>0 && kz>0) ) alpha1=-alpha1;
            Mx_DarwinReflectivity(&Rpi , &Thetah, &Theta0, &DeltaTheta0, f00, f0h, fp, fpp, V, alpha1, h, k, l,
                debye_waller_B, E, Thetain,1, crystal_type, structure_factor_scale_r, structure_factor_scale_i
            );
            Mx_DarwinReflectivity(&Rsig, &Thetah, &Theta0, &DeltaTheta0, f00, f0h, fp, fpp, V, alpha1, h, k, l,
                debye_waller_B, E, Thetain,2, crystal_type, structure_factor_scale_r, structure_factor_scale_i
            );

            double pi_x, pi_y, pi_z, sig_x, sig_y, sig_z;
            double kx0=kx, ky0=ky, kz0=kz, Ex0=Ex, Ey0=Ey, Ez0=Ez;

            /* sig_x,y,z is k(in) x surface_normal i.e. the direction of sigma polarization */
            vec_prod_func(&sig_x , &sig_y , &sig_z , kx0, ky0, kz0, grad_x, grad_y, grad_z);
            NORM(sig_x, sig_y, sig_z);
            /* pi is a vector perpendicular to k_in and sig i.e. the direction of pi polarization incoming */
            vec_prod_func(&pi_x, &pi_y, &pi_z, kx0, ky0, kz0, sig_x, sig_y, sig_z);
            NORM(pi_x , pi_y , pi_z );

            /* note that the code in DarwinReflectivity2 assumes +ive alpha decreases the angle from the planes */
            /* DarwinReflectivity returns Theta0, the incoming angle form the Bragg planes, and Thetah, the outgoing angle */
            /* we rotate the beam about the sigma axis by theta0+thetah */
#ifdef MCDEBUG
            printf("%s: Thetain, Theta0, Thetah, alpha, delta_alpha= %f %f %f %f %f\n", NAME_CURRENT_COMP,
                Thetain*180/PI, Theta0*180/PI, Thetah*180/PI, alpha*180/PI, delta_alpha*180/PI);
#endif

            double sth=sin(Theta0+Thetah), cth=cos(Theta0+Thetah);
            if(sig_x*pi_y*pi_z > 0) { /* backwards hit, rotate the other way */
                sth=-sth;
            }
            double sx2=sig_x*sig_x, sy2=sig_y*sig_y, sz2=sig_z*sig_z, r2=sig_x*sig_x+sig_y*sig_y;

            /* initialize a rotation matrix by the appropriate angle around the sigma axis, this from Mathematica RotationMatrix[] */
            double m[3][3]={
                sx2 + (cth*(sy2 + sx2*sz2))/r2,sig_x*sig_y - sig_z*sth + (cth*sig_x*sig_y*(-1 + sz2))/r2,sig_x*sig_z - cth*sig_x*sig_z + sig_y*sth,
                sig_x*sig_y + sig_z*sth + (cth*sig_x*sig_y*(-1 + sz2))/r2,sy2 + (cth*(sx2 + sy2*sz2))/r2,sig_y*sig_z - cth*sig_y*sig_z - sig_x*sth,
                sig_x*sig_z - cth*sig_x*sig_z - sig_y*sth,sig_y*sig_z - cth*sig_y*sig_z + sig_x*sth,cth*r2 + sz2
            };

            /* execute the rotation about the sigma vector */
            kx=m[0][0]*kx0+m[0][1]*ky0+m[0][2]*kz0;
            ky=m[1][0]*kx0+m[1][1]*ky0+m[1][2]*kz0;
            kz=m[2][0]*kx0+m[2][1]*ky0+m[2][2]*kz0;

#ifdef MCDEBUG
            printf("%s matrix=\n%12.3f %12.3f %12.3f\n%12.3f %12.3f %12.3f\n%12.3f %12.3f %12.3f\n", NAME_CURRENT_COMP,
                m[0][0],m[0][1],m[0][2],m[1][0],m[1][1],m[1][2],m[2][0],m[2][1],m[2][2]
            );
#endif
            /* resolve incoming polarization into sig and pi bits, and scale by sqrt(reflectivity) which is amplitude scale */
            double Esig=(Ex*sig_x+Ey*sig_y+Ez*sig_z), Epi=(Ex*pi_x+Ey*pi_y+Ez*pi_z);
            if(Esig==0 && Epi==0) { /* someone didn't set the polarization direction; set it now to a random value and it will propagate */
                double psi=rand01()*PI/2;
                Esig=cos(psi); Epi=sin(psi);
            }
            Esig=Esig*sqrt(Rsig);
            Epi=Epi*sqrt(Rpi);
            R=Esig*Esig+Epi*Epi; /* projected reflectivity, squared back to intensity */

            /* pi is now a vector perpendicular to k_out and sig i.e. the direction of pi polarization outgoing */
            vec_prod_func(&pi_x, &pi_y, &pi_z, kx, ky, kz, sig_x, sig_y, sig_z);
            NORM(pi_x , pi_y , pi_z );

            /* a linear combination of these is still perpendicular to k, but has the correct polarization weighting */
            Ex=Epi*pi_x+Esig*sig_x;
            Ey=Epi*pi_y+Esig*sig_y;
            Ez=Epi*pi_z+Esig*sig_z;
            NORM(Ex, Ey, Ez);
#ifdef MCDEBUG
            printf("%s: k0, k1, e0, e1: (%g, %g, %g) (%g, %g, %g) (%g, %g, %g) (%g, %g, %g)\n", NAME_CURRENT_COMP,
            kx0, ky0, kz0, kx, ky, kz, Ex0, Ey0, Ez0, Ex, Ey, Ez);
#endif
            /* apply Darwin reflectivity if not is supplied from outside*/
            if (!R0){
                p*=R;
            }else{
                p*=R0;
            }
            /*catch dead rays*/
            if (p==0) ABSORB;
        } else {
            RESTORE_XRAY(INDEX_CURRENT_COMP, x, y, z, kx, ky, kz, phi, t, Ex, Ey, Ez, p);
        }
    }
%}

MCDISPLAY
%{
  int i,j,N=12;
  double t,xx,yy,zz,xx0,yy0,zz0;
  /* printf("plot params (%f %f %f) (%f %f %f) %f %f\n", x_a, y_b, z_c, a2, b2, c2, width, length); */
  xx=width/2.0;
  for (j=0;j<N;j++){
    zz=j*length/(N-1)-length/2.0;
    yy=-sqrt((1-zz*zz*c2inv)/b2inv);
    if(j) multiline(5,
        -xx,yy0+y_b,zz0,
         xx,yy0+y_b,zz0,
         xx, yy+y_b,zz,
        -xx, yy+y_b,zz,
        -xx,yy0+y_b,zz0); /* draw a rectangular slice of the mirror */
    zz0=zz;
    yy0=yy; 
  }
%}

END
