/*******************************************************************************
*
* McXtrace, X-ray tracing package
*         Copyright, All rights reserved
*         DTU Physics, Kgs. Lyngby, Denmark
*         Synchrotron SOLEIL, Saint-Aubin, France
*
* Component: Source_spectra
*
* %Identification
* Written by: Erik Knudsen 
* Date: November 11, 2019
* Origin: Risoe
* Release: McXtrace 1.5
*
* Specialized X-ray source for reading in SPECTRA 10 source definitions
*
* %Description
*
* This is a source component for connecting SPECTRA 10-output files with McXtrace.
* json-style SPECTRA 11 output files are not yet supported.
* 
* SPECTRA is an application software to calculate optical properties of synchrotron 
* radiation (SR) emitted from bending magnets, wigglers (conventional and elliptical) 
* and undulators (conventional, helical, elliptical and figure-8). Calculations 
* of radiation from an arbitrary magnetic field distribution are also available. 
* Parameters on the electron beam and the source can be edited completely on 
* graphical user interfaces (GUIs) and it is possible to show the calculation 
* result graphically. The energy spectrum and radiation power after transmitting 
* various filters and convolution of detector's resolution are also available. 
* See <a href="http://spectrax.org/spectra/">SPECTRA</a>.
*
* If the source is symmetric in x and/or y it is possible to speed up the spectra
* calculations by only including one half-plane or quadrant. The other side/quadrants will then
* be mirrored by McXtrace.
*
* %BUGS
* Absolute intensity of 4D (x,y,x',y') is nor correctly normalized.
*
* %Parameters
* E0:             [keV] Mean energy of X-rays.
* dE:             [keV] Energy spread of X-rays.
* Emin:           [keV] Energy of low end of the Spectra-calculated data.
* Emax:           [keV] Energy of high end of the Spectra-calculated data.
* nE:             [int] Number of steps in the spectra-calculations.
* randomphase:    [0/1] If !=0 the photon phase is chosen randomly.
* nx:             [int] Number of grid points along x in datafiles. If zero this is computed from the files.
* ny:             [int] Number of grid points along y in datafiles. If zero this is computed from the files.
* npx:            [int] Number of grid points along x' in datafiles. If zero this is computed from the files.
* npy:            [int] Number of grid points along y' in datafiles. If zero this is computed from the files.
* phase:          [rad] Value of the photon phase (only used if randomphase==0).
* verbose:        [0/1] If non-zero output more warning messages.
* initial_serial: [int] First serial number of the series of spectra files.
* symmetricx:     [0/1] If nonzero the source is mirrored in the x-axis. This to allow smaller spectra-calculations.
* symmetricy:     [0/1] If nonzero the source is mirrored in the y-axis. This to allow smaller spectra-calculations.
* spectra_stem_x: [str] Filename stem of x-projection of source distribution. -n.xxx will be added where n is a serial number and xxx spectra_suffix.
* spectra_stem_y: [str] Filename stem of y-projection of source distribution. -n.xxx will be added where n is a serial number and xxx spectra_suffix.
* spectra_stem:   [str] Filename stem of x,x',y,y'-source distribution distribution. -n.xxx will be added where n is a serial number and xxx spectra_suffix.
* spectra_suffix: [str] Suffix of spectra output files.
* flag4d:         [0/1] Use either (0) x,y-projections or (1) full 4D x,y,x',y' datafiles.
* noinit:         [0/1] Do no initialize the component. Can be usefiul in conjunction with a deactivating WHEN-clause.
*
* OUTPUT PARAMETERS:
*
* %Link
* Tanaka, J. Synchrotron Rad. (2001). 8, 1221-1228. https://doi.org/10.1107/S090904950101425X
* http://spectrax.org/spectra/
*
* %End
*******************************************************************************/

DEFINE COMPONENT Source_spectra
DEFINITION PARAMETERS ()
SETTING PARAMETERS (
    string spectra_stem_x="", string spectra_stem_y="", string spectra_stem="", string spectra_suffix="dsc",
    E0=0, dE=0, Emin,Emax, int nE, int randomphase=1, phase=0,
    int nx=0, int ny=0, int npx=0, int npy=0, int initial_serial=1, int symmetricx=0, int symmetricy=0, int verbose=0,
    int flag4d=0, int noinit=0)
