/*******************************************************************************
*
* McStas, neutron ray-tracing package
*         Copyright 1997-2002, All rights reserved
*         Risoe National Laboratory, Roskilde, Denmark
*         Institut Laue Langevin, Grenoble, France
*
* Component: Source_adapt.comp
*
* %I
* Written by: Kristian Nielsen
* Date: 1999
* Version: $Revision: 1.28 $
* Origin: Risoe
* Release: McStas 1.6
*
* Neutron source with adaptive importance sampling
*
* %D
* Rectangular source with flat energy distribution that uses adaptive
* importance sampling to improve simulation efficiency. Works
* together with the Adapt_check component.
*
* The source divides the three-dimensional phase space of (energy,
* horizontal position, horizontal divergence) into a number of
* rectangular bins. The probability for selecting neutrons from each
* bin is adjusted so that neutrons that reach the Adapt_check
* component with high weights are emitted more frequently than those
* with low weights. The adjustment is made so as to attemt to make
* the weights at the Adapt_check components equal.
*
* Focusing is achieved by only emitting neutrons towards a rectangle
* perpendicular to and placed at a certain distance along the Z axis.
* Focusing is only approximate (for simplicity); neutrons are also
* emitted to pass slightly above and below the focusing rectangle,
* more so for wider focusing.
*
* In order to prevent false learning, a parameter beta sets a
* fraction of the neutrons that are emitted uniformly, without regard
* to the adaptive distribution. The parameter alpha sets an initial
* fraction of neutrons that are emitted with low weights; this is
* done to prevent early neutrons with rare initial parameters but
* high weight to ruin the statistics before the component adapts its
* distribution to the problem at hand. Good general-purpose values
* for these parameters are alpha = beta = 0.25.
*
* %P
* INPUT PARAMETERS:
*
* xmin:   (m)          Left edge of rectangular source
* xmax:   (m)          Right edge
* ymin:   (m)          Lower edge
* ymax:   (m)          Upper edge
* dist:   (m)          Distance to target rectangle along z axis
* xw:     (m)          Width(x) of target
* yh:     (m)          Height(y) of target
* E0:     (meV)        Mean energy of neutrons
* dE:     (meV)        Energy spread (energy range is from E0-dE to E0+dE)
* flux:   (1/(cm**2*AA**st)) Absolute source flux
* N_E:    (1)          Number of bins in energy dimension
* N_xpos: (1)          Number of bins in horizontal position
* N_xdiv: (1)          Number of bins in horizontal divergence
* alpha:  (1)          Learning cut-off factor (0 < alpha <= 1)
* beta:   (1)          Aggressiveness of adaptive algorithm (0 < beta <= 1)
* filename: (string)   Optional filename for adaptive distribution output
*
* OUTPUT PARAMETERS:
*
* p_in:  Internal, holds initial neutron weight
* y_0:   Internal
* C:     Internal
* r_0:   Internal
* count: Internal, counts neutrons emitted
* adpt:  Internal structure shared with the Adapt_check component
*
* %E
*******************************************************************************/

DEFINE COMPONENT Source_adapt
DEFINITION PARAMETERS (N_E=20, N_xpos=20, N_xdiv=20, alpha=0.25, beta=0.25,filename=0)
SETTING PARAMETERS (xmin,xmax,ymin,ymax, dist=2.33, xw=0.05, yh=0.1, E0=14.68, dE=2, flux=1e13)
OUTPUT PARAMETERS (p_in, y_0, C, r_0, count, adpt)
STATE PARAMETERS (x,y,z,vx,vy,vz,t,s1,s2,p)

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