OUTPUT PARAMETERS ()
/* X-ray parameters: (x,y,z,kx,ky,kz,phi,t,Ex,Ey,Ez,p) */ 

SHARE
%{
  %include "read_table-lib";

  int source_spectra_find_offset(char * fn){
      /*find the first line that starts with [-0-9], i.e. can be considered a number*/
      char line[3][512];
      int linecheck[3],done=0;
      long pos[3]={0,-1,-2};
      double buf[6];
      FILE *fs;

      if( (fs=Open_File(fn,"rb",NULL))==NULL){
        /*Open_File from read_table-lib searches the McXtrace library. Will report error on failure, so just exit.*/
        exit(-1);
      }

      /*read lines and save position 3 lines back, when three consecutive line have 3
        columns we have an offset*/
      line[0][0]='\0';line[1][0]='\0';line[2][0]='\0';
      line[0][511]='\0';line[1][511]='\0';line[2][511]='\0';

      do {
          pos[2]=pos[1];pos[1]=pos[0];pos[0]=ftell(fs);
          strncpy(line[2],line[1],511);
          strncpy(line[1],line[0],511);
          fgets(line[0],512,fs);

          /*check for file overrun*/
          if (feof(fs)){
              fprintf(stderr,"ERROR (Source_spectra): Could not strip header from file %s\n",fn);
              exit(-1);
          }

          int i;
          for (i=0;i<3;i++){
              linecheck[i]=sscanf(line[i],"%lf %lf %lf %lf %lf %lf", buf, buf+1, buf+2, buf+3, buf+4, buf+5);
          }
          if(linecheck[0]==linecheck[1] && linecheck[2]==linecheck[1] && (linecheck[0]==3 || linecheck[0]==5) ){
              done=1;
          }
      } while(!done);
      return pos[2];
  }

  double interpolate_4d_spectra(t_Table data, int *idx,int *N, double *alpha){
    /*interpolate in the 4d spectra data structure at the points idx;idx+1 according
      the normalized coordinates alpha*/
    double first[16],second[8],third[4],fourth[2];
    int i,j,k,l,m;
    double result;
    /*pick the "corner" values to interpolate in*/
    for (m=0;m<16;m++){
      i=idx[0]+ m%2;
      j=idx[1]+ (m/2)%2;
      k=idx[2]+ (m/4)%4;
      l=idx[3]+ (m/8)%2;
      first[m]=Table_Index(data,i+ j*N[0] + k*N[0]*N[1] + l*N[0]*N[1]*N[2],4);
    }
    /*reduce and interpolate the values by successive pairwise weighting, i.e. alpha*p{i,j,k,l} + (1-alpha*p_{i+1,j,k,l} etc.*/
    for (m=0;m<8;m++){
      second[m]=(1-alpha[0])*first[m*2] + alpha[0]*first[m*2+1];
    }
    for (m=0;m<4;m++){
      third[m]=(1-alpha[1])*second[m*2] + alpha[1]*second[m*2+1];
    }
    for (m=0;m<2;m++){
      fourth[m]=(1-alpha[2])*third[m*2] + alpha[2]*third[m*2+1];
    }
    result=(1-alpha[3])*fourth[0] + alpha[3]*fourth[1];
    return result;

  }

%}

DECLARE
%{
  double K;
  double dK;
  double pmul;
  double pint;
  t_Table *xproj;
  t_Table *yproj;
  t_Table *map;
  double *Ix;
  double *Iy;
  double *Imap;
  double xmin;
  double xmax;
  double ymin;
  double ymax;
  double xpmin;
  double xpmax;
  double ypmin;
  double ypmax;
  double xstep;
  double ystep;
  double xpstep;
  double ypstep;
  int brilliance_column;
%}