DECLARE
%{
  struct source_adapt
    {
      struct adapt_tree *atree; /* Adaptive search tree */
      int idx;                  /* Index of current bin */
      double *psi, *n;          /* Arrays of weight sums, neutron counts */
      double psi_tot;           /* Total weight sum */
      double pi, num;           /* Initial p, number of bins in tree */
      double factor;            /* Adaption quality factor */
      double a_beta;            /* Adaption agression factor */
    } adpt;
  double count;                 /* Neutron counter */
  double y_0, C, r_0;
  double p_in;
%}
INITIALIZE
%{
  int i;
  double a, lambda_min, lambda_max, delta_lambda, source_area;

  adpt.num = N_E*N_xpos*N_xdiv;
  adpt.a_beta = beta;
  lambda_min = sqrt(81.81/(E0+dE)); /* AAngstroem */
  lambda_max = sqrt(81.81/(E0-dE));
  delta_lambda = lambda_max - lambda_min;
  source_area = (xmax - xmin)*(ymax - ymin)*1e4; /* cm^2 */
  p_in = flux/mcget_ncount()*delta_lambda*source_area;
  adpt.atree = adapt_tree_init(adpt.num);
  adpt.psi = malloc(adpt.num*sizeof(*adpt.psi));
  adpt.n = malloc(adpt.num*sizeof(*adpt.n));
  if(!(adpt.psi && adpt.n))
  {
    fprintf(stderr, "Fatal error: out of memory.\n");
    exit(1);
  }
  for(i = 0; i < adpt.num; i++)
  {
    adapt_tree_add(adpt.atree, i, 1.0/adpt.num);
    adpt.psi[i] = adpt.n[i] = 0;
  }
  adpt.psi_tot = 0;
  count = 0;
  y_0 = adpt.num > 8 ? 2.0/adpt.num : 0.25;
  r_0 = 1/(double)alpha*log((1 - y_0)/y_0)/(double)mcget_ncount();
  C = 1/(1 + log(y_0 + (1 - y_0)*exp(-r_0*mcget_ncount()))/(r_0*mcget_ncount()));
%}
TRACE
%{
  double thmin,thmax,phmin,phmax,theta,phi,E,v,r;
  double new_v;
  int i_E, i_xpos, i_xdiv;

  /* Randomly select a bin in the current distribution */
  r = rand01();
  adpt.idx = adapt_tree_search(adpt.atree, adpt.atree->total*r);
  if(adpt.idx >= adpt.num)
  {
    fprintf(stderr,
            "Hm, idx is %d, num is %d, r is %g, atree->total is %g\n",
            adpt.idx, (int)adpt.num, r, adpt.atree->total);
    adpt.idx = adpt.num - 1;
  }
  /* Now find the bin coordinates. */
  i_xdiv = adpt.idx % (int)N_xdiv;
  i_xpos = (adpt.idx / (int)N_xdiv) % (int)N_xpos;
  i_E = (adpt.idx / (int)N_xdiv) / (int)N_xpos;
  /* Compute the initial neutron parameters, selecting uniformly randomly
     within each bin dimension. */
  x = xmin + (i_xpos + rand01())*((xmax - xmin)/(double)N_xpos);
  y = ymin + rand01()*(ymax - ymin);
  z=0;
  thmin = atan2(-xw/2.0 - x, dist);
  thmax = atan2( xw/2.0 - x, dist);
  theta = thmin + (i_xdiv + rand01())*((thmax - thmin)/(double)N_xdiv);
  phmin = atan2(-yh/2.0 - y, dist);
  phmax = atan2( yh/2.0 - y, dist);
  phi = phmin + rand01()*(phmax - phmin);
  E = E0 - dE + (i_E + rand01())*(2.0*dE/(double)N_E);
  v = sqrt(E)*SE2V;
  vy = v*sin(phi);
  vx = v*cos(phi)*sin(theta);
  vz = v*cos(phi)*cos(theta);
  t = 0;
  /* Adjust neutron weight. */
  p = p_in;
  adpt.factor = y_0/(y_0 + (1 - y_0)*exp(-r_0*count));
  count++;
  p /= adpt.atree->v[adpt.idx]/(adpt.atree->total/adpt.num);
  p *= C*adpt.factor*(thmax - thmin)*(sin(phmax) - sin(phmin));
  SCATTER;
  /* Update distribution, assuming absorbtion. */
  if(adpt.n[adpt.idx] > 0)
    adpt.psi_tot -= adpt.psi[adpt.idx]/
      (adpt.n[adpt.idx]*(adpt.n[adpt.idx] + 1));
  adpt.n[adpt.idx]++;
  if(adpt.psi_tot != 0)
  {
    new_v = (1 - adpt.a_beta)*adpt.factor*adpt.psi[adpt.idx]/
                (adpt.n[adpt.idx]*adpt.psi_tot) +
            adpt.a_beta/adpt.num;
    adapt_tree_add(adpt.atree, adpt.idx, new_v - adpt.atree->v[adpt.idx]);
  }
  /* Remember initial neutron weight. */
  adpt.pi = p;
%}

FINALLY
%{
  double *p1 = NULL;
  int i;

  if(filename)
  {
    p1 = malloc(adpt.num*sizeof(double));
    if(!p1)
      fprintf(stderr, "Warning: Source_adapt: "
              "not enough memory to write distribution.\n");
  }
  if(p1)
  {
    for(i = 0; i < adpt.num; i++)
      p1[i] = adpt.atree->v[i]/adpt.atree->total;
    DETECTOR_OUT_1D("Adaptive source energy distribution",
                    "Energy [meV]",
                    "Probability",
                    "E", E0 - dE, E0 + dE, adpt.num,
                    NULL, p1, NULL, filename);
    free(p1);
  }
  adapt_tree_free(adpt.atree);
%}
MCDISPLAY
%{
  magnify("xy");
  multiline(5, (double)xmin, (double)ymin, 0.0,
               (double)xmax, (double)ymin, 0.0,
               (double)xmax, (double)ymax, 0.0,
               (double)xmin, (double)ymax, 0.0,
               (double)xmin, (double)ymin, 0.0);
%}

END