INITIALIZE
%{
  /*if noinit is set - return early from the init function*/
  if (noinit){
    return(_comp);
  }

  int num,status;
  long offset, orig_offset;
  char fnx[256]="";
  char fny[256]="";

  if(flag4d==0){
    /*flag4d not set - the datafiles are x,x',p and y,y',p projections*/
    brilliance_column=2;
    xproj=calloc(nE,sizeof(t_Table));
    yproj=calloc(nE,sizeof(t_Table));
    Ix=calloc(nE,sizeof(double));
    Iy=calloc(nE,sizeof(double));
    if(xproj==NULL || yproj==NULL || Ix==NULL || Iy==NULL){
      fprintf(stderr,"ERROR (%s): Memory allocation error\n",NAME_CURRENT_COMP);
      exit(-1);
    }
    /*find the offset of the datafiles. Assume to be identical for all of them.*/
    snprintf(fnx,255,"%s-%d.%s",spectra_stem_x,initial_serial,spectra_suffix);
    orig_offset=source_spectra_find_offset(fnx);

    for (num=0;num<nE;num++){
      snprintf(fnx,255,"%s-%d.%s",spectra_stem_x,num+initial_serial,spectra_suffix);
      offset=orig_offset;/*Have to do this every time since Table_Read_Offset overwrites offset*/
      if ( (status=Table_Read_Offset(&(xproj[num]),fnx,0,&offset,0))==-1){
        fprintf(stderr,"ERROR (%s): Could not parse file \"%s\"\n",NAME_CURRENT_COMP,fnx);
        exit(-1);
      }
      snprintf(fny,255,"%s-%d.%s",spectra_stem_y,num+initial_serial,spectra_suffix);
      offset=orig_offset;/*Have to do this every time since Table_Read_Offset overwrites offset*/
      if ( (status=Table_Read_Offset(&(yproj[num]),fny,0,&offset,0))==-1){
        fprintf(stderr,"ERROR (%s): Could not parse file \"%s\"\n",NAME_CURRENT_COMP,fny);
        exit(-1);
      }
      Ix[num]=Iy[num]=0;
      /*sum the brilliances to get something to normalize to*/
      int r;
      for (r=0;r<xproj[num].rows;r++){
        Ix[num]+=Table_Index(xproj[num],r,brilliance_column);//xproj.data[r*xproj.columns+ brilliance_column];
      }

      for (r=0;r<yproj[num].rows;r++){
        Iy[num]+=Table_Index(yproj[num],r,brilliance_column);//yproj.data[r*yproj.columns+ brilliance_column];
      }
      if (verbose && Ix[num]!=Iy[num]){
        fprintf(stderr,"WARNING (%s): Integrated intensities do not match up for x and y projections at num %d\n",NAME_CURRENT_COMP,num);
      }
      if (verbose) printf("INFO (%s): Integrated intensity for projections I [%d] = (%g,%g)\n",NAME_CURRENT_COMP,num,Ix[num],Iy[num]);

      if (num==0){
        /*check the data structure for the first two input files*/
        /*if not given deduce the number of sample-points in datafiles*/
        if (nx==0){
          int r;
          double p1,p2;
          for (r=0;r<xproj[0].rows;r++){
            if ( nx==0 && (p1=Table_Index(xproj[0],r,0))>(p2=Table_Index(xproj[0],r+1,0)) ){
              /*this means we have found where the first coordinate starts over*/
              nx=r+1;
              break;
            }
          }
          if (nx==0){
            nx=1;
          }
          npx/=nx;
        }
        if (npx==0){
          npx=xproj[0].rows/nx;
        }

        if( nx*npx != xproj[0].rows){
          fprintf(stderr,"Error (%s): number of read rows (%d) in %s does not match nx*npx = ( %d * %d). Please check the input files. Aborting.\n",NAME_CURRENT_COMP,xproj[0].rows,fnx,nx,npx);
          exit(-1);
        }

        if(ny==0){
          int r;
          for (r=0;r<yproj[0].rows;r++){
            if ( ny==0 && Table_Index(yproj[0],r,0)>Table_Index(yproj[0],r+1,0) ){
              /*this means we have found where the first coordinate starts over*/
              ny=r+1;
              break;
            }
          }
          if (ny==0){
            ny=1;
          }
        }
        if (npy==0){
          npy=yproj[0].rows/ny;
        }
        if( ny*npy != yproj[0].rows){
          fprintf(stderr,"ERROR (%s): number of read rows (%d) in %s does not match ny*npy = ( %d * %d). Please check the input files. Aborting.\n",NAME_CURRENT_COMP,yproj[0].rows,fny,ny,npy);
          exit(-1);
        }
        if(verbose) printf("INFO (%s): (nx,nxp) = ( %d %d ), (ny,npy) = ( %d %d )\n",NAME_CURRENT_COMP,nx,npx,ny,npy);
      }/*if num==0*/
    }
    /*find limits in x,x',y, and y', assuming they're the same across all source files.*/
    /*these would be relevant for a search*/

    t_Table *xptr=&(xproj[0]);
    t_Table *yptr=&(yproj[0]);
    xmin=Table_Index(*xptr,0,0);
    xpmin=Table_Index(*xptr,0,1);
    ymin=Table_Index(*yptr,0,0);
    ypmin=Table_Index(*yptr,0,1);
    xmax=Table_Index(*xptr,nx-1,0);
    xpmax=Table_Index(*xptr,nx*npx-1,1);
    ymax=Table_Index(*yptr,ny-1,0);
    ypmax=Table_Index(*yptr,ny*npy-1,1);
    xstep=Table_Index(*xptr,1,0)-Table_Index(*xptr,0,0);
    xpstep=Table_Index(*xptr,nx,1)-Table_Index(*xptr,0,1);
    ystep=Table_Index(*yptr,1,0)-Table_Index(*yptr,0,0);
    ypstep=Table_Index(*yptr,ny,1)-Table_Index(*yptr,0,1);

    if(verbose && xmin==0 && symmetricx==0){
    fprintf(stderr,"WARNING (%s): Minimum x-value in datafile is 0 but symmetricx is not set.\n",NAME_CURRENT_COMP);
    }
    if(verbose && xmin==0 && symmetricy==0){
      fprintf(stderr,"WARNING (%s): Minimum y-value in datafile is 0 but symmetricy is not set.\n",NAME_CURRENT_COMP);
    }
  } else {
    /*The datafiles are 4D: i.e. x,x',y,y' and p columns;*/
    brilliance_column=4;
    map=calloc(nE,sizeof(t_Table));
    Imap=calloc(nE,sizeof(double));
    sprintf(fnx,"%s-%d.%s",spectra_stem,initial_serial,spectra_suffix);
    orig_offset=source_spectra_find_offset(fnx);
    for (num=0;num<nE;num++){
      sprintf(fnx,"%s-%d.%s",spectra_stem,num+initial_serial,spectra_suffix);
      offset=orig_offset;/*Table_Read_Offset overwrites offset*/
      if ( (status=Table_Read_Offset(&(map[num]),fnx,0,&offset,0))==-1){
        fprintf(stderr,"Source_spectra(%s) Error: Could not parse file \"%s\"\n",NAME_CURRENT_COMP,fnx);
        exit(-1);
      }
      Imap[num]=0;
      /*sum the brilliances to get something to normalize to*/
      int r;
      for (r=0;r<map[num].rows;r++){
        Imap[num]+=Table_Index(map[num],r,brilliance_column);
      }
    }
    /*if the limits are not given, try to deduce the number of steps from the data files*/
    int r;
    double p1,p2;
    for (r=0;r<map[0].rows;r++){
      if ( nx==0 && (p1=Table_Index(map[0],r,0))>(p2=Table_Index(map[0],r+1,0)) ){
        /*this means we have found where the first coordinate starts over*/
        nx=r+1;
      }
      if ( ny==0 && (p1=Table_Index(map[0],r,1))>(p2=Table_Index(map[0],r+1,1)) ){
        /*this means we have found where the 2nd coordinate starts over*/
        ny=(r+1)/nx;
      }
      if ( npx==0 && (p1=Table_Index(map[0],r,2))>(p2=Table_Index(map[0],r+1,2)) ){
        /*this means we have found where the 3rd coordinate starts over*/
        npx=(r+1)/(nx*ny);
      }
      /*if first 3 are set stop searching - set the last later*/
      if(nx && ny && npx ){
        break;
      }
    }
    if (nx==0){
      nx=1;
    }
    if (ny==0){
      ny=1;
    }
    if (npx==0){
      npx=1;
    }
    /*last one is set frm th evalues of the other ones*/
    if (npy==0){
      npy=map[0].rows/(nx*ny*npx);
    }
    if (verbose) printf("INFO (%s): [Nx,Ny,Nx,Ny]= [ %d %d %d %d ] for files: %s-X.%s\n",NAME_CURRENT_COMP,nx,ny,npx,npy,spectra_stem,spectra_suffix);

    /*set limit values. Use that the last row contains the maximum value for all 4 dimensions.*/
    xmin=Table_Index(map[0],0,0);
    xmax=Table_Index(map[0],map[0].rows,0);
    xstep=(xmax-xmin)/nx;
    ymin=Table_Index(map[0],0,1);
    ymax=Table_Index(map[0],map[0].rows,1);
    ystep=(ymax-ymin)/ny;
    xpmin=Table_Index(map[0],0,2);
    xpmax=Table_Index(map[0],map[0].rows,2);
    xpstep=(xpmax-xpmin)/npx;
    ypmin=Table_Index(map[0],0,3);
    ypmax=Table_Index(map[0],map[0].rows,3);
    ypstep=(ypmax-ypmin)/npy;

    if (verbose) printf("INFO (%s): (xmin,xmax)=( %g %g ), (ymin,ymax)=( %g %g ), (xpmin,xpmax)=( %g %g ), (ypmin,ypmax)=( %g %g )\n",NAME_CURRENT_COMP,xmin,xmax,ymin,ymax,xpmin,xpmax,ypmin,ypmax);
  }

  if(E0-dE-Emin <-FLT_MAX || E0+dE-Emax>FLT_MAX){
    fprintf(stderr,"WARNING(%s): Sampled energy interval (%g+-%g keV) reaches outside what\'s defined by datafiles (%g+-%g keV)\n",NAME_CURRENT_COMP,E0,dE,(Emin+Emax)*0.5,(Emax-Emin)*0.5);
  }

  /*downweight accoring to number of rays*/
  pmul=1.0/((double) mcget_ncount());

  /*downweight for not using the full energy window. Don't do this for deterministic energy (dE=0).*/
  if(dE && ( (E0-dE>Emin) || E0+dE<Emax) ){
    pmul*=2*dE/(Emax-Emin);
  }

%}

TRACE
%{
  double kk,theta_x,theta_y,l,e,k,xp,yp;
  int num,ix,ipx,iy,ipy;
  double alpha,beta,Iinterpx,Iinterpy;
  t_Table *xptr,*yptr;
  
  p=pmul;
  theta_x=(xpmin + rand01()*(xpmax-xpmin))*1e-3;
  theta_y=(ypmin + rand01()*(ypmax-ypmin))*1e-3;
  
  x=(xmin+rand01()*(xmax-xmin))*1e-3;
  y=(ymin+rand01()*(ymax-ymin))*1e-3;

  
  /*So now interpolate to get at Brilliance values*/
  /*Need to normalize to something*/
  
  /*pick an energy randomly*/
  e=rand01()*2*dE+(E0-dE);
  if(e<Emin || e>Emax){
    ABSORB;
  }
  k=E2K*e;

  kx=tan(theta_x);
  ky=tan(theta_y);
  kz=1;
  NORM(kx,ky,kz);

  kx*=k;
  ky*=k;
  kz*=k;
  /*compute xp and yp*/
  xp=kx/kz*1e3;/*spectra output is in millirad*/
  yp=ky/kz*1e3;
  double xx=x*1e3;/*spectra output is in mm*/
  double yy=y*1e3;

  ix  = (int)floor((xx - xmin)*(nx-1)/(xmax - xmin));
  ipx = (int)floor((xp- xpmin)*(npx-1)/(xpmax-xpmin));
  iy  = (int)floor((yy - ymin)*(ny-1)/(ymax - ymin));
  ipy = (int)floor((yp- ypmin)*(npy-1)/(ypmax-ypmin));

  int ie;
  double pe[2];
  double ealpha,estep;
  estep=(Emax-Emin)/(nE-1);
  num=(int)floor( (e-Emin)/estep);
  if (num<0) num=0;
  if (num>nE-1) num=nE-1;
  ealpha = (e- (Emin+estep*num))/estep;

  if(!flag4d){
    xptr=&(xproj[num]);
    yptr=&(yproj[num]);
    for (ie=0;ie<2;ie++){
      alpha=( (xx - Table_Index(*xptr,ix,0)) /xstep);/*regular grid so no need to do ix + ipx*nx*/
      beta=( (xp - Table_Index(*xptr,ipx*nx,1)) /xpstep) ;

      double t0,t1;
      t0=(1-alpha)*fabs(Table_Index(*xptr,ix+ipx*nx,2)) + alpha*fabs(Table_Index(*xptr,(ix+1)+ipx*nx,2));
      t1=(1-alpha)*fabs(Table_Index(*xptr,ix+(ipx+1)*nx,2)) + alpha*fabs(Table_Index(*xptr,(ix+1)+(ipx+1)*nx,2));
      Iinterpx = (1-beta)*t0+beta*t1 *xstep*1e-3*xpstep*1e-3;

      alpha=( (yy - Table_Index(*yptr,iy,0)) /ystep);/*regular grid so no need to do iy + ipy*ny*/
      beta=( (yp - Table_Index(*yptr,ipy*ny,1)) /ypstep) ;

      t0=(1-alpha)*fabs(Table_Index(*yptr,iy+ipy*ny,2)) + alpha*fabs(Table_Index(*yptr,(iy+1)+ipy*ny,2));
      t1=(1-alpha)*fabs(Table_Index(*yptr,iy+(ipy+1)*ny,2)) + alpha*fabs(Table_Index(*yptr,(iy+1)+(ipy+1)*ny,2));
      Iinterpy = (1-beta)*t0+beta*t1 * ystep*1e-3*ypstep*1e-3;

      pe[ie]=Iinterpx/Ix[num+ie] * Iinterpy/Iy[num+ie] * (Ix[num+ie]+Iy[num+ie])*0.5;
    }
    /* Set the photon ray weight to the mean of the two multiplied by initial weight
     * due to energy interval and ray count*/

    //p=pmul * Iinterpx/Ix[num] * Iinterpy/Iy[num] * (Ix[num]+Iy[num])*0.5;
    /*now we interpolate in energy*/
    p=pmul * ( (1-ealpha)*pe[0] + ealpha*pe[1]);
  }else{
    /*each pt in x,x',y,y' space is surrounded by 16 points. Interpolate between them*/
    double alx,alxp,aly,alyp;
    alx=( (xx - Table_Index(map[ie],ix,0)) /xstep);
    aly=( (yy - Table_Index(map[ie],iy*nx,1)) /ystep);
    alxp=( (xp - Table_Index(map[ie],ipx*nx*ny,2)) /xpstep);
    alyp=( (yp - Table_Index(map[ie],ipy*nx*ny*npx,3)) /ypstep);

    for (ie=0;ie<2;ie++){
      int idx[]={ix,iy,ipx,ipy};
      double alpha[]={alx,aly,alxp,alyp};
      int NN[]={nx,ny,npx,npy};
      pe[ie]=interpolate_4d_spectra(map[num+ie],idx,NN,alpha)*xstep*ystep*1e-6 *xpstep*ypstep*1e-6;
    }
    /*lastly interpolate over energy*/
    p=pmul * ( (1-ealpha)*pe[0] + ealpha*pe[1]);
  }
  /*if symmetric source possibly reflect x*/
  if (symmetricx){
    if (rand01()<0.5){
      x=-x;
    }
    if (rand01()<0.5){
      kx=-kx;
    }
  }
  if (symmetricy){
    if (rand01()<0.5){
      y=-y;
    }
    if (rand01()<0.5){
      ky=-ky;
    }
  }

  /*spectra output is in brilliance (unit photons/s/mm^2/mrad^2/0.1%BW), so scale to get at raw flux in photons/s */

  /*set polarization and phase to something known*/
  Ex=0;Ey=0;Ez=0;
  if (!randomphase){
    phi=0;
  }else{
    phi=rand01()*M_2_PI;
  }

  /*set polarization vector*/
  Ex=0;Ey=0;Ez=0;

%}

FINALLY
%{
  if(!noinit){
    if(!flag4d){
      free(Ix);
      free(Iy);
      Table_Free(xproj);
      Table_Free(yproj);
      free(yproj);
      free(xproj);
    }else{
      free(Imap);
      Table_Free(map);
      free(map);
    }
  }
%}

MCDISPLAY
%{
  double dist=1;
  multiline(5, xmin, ymin, 0.0,
      xmax, ymin, 0.0,
      xmax, ymax, 0.0,
      xmin, ymax, 0.0,
      ymin, ymin, 0.0);

  dashed_line(0,0,0, tan(xpmax)*dist,0,dist,4);
  dashed_line(0,0,0, tan(xpmin)*dist,0,dist,4);
    
  dashed_line(0,0,0, 0,tan(ypmax)*dist,dist,4);
  dashed_line(0,0,0, 0,tan(ypmin)*dist,dist,4);
%}

END
