#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#ifndef NOMPI
#include <mpi.h>
#endif
#include <sys/types.h>
#include <unistd.h>
#include <gsl/gsl_rng.h>

#include "allvars.h"
#include "proto.h"

/*! \file begrun.c
 *  \brief initial set-up of a simulation run
 *
 *  This file contains various functions to initialize a simulation run. In
 *  particular, the parameterfile is read in and parsed, the initial
 *  conditions or restart files are read, and global variables are
 *  initialized to their proper values.
 */

/*! This function performs the initial set-up of the simulation. First, the
 *  parameterfile is set, then routines for setting units, reading
 *  ICs/restart-files are called, auxialiary memory is allocated, etc.
 */
void begrun(void)
{
  struct global_data_all_processes all;

  if(ThisTask == 0)
    {
      printf("\nThis is Gadget, version `%s'.\n", GADGETVERSION);
      printf("\nRunning on %d processors.\n", NTask);
    }

  read_parameter_file(ParameterFile);	/* ... read in parameters for this run */

  allocate_commbuffers();	/* ... allocate buffer-memory for particle
				   exchange during force computation */
  set_units();

#if defined(PERIODIC) && (!defined(PMGRID) || defined(FORCETEST))
  ewald_init();
#endif

  open_outputfiles();

  random_generator = gsl_rng_alloc(gsl_rng_ranlxd1);
  gsl_rng_set(random_generator, 42);	/* start-up seed */

#ifdef PMGRID
  long_range_init();
#endif

  All.TimeLastRestartFile = CPUThisRun;

  if(RestartFlag == 0 || RestartFlag == 2)
    {
      set_random_numbers();

      init();			/* ... read in initial model */
    }
  else
    {
      all = All;		/* save global variables. (will be read from restart file) */

      restart(RestartFlag);	/* ... read restart file. Note: This also resets
				   all variables in the struct `All'.
				   However, during the run, some variables in the parameter
				   file are allowed to be changed, if desired. These need to
				   copied in the way below.
				   Note:  All.PartAllocFactor is treated in restart() separately.
				 */

      All.MinSizeTimestep = all.MinSizeTimestep;
      All.MaxSizeTimestep = all.MaxSizeTimestep;
      All.BufferSize = all.BufferSize;
      All.BunchSizeForce = all.BunchSizeForce;
      All.BunchSizeDensity = all.BunchSizeDensity;
      All.BunchSizeHydro = all.BunchSizeHydro;
      All.BunchSizeDomain = all.BunchSizeDomain;

      All.TimeLimitCPU = all.TimeLimitCPU;
      All.ResubmitOn = all.ResubmitOn;
      All.TimeBetSnapshot = all.TimeBetSnapshot;
      All.TimeBetStatistics = all.TimeBetStatistics;
      All.CpuTimeBetRestartFile = all.CpuTimeBetRestartFile;
      All.ErrTolIntAccuracy = all.ErrTolIntAccuracy;
      All.MaxRMSDisplacementFac = all.MaxRMSDisplacementFac;

      All.ErrTolForceAcc = all.ErrTolForceAcc;

      All.TypeOfTimestepCriterion = all.TypeOfTimestepCriterion;
      All.TypeOfOpeningCriterion = all.TypeOfOpeningCriterion;
      All.NumFilesWrittenInParallel = all.NumFilesWrittenInParallel;
      All.TreeDomainUpdateFrequency = all.TreeDomainUpdateFrequency;

      All.SnapFormat = all.SnapFormat;
      All.NumFilesPerSnapshot = all.NumFilesPerSnapshot;
      All.MaxNumNgbDeviation = all.MaxNumNgbDeviation;
      All.ArtBulkViscConst = all.ArtBulkViscConst;
      All.ArtBulkViscBeta = 2*all.ArtBulkViscConst;

      All.OutputListOn = all.OutputListOn;
      All.CourantFac = all.CourantFac;

      All.OutputListLength = all.OutputListLength;
      memcpy(All.OutputListTimes, all.OutputListTimes, sizeof(double) * All.OutputListLength);

      strcpy(All.ResubmitCommand, all.ResubmitCommand);
      strcpy(All.OutputListFilename, all.OutputListFilename);
      strcpy(All.OutputDir, all.OutputDir);
      strcpy(All.RestartFile, all.RestartFile);
      strcpy(All.EnergyFile, all.EnergyFile);
      strcpy(All.InfoFile, all.InfoFile);
      strcpy(All.CpuFile, all.CpuFile);
      strcpy(All.TimingsFile, all.TimingsFile);
      strcpy(All.SnapshotFileBase, all.SnapshotFileBase);

      if(All.TimeMax != all.TimeMax)
	readjust_timebase(All.TimeMax, all.TimeMax);
    }

#ifdef PMGRID
  long_range_init_regionsize();
#endif

  if(All.ComovingIntegrationOn)
    init_drift_table();

  if(RestartFlag == 2)
    All.Ti_nextoutput = find_next_outputtime(All.Ti_Current + 1);
  else
    All.Ti_nextoutput = find_next_outputtime(All.Ti_Current);

  All.TimeLastRestartFile = CPUThisRun;
}

/*! Computes conversion factors between internal code units and the
 *  cgs-system.
 */
void set_units(void)
{
  double meanweight;

  All.UnitTime_in_s = All.UnitLength_in_cm / All.UnitVelocity_in_cm_per_s;
  All.UnitTime_in_Megayears = All.UnitTime_in_s / SEC_PER_MEGAYEAR;

  if(All.GravityConstantInternal == 0)
    All.G = GRAVITY / pow(All.UnitLength_in_cm, 3) * All.UnitMass_in_g * pow(All.UnitTime_in_s, 2);
  else
    All.G = All.GravityConstantInternal;

  All.UnitDensity_in_cgs = All.UnitMass_in_g / pow(All.UnitLength_in_cm, 3);
  All.UnitPressure_in_cgs = All.UnitMass_in_g / All.UnitLength_in_cm / pow(All.UnitTime_in_s, 2);
  All.UnitCoolingRate_in_cgs = All.UnitPressure_in_cgs / All.UnitTime_in_s;
  All.UnitEnergy_in_cgs = All.UnitMass_in_g * pow(All.UnitLength_in_cm, 2) / pow(All.UnitTime_in_s, 2);

  /* convert some physical input parameters to internal units */

  All.Hubble = HUBBLE * All.UnitTime_in_s;

  if(ThisTask == 0)
    {
      printf("\nHubble (internal units) = %g\n", All.Hubble);
      printf("G (internal units) = %g\n", All.G);
      printf("UnitMass_in_g = %g \n", All.UnitMass_in_g);
      printf("UnitTime_in_s = %g \n", All.UnitTime_in_s);
      printf("UnitVelocity_in_cm_per_s = %g \n", All.UnitVelocity_in_cm_per_s);
      printf("UnitDensity_in_cgs = %g \n", All.UnitDensity_in_cgs);
      printf("UnitEnergy_in_cgs = %g \n", All.UnitEnergy_in_cgs);
      printf("\n");
    }

  meanweight = 4.0 / (1 + 3 * HYDROGEN_MASSFRAC);	/* note: we assume neutral gas here */

#ifdef ISOTHERM_EQS
  All.MinEgySpec = 0;
#else
  All.MinEgySpec = 1 / meanweight * (1.0 / GAMMA_MINUS1) * (BOLTZMANN / PROTONMASS) * All.MinGasTemp;
  All.MinEgySpec *= All.UnitMass_in_g / All.UnitEnergy_in_cgs;
#endif

}

/*!  This function opens various log-files that report on the status and
 *   performance of the simulstion. On restart from restart-files
 *   (start-option 1), the code will append to these files.
 */
void open_outputfiles(void)
{
  char mode[2], buf[200];

  if(ThisTask != 0)		/* only the root processor writes to the log files */
    return;

  if(RestartFlag == 0)
    strcpy(mode, "w");
  else
    strcpy(mode, "a");

  sprintf(buf, "%s%s", All.OutputDir, All.CpuFile);
  if(!(FdCPU = fopen(buf, mode)))
    {
      printf("error in opening file '%s'\n", buf);
      endrun(1);
    }

  sprintf(buf, "%s%s", All.OutputDir, All.InfoFile);
  if(!(FdInfo = fopen(buf, mode)))
    {
      printf("error in opening file '%s'\n", buf);
      endrun(1);
    }

  sprintf(buf, "%s%s", All.OutputDir, All.EnergyFile);
  if(!(FdEnergy = fopen(buf, mode)))
    {
      printf("error in opening file '%s'\n", buf);
      endrun(1);
    }

  sprintf(buf, "%s%s", All.OutputDir, All.TimingsFile);
  if(!(FdTimings = fopen(buf, mode)))
    {
      printf("error in opening file '%s'\n", buf);
      endrun(1);
    }

#ifdef FORCETEST
  if(RestartFlag == 0)
    {
      sprintf(buf, "%s%s", All.OutputDir, "forcetest.txt");
      if(!(FdForceTest = fopen(buf, "w")))
	{
	  printf("error in opening file '%s'\n", buf);
	  endrun(1);
	}
      fclose(FdForceTest);
    }
#endif
}

/*!  This function closes the global log-files.
 */
void close_outputfiles(void)
{
  if(ThisTask != 0)		/* only the root processor writes to the log files */
    return;

  fclose(FdCPU);
  fclose(FdInfo);
  fclose(FdEnergy);
  fclose(FdTimings);
#ifdef FORCETEST
  fclose(FdForceTest);
#endif
}

/*! This function parses the parameterfile in a simple way.  Each paramater
 *  is defined by a keyword (`tag'), and can be either of type double, int,
 *  or character string.  The routine makes sure that each parameter
 *  appears exactly once in the parameterfile, otherwise error messages are
 *  produced that complain about the missing parameters.
 */
void read_parameter_file(char *fname)
{
#define DOUBLE 1
#define STRING 2
#define INT 3
#define MAXTAGS 300

  FILE *fd, *fdout;
  char buf[200], buf1[200], buf2[200], buf3[400];
  int i, j, nt;
  int id[MAXTAGS];
  void *addr[MAXTAGS];
  char tag[MAXTAGS][50];
  int  errorFlag = 0;

  if(sizeof(long long) != 8)
    {
      if(ThisTask == 0)
	printf("\nType `long long' is not 64 bit on this platform. Stopping.\n\n");
      endrun(0);
    }

  if(sizeof(int) != 4)
    {
      if(ThisTask == 0)
	printf("\nType `int' is not 32 bit on this platform. Stopping.\n\n");
      endrun(0);
    }

  if(sizeof(float) != 4)
    {
      if(ThisTask == 0)
	printf("\nType `float' is not 32 bit on this platform. Stopping.\n\n");
      endrun(0);
    }

  if(sizeof(double) != 8)
    {
      if(ThisTask == 0)
	printf("\nType `double' is not 64 bit on this platform. Stopping.\n\n");
      endrun(0);
    }

  if(ThisTask == 0)		/* read parameter file on process 0 */
    {
      nt = 0;

      strcpy(tag[nt], "InitCondFile");
      addr[nt] = All.InitCondFile;
      id[nt++] = STRING;

      strcpy(tag[nt], "OutputDir");
      addr[nt] = All.OutputDir;
      id[nt++] = STRING;

      strcpy(tag[nt], "SnapshotFileBase");
      addr[nt] = All.SnapshotFileBase;
      id[nt++] = STRING;

      strcpy(tag[nt], "EnergyFile");
      addr[nt] = All.EnergyFile;
      id[nt++] = STRING;

      strcpy(tag[nt], "CpuFile");
      addr[nt] = All.CpuFile;
      id[nt++] = STRING;

      strcpy(tag[nt], "InfoFile");
      addr[nt] = All.InfoFile;
      id[nt++] = STRING;

      strcpy(tag[nt], "TimingsFile");
      addr[nt] = All.TimingsFile;
      id[nt++] = STRING;

      strcpy(tag[nt], "RestartFile");
      addr[nt] = All.RestartFile;
      id[nt++] = STRING;

      strcpy(tag[nt], "ResubmitCommand");
      addr[nt] = All.ResubmitCommand;
      id[nt++] = STRING;

      strcpy(tag[nt], "OutputListFilename");
      addr[nt] = All.OutputListFilename;
      id[nt++] = STRING;

      strcpy(tag[nt], "OutputListOn");
      addr[nt] = &All.OutputListOn;
      id[nt++] = INT;

      strcpy(tag[nt], "Omega0");
      addr[nt] = &All.Omega0;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "OmegaBaryon");
      addr[nt] = &All.OmegaBaryon;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "OmegaLambda");
      addr[nt] = &All.OmegaLambda;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "HubbleParam");
      addr[nt] = &All.HubbleParam;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "BoxSize");
      addr[nt] = &All.BoxSize;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "PeriodicBoundariesOn");
      addr[nt] = &All.PeriodicBoundariesOn;
      id[nt++] = INT;

      strcpy(tag[nt], "TimeOfFirstSnapshot");
      addr[nt] = &All.TimeOfFirstSnapshot;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "CpuTimeBetRestartFile");
      addr[nt] = &All.CpuTimeBetRestartFile;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "TimeBetStatistics");
      addr[nt] = &All.TimeBetStatistics;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "TimeBegin");
      addr[nt] = &All.TimeBegin;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "TimeMax");
      addr[nt] = &All.TimeMax;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "TimeBetSnapshot");
      addr[nt] = &All.TimeBetSnapshot;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "UnitVelocity_in_cm_per_s");
      addr[nt] = &All.UnitVelocity_in_cm_per_s;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "UnitLength_in_cm");
      addr[nt] = &All.UnitLength_in_cm;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "UnitMass_in_g");
      addr[nt] = &All.UnitMass_in_g;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "TreeDomainUpdateFrequency");
      addr[nt] = &All.TreeDomainUpdateFrequency;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "ErrTolIntAccuracy");
      addr[nt] = &All.ErrTolIntAccuracy;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "ErrTolTheta");
      addr[nt] = &All.ErrTolTheta;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "ErrTolForceAcc");
      addr[nt] = &All.ErrTolForceAcc;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "MinGasHsmlFractional");
      addr[nt] = &All.MinGasHsmlFractional;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "MaxSizeTimestep");
      addr[nt] = &All.MaxSizeTimestep;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "MinSizeTimestep");
      addr[nt] = &All.MinSizeTimestep;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "MaxRMSDisplacementFac");
      addr[nt] = &All.MaxRMSDisplacementFac;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "ArtBulkViscConst");
      addr[nt] = &All.ArtBulkViscConst;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "CourantFac");
      addr[nt] = &All.CourantFac;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "DesNumNgb");
      addr[nt] = &All.DesNumNgb;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "MaxNumNgbDeviation");
      addr[nt] = &All.MaxNumNgbDeviation;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "ComovingIntegrationOn");
      addr[nt] = &All.ComovingIntegrationOn;
      id[nt++] = INT;

      strcpy(tag[nt], "ICFormat");
      addr[nt] = &All.ICFormat;
      id[nt++] = INT;

      strcpy(tag[nt], "SnapFormat");
      addr[nt] = &All.SnapFormat;
      id[nt++] = INT;

      strcpy(tag[nt], "NumFilesPerSnapshot");
      addr[nt] = &All.NumFilesPerSnapshot;
      id[nt++] = INT;

      strcpy(tag[nt], "NumFilesWrittenInParallel");
      addr[nt] = &All.NumFilesWrittenInParallel;
      id[nt++] = INT;

      strcpy(tag[nt], "ResubmitOn");
      addr[nt] = &All.ResubmitOn;
      id[nt++] = INT;

      strcpy(tag[nt], "TypeOfTimestepCriterion");
      addr[nt] = &All.TypeOfTimestepCriterion;
      id[nt++] = INT;

      strcpy(tag[nt], "TypeOfOpeningCriterion");
      addr[nt] = &All.TypeOfOpeningCriterion;
      id[nt++] = INT;

      strcpy(tag[nt], "TimeLimitCPU");
      addr[nt] = &All.TimeLimitCPU;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "SofteningHalo");
      addr[nt] = &All.SofteningHalo;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "SofteningDisk");
      addr[nt] = &All.SofteningDisk;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "SofteningBulge");
      addr[nt] = &All.SofteningBulge;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "SofteningGas");
      addr[nt] = &All.SofteningGas;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "SofteningStars");
      addr[nt] = &All.SofteningStars;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "SofteningBndry");
      addr[nt] = &All.SofteningBndry;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "SofteningHaloMaxPhys");
      addr[nt] = &All.SofteningHaloMaxPhys;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "SofteningDiskMaxPhys");
      addr[nt] = &All.SofteningDiskMaxPhys;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "SofteningBulgeMaxPhys");
      addr[nt] = &All.SofteningBulgeMaxPhys;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "SofteningGasMaxPhys");
      addr[nt] = &All.SofteningGasMaxPhys;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "SofteningStarsMaxPhys");
      addr[nt] = &All.SofteningStarsMaxPhys;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "SofteningBndryMaxPhys");
      addr[nt] = &All.SofteningBndryMaxPhys;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "BufferSize");
      addr[nt] = &All.BufferSize;
      id[nt++] = INT;

      strcpy(tag[nt], "PartAllocFactor");
      addr[nt] = &All.PartAllocFactor;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "TreeAllocFactor");
      addr[nt] = &All.TreeAllocFactor;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "GravityConstantInternal");
      addr[nt] = &All.GravityConstantInternal;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "InitGasTemp");
      addr[nt] = &All.InitGasTemp;
      id[nt++] = DOUBLE;

      strcpy(tag[nt], "MinGasTemp");
      addr[nt] = &All.MinGasTemp;
      id[nt++] = DOUBLE;

      if((fd = fopen(fname, "r")))
	{
	  sprintf(buf, "%s%s", fname, "-usedvalues");
	  if(!(fdout = fopen(buf, "w")))
	    {
	      printf("error opening file '%s' \n", buf);
	      errorFlag = 1;
	    }
	  else
	    {
	      while(!feof(fd))
		{
		  *buf = 0;
		  fgets(buf, 200, fd);
		  if(sscanf(buf, "%s%s%s", buf1, buf2, buf3) < 2)
		    continue;

		  if(buf1[0] == '%')
		    continue;

		  for(i = 0, j = -1; i < nt; i++)
		    if(strcmp(buf1, tag[i]) == 0)
		      {
			j = i;
			tag[i][0] = 0;
			break;
		      }

		  if(j >= 0)
		    {
		      switch (id[j])
			{
			case DOUBLE:
			  *((double *) addr[j]) = atof(buf2);
			  fprintf(fdout, "%-35s%g\n", buf1, *((double *) addr[j]));
			  break;
			case STRING:
			  strcpy(addr[j], buf2);
			  fprintf(fdout, "%-35s%s\n", buf1, buf2);
			  break;
			case INT:
			  *((int *) addr[j]) = atoi(buf2);
			  fprintf(fdout, "%-35s%d\n", buf1, *((int *) addr[j]));
			  break;
			}
		    }
		  else
		    {
		      fprintf(stdout, "Error in file %s:   Tag '%s' not allowed or multiple defined.\n",
			      fname, buf1);
		      errorFlag = 1;
		    }
		}
	      fclose(fd);
	      fclose(fdout);

	      i = strlen(All.OutputDir);
	      if(i > 0)
		if(All.OutputDir[i - 1] != '/')
		  strcat(All.OutputDir, "/");

	      sprintf(buf1, "%s%s", fname, "-usedvalues");
	      sprintf(buf2, "%s%s", All.OutputDir, "parameters-usedvalues");
	      sprintf(buf3, "cp %s %s", buf1, buf2);
	      system(buf3);
	    }
	}
      else
	{
	  printf("\nParameter file %s not found.\n\n", fname);
	  errorFlag = 2;
	}

      if(errorFlag != 2)
	for(i = 0; i < nt; i++)
	  {
	    if(*tag[i])
	      {
		printf("Error. I miss a value for tag '%s' in parameter file '%s'.\n", tag[i], fname);
		errorFlag = 1;
	      }
	  }

      if(All.OutputListOn && errorFlag == 0)
	errorFlag += read_outputlist(All.OutputListFilename);
      else
	All.OutputListLength = 0;
    }
#ifndef NOMPI
  MPI_Bcast(&errorFlag, 1, MPI_INT, 0, GADGET_WORLD);
#endif
  if(errorFlag)
    {
#ifndef NOMPI
      MPI_Finalize();
#endif
      exit(0);
    }

  /* now communicate the relevant parameters to the other processes */
#ifndef NOMPI
  MPI_Bcast(&All, sizeof(struct global_data_all_processes), MPI_BYTE, 0, GADGET_WORLD);
#endif

  if(All.NumFilesWrittenInParallel < 1)
    {
      if(ThisTask == 0)
	printf("NumFilesWrittenInParallel MUST be at least 1\n");
      endrun(0);
    }

  if(All.NumFilesWrittenInParallel > NTask)
    {
      if(ThisTask == 0)
	printf("NumFilesWrittenInParallel MUST be smaller than number of processors\n");
      endrun(0);
    }

#ifdef PERIODIC
  if(All.PeriodicBoundariesOn == 0)
    {
      if(ThisTask == 0)
	{
	  printf("Code was compiled with periodic boundary conditions switched on.\n");
	  printf("You must set `PeriodicBoundariesOn=1', or recompile the code.\n");
	}
      endrun(0);
    }
#else
  if(All.PeriodicBoundariesOn == 1)
    {
      if(ThisTask == 0)
	{
	  printf("Code was compiled with periodic boundary conditions switched off.\n");
	  printf("You must set `PeriodicBoundariesOn=0', or recompile the code.\n");
	}
      endrun(0);
    }
#endif

  if(All.TypeOfTimestepCriterion >= 1)
    {
      if(ThisTask == 0)
	{
	  printf("The specified timestep criterion\n");
	  printf("is not valid\n");
	}
      endrun(0);
    }

#if defined(LONG_X) ||  defined(LONG_Y) || defined(LONG_Z)
#ifndef NOGRAVITY
  if(ThisTask == 0)
    {
      printf("Code was compiled with LONG_X/Y/Z, but not with NOGRAVITY.\n");
      printf("Stretched periodic boxes are not implemented for gravity yet.\n");
    }
  endrun(0);
#endif
#endif

#undef DOUBLE
#undef STRING
#undef INT
#undef MAXTAGS
}

/*! this function reads a table with a list of desired output times. The
 *  table does not have to be ordered in any way, but may not contain more
 *  than MAXLEN_OUTPUTLIST entries.
 */
int read_outputlist(char *fname)
{
  FILE *fd;

  if(!(fd = fopen(fname, "r")))
    {
      printf("can't read output list in file '%s'\n", fname);
      return 1;
    }

  All.OutputListLength = 0;
  do
    {
      if(fscanf(fd, " %lg ", &All.OutputListTimes[All.OutputListLength]) == 1)
	All.OutputListLength++;
      else
	break;
    }
  while(All.OutputListLength < MAXLEN_OUTPUTLIST);

  fclose(fd);

  printf("\nfound %d times in output-list.\n", All.OutputListLength);

  return 0;
}

/*! If a restart from restart-files is carried out where the TimeMax
 *  variable is increased, then the integer timeline needs to be
 *  adjusted. The approach taken here is to reduce the resolution of the
 *  integer timeline by factors of 2 until the new final time can be
 *  reached within TIMEBASE.
 */
void readjust_timebase(double TimeMax_old, double TimeMax_new)
{
  int i;
  long long ti_end;

  if(ThisTask == 0)
    {
      printf("\nAll.TimeMax has been changed in the parameterfile\n");
      printf("Need to adjust integer timeline\n\n\n");
    }

  if(TimeMax_new < TimeMax_old)
    {
      if(ThisTask == 0)
	printf("\nIt is not allowed to reduce All.TimeMax\n\n");
      endrun(556);
    }

  if(All.ComovingIntegrationOn)
    ti_end = log(TimeMax_new / All.TimeBegin) / All.Timebase_interval;
  else
    ti_end = (TimeMax_new - All.TimeBegin) / All.Timebase_interval;

  while(ti_end > TIMEBASE)
    {
      All.Timebase_interval *= 2.0;

      ti_end /= 2;
      All.Ti_Current /= 2;

#ifdef PMGRID
      All.PM_Ti_begstep /= 2;
      All.PM_Ti_endstep /= 2;
#endif

      for(i = 0; i < NumPart; i++)
	{
	  P[i].Ti_begstep /= 2;
	  P[i].Ti_endstep /= 2;
	}
    }

  All.TimeMax = TimeMax_new;
}


#ifndef UTILSH
#define UTILSH

#include <gsl/gsl_vector.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_sf.h>
#include <gsl/gsl_permutation.h>
#include <math.h>
#include <time.h>
#include <assert.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdarg.h>

time_t TIME;
char line1[80];

void outlog(char* fmt, ...);

typedef struct int_vector
{
    int size;
    int* val;
} int_vector;

int_vector* new_int_vector(int size);
void delete_int_vector(int_vector* iv);

void ivappend(int_vector* v, int val);

static inline int ivget(int_vector* v, int n)
{ return(v->val[n]); };

static inline void ivset(int_vector* v, int n, int val)
{ v->val[n] = val; };

static inline void vset(gsl_vector* v, int i, double x)
{ gsl_vector_set(v, i, x); };

static inline double vget(const gsl_vector* v, int i)
{ return(gsl_vector_get(v, i)); };

static inline void vinc(gsl_vector* v, int i, double x)
{ vset(v, i, vget(v, i) + x); };

static inline double log_sum(double log_a, double log_b)
{
  if (log_a < log_b)
      return(log_b+log(1 + exp(log_a-log_b)));
  else
      return(log_a+log(1 + exp(log_b-log_a)));
};

static inline double lgam(double x)
{ return(gsl_sf_lngamma(x)); }

double runif();

double rgauss(double mean, double stdev);

int sample_from_log(gsl_vector* log_prob);

void init_random_number_generator();

void vct_fscanf(const char* filename, gsl_vector* v);

void iv_permute(int_vector* iv);
void iv_permute_from_perm(int_vector* iv, gsl_permutation* p);
int_vector* iv_copy(int_vector* iv);

double sum(gsl_vector* v);

void print_vector(gsl_vector* v);

gsl_permutation* rpermutation(int size);

void write_vect(gsl_vector* vect, char* name, FILE* file);

gsl_vector* read_vect(char* name, int size, FILE* file);

int read_int(char* name, FILE* file);

void write_int(int x, char* name, FILE* file);

void write_double(double x, char* name, FILE* file);

double read_double(char* name, FILE* file);

int directory_exist(const char *dname);

void make_directory(char* name);

int rbernoulli(double p);

double rbeta(double a,
             double b);

double rgamma(double shape,
              double scale);

double log_dgamma(double x,
              double shape,
              double scale);

void resize(gsl_vector * vec, size_t newsize);

#endif


#pragma once

#include <glm/glm.hpp>
#include <gsl/gsl>
#include <optional>

namespace rev {

struct Segment {
    glm::vec3 start;
    glm::vec3 end;
};

struct Ray {
    glm::vec3 origin;
    glm::vec3 direction;
};

struct Sphere {
    glm::vec3 center;
    float radius;
};

template <typename VertexRange, typename SegmentVisitor>
void iteratePolygonVertices(const VertexRange& vertices, SegmentVisitor&& visitor)
{
    Expects(vertices.size() >= 3);
    auto begin = std::begin(vertices);
    auto end = std::end(vertices);

    auto segmentStart = begin;
    auto segmentEnd = begin + 1;

    while (segmentStart != end) {
        if (segmentEnd == end) {
            segmentEnd = begin;
        }

        visitor(Segment{ *segmentStart, *segmentEnd });
        segmentStart++;
        segmentEnd++;
    }
}

class NullPolygonBuilder {
public:
    void addVertex(const glm::vec3& vertex) {}
};

template <typename IteratorType>
class OutputIteratorPolygonBuilder {
public:
    OutputIteratorPolygonBuilder(IteratorType iterator)
        : _iter(iterator)
    {
    }

    void addVertex(const glm::vec3& vertex)
    {
        *_iter = vertex;
        _iter++;
    }

    IteratorType getIterator() const { return _iter; }

private:
    IteratorType _iter;
};

struct AxisAlignedPlane {
    struct Hit {
        glm::vec3 intersectionPoint;
        float t;
    };

    Hit intersect(const Ray& ray) const
    {
        float boundaryDelta = boundary - ray.origin[dimensionIndex];
        float t = boundaryDelta / ray.direction[dimensionIndex];

        glm::vec3 intersectPoint = ray.origin + (t * ray.direction);

        // Avoid floating point error along the boundary dimension.
        intersectPoint[dimensionIndex] = boundary;
        return { intersectPoint, t };
    }

    glm::vec3 intersect(const Segment& segment) const
    {
        glm::vec3 direction = segment.end - segment.start;
        auto [intersectionPoint, t] = intersect(Ray{ segment.start, direction });
        if (!(t > 0.0f)) {
            return segment.start;
        } else if (!(t < 1.0f)) {
            return segment.end;
        }

        for (size_t k = 0; k < 3; k++) {
            if (segment.start[k] == segment.end[k]) {
                intersectionPoint[k] = segment.start[k];
            }
        }
        return intersectionPoint;
    }

    template <typename InputVertexRange, typename LeftBuilder, typename RightBuilder>
    void splitConvexPolygon(const InputVertexRange& inputVertices, LeftBuilder&& leftBuilder,
        RightBuilder&& rightBuilder) const
    {
        iteratePolygonVertices(
            inputVertices, [this, &leftBuilder, &rightBuilder](const Segment& segment) {
                float startPosition = segment.start[dimensionIndex];
                float endPosition = segment.end[dimensionIndex];

                bool emitIntersection = false;
                if (startPosition < boundary) {
                    leftBuilder.addVertex(segment.start);
                    if (endPosition > boundary) {
                        emitIntersection = true;
                    }
                } else if (startPosition > boundary) {
                    rightBuilder.addVertex(segment.start);
                    if (endPosition < boundary) {
                        emitIntersection = true;
                    }
                } else {
                    // Right on the boundary.
                    leftBuilder.addVertex(segment.start);
                    rightBuilder.addVertex(segment.start);
                }

                if (emitIntersection) {
                    auto intersection = intersect(segment);
                    leftBuilder.addVertex(intersection);
                    rightBuilder.addVertex(intersection);
                }
            });
    }

    uint8_t dimensionIndex;
    float boundary;
};

struct AxisAlignedBoundingBox {
    void expandToVertex(const glm::vec3& vertex)
    {
        for (int k = 0; k < 3; k++) {
            if (vertex[k] < minimum[k]) {
                minimum[k] = vertex[k];
            }
            if (vertex[k] > maximum[k]) {
                maximum[k] = vertex[k];
            }
        }
    }

    void expandToBox(const AxisAlignedBoundingBox& other)
    {
        for (int k = 0; k < 3; k++) {
            if (other.minimum[k] < minimum[k]) {
                minimum[k] = other.minimum[k];
            }
            if (other.maximum[k] > maximum[k]) {
                maximum[k] = other.maximum[k];
            }
        }
    }

    void reduceToBox(const AxisAlignedBoundingBox& other)
    {
        for (int k = 0; k < 3; k++) {
            if (other.minimum[k] > minimum[k]) {
                minimum[k] = other.minimum[k];
            }
            if (other.maximum[k] < maximum[k]) {
                maximum[k] = other.maximum[k];
            }
            Expects(!(minimum[k] > maximum[k]));
        }
    }

    float getSurfaceArea() const
    {
        glm::vec3 diagonal = maximum - minimum;
        float surfaceArea = 0.0f;
        surfaceArea += diagonal.x * diagonal.y;
        surfaceArea += diagonal.y * diagonal.z;
        surfaceArea += diagonal.z * diagonal.z;

        return 2.0f * surfaceArea;
    }

    float getVolume() const
    {
        glm::vec3 diagonal = maximum - minimum;
        return diagonal.x * diagonal.y * diagonal.z;
    }

    std::array<AxisAlignedBoundingBox, 2> split(const AxisAlignedPlane& plane) const
    {
        Expects(!(plane.boundary < minimum[plane.dimensionIndex]));
        Expects(!(plane.boundary > maximum[plane.dimensionIndex]));

        std::array<AxisAlignedBoundingBox, 2> splitBoxes{ *this, *this };
        splitBoxes[0].maximum[plane.dimensionIndex] = plane.boundary;
        splitBoxes[1].minimum[plane.dimensionIndex] = plane.boundary;

        return splitBoxes;
    }

    struct Hit {
        glm::vec3 intersectionPoint;
        float t;
        uint8_t planeDimension;
    };

    bool containsPoint(const glm::vec3& point) const
    {
        for (int k = 0; k < 3; k++) {
            if (point[k] < minimum[k]) {
                return false;
            }
            if (point[k] > maximum[k]) {
                return false;
            }
        }

        return true;
    }

    bool intersectsSphere(const Sphere& sphere) const
    {
        float r2 = sphere.radius * sphere.radius;
        for (size_t k = 0; k < 3; k++) {
            if (sphere.center[k] < minimum[k]) {
                float diff = minimum[k] - sphere.center[k];
                r2 -= diff * diff;
            } else if (sphere.center[k] > maximum[k]) {
                float diff = sphere.center[k] - maximum[k];
                r2 -= diff * diff;
            }
        }
        return r2 > 0.0f;
    }

    std::optional<Hit> castExternalRay(const Ray& ray) const
    {
        for (uint8_t k = 0; k < 3; k++) {
            float d = ray.direction[k];
            if (d < 0.0f) {
                float boundary = maximum[k];
                if (ray.origin[k] > boundary) {
                    auto intersection = AxisAlignedPlane{ k, boundary }.intersect(ray);
                    if (containsPoint(intersection.intersectionPoint)) {
                        return Hit{ intersection.intersectionPoint, intersection.t, k };
                    }
                }
            } else if (d > 0.0f) {
                float boundary = minimum[k];
                if (ray.origin[k] < boundary) {
                    auto intersection = AxisAlignedPlane{ k, boundary }.intersect(ray);
                    if (containsPoint(intersection.intersectionPoint)) {
                        return Hit{ intersection.intersectionPoint, intersection.t, k };
                    }
                }
            }
        }
        return std::nullopt;
    }

    Hit castInternalRay(const Ray& ray) const
    {
        for (uint8_t k = 0; k < 3; k++) {
            float d = ray.direction[k];

            float boundary;
            if (d < 0.0f) {
                boundary = minimum[k];
            } else if (d > 0.0f) {
                boundary = maximum[k];
            } else {
                continue;
            }

            auto intersection = AxisAlignedPlane{ k, boundary }.intersect(ray);
            if (containsPoint(intersection.intersectionPoint)) {
                return Hit{ intersection.intersectionPoint, intersection.t, k };
            }
        }
        throw std::runtime_error("Internal ray intersection not found.");
    }

    glm::vec3 minimum{ std::numeric_limits<float>::infinity() };
    glm::vec3 maximum{ -std::numeric_limits<float>::infinity() };
};

template <typename VertexRange>
AxisAlignedBoundingBox smallestBoxContainingVertices(const VertexRange& range)
{
    AxisAlignedBoundingBox box;
    for (const auto& vertex : range) {
        box.expandToVertex(vertex);
    }
    return box;
}

}


#include <stdio.h>
#include <stdlib.h>
#include <image_lib.h>
#include <gsl/gsl_fit.h>
#include "tz_darray.h"
#include "tz_stack_io.h"
#include "tz_stack_stat.h"
#include "tz_image_lib.h"
#include "tz_stack_threshold.h"
#include "tz_fimage_lib.h"
#include "tz_error.h"

#include "private/alignstack.c"

int main(int argc, const char *argv[])
{
  const char *file1 = "/Users/zhaot/Data/nathan/2008-04-18/tiletest_R1_GR1_B1_L312.tif";
  const char *file2 = "/Users/zhaot/Data/nathan/2008-04-18/tiletest_R1_GR1_B1_L334.tif";

  //const char *file1 = "/Volumes/Mac HD 2/JF8904-PRV/11/11_1.tif";
  //const char *file2 = "/Volumes/Mac HD 2/JF8904-PRV/11/11_2.tif";

  if (argc == 3) {
    file1 = argv[1];
    file2 = argv[2];
  }

  Stack *stack1 = Read_Stack((char *) file1);
  Stack *stack2 = Read_Stack((char *) file2);

#if 0
  FMatrix *filter = Mexihat_3D_F(3, NULL);
  //FMatrix_Negative(filter);

  //double sigma[] = {2.0, 2.0, 3.0};
  //FMatrix *filter = Gaussian_3D_Filter_F(sigma, NULL);

  FMatrix *out = Filter_Stack_Fast_F(stack1, filter, NULL, 0);
  Kill_Stack(stack1);

  dim_type sub[3];
  printf("%g\n", FMatrix_Max(out, sub));

  stack1 = Scale_Float_Stack(out->array, out->dim[0], out->dim[1], out->dim[2],
			      GREY);
  Kill_FMatrix(out);
  //Stack_Threshold_RC(stack1, 0, 65535);

  out = Filter_Stack_Fast_F(stack2, filter, NULL, 0);
  Kill_Stack(stack2);
  stack2 = Scale_Float_Stack(out->array, out->dim[0], out->dim[1], out->dim[2],
			      GREY);
  Kill_FMatrix(out);
  //Stack_Threshold_RC(stack2, 0, 65535);
#endif

#if 1
  int chopoff1 = estimate_chopoff(stack1);
  printf("%d\n", chopoff1);
  Stack *substack1 = Crop_Stack(stack1, 0, 0, chopoff1,
				stack1->width, stack1->height,
				stack1->depth - chopoff1, NULL);
  int chopoff2 = estimate_chopoff(stack2);
  printf("%d\n", chopoff2);
  Stack *substack2 = Crop_Stack(stack2, 0, 0, chopoff2,
				stack2->width, stack2->height,
				stack2->depth - chopoff2, NULL);
#endif

#if 0
  Stack_Threshold_Common(substack1, 0, 65535);
  Stack_Threshold_RC(substack1, 0, 65535);
  Stack_Threshold_Common(substack2, 0, 65535);
  Stack_Threshold_RC(substack2, 0, 65535);
#endif

#if 0
  Stack_Threshold_RC(substack1, 1000, 65535);
  Stack_Threshold_RC(substack2, 1000, 65535);
#endif

  tic();
  //Write_Stack("../data/test.tif", substack1);

  float unnorm_maxcorr;
  int offset[3];
  //float score = Align_Stack_F(substack1, substack2, offset, &unnorm_maxcorr);

  int intv[] = {3, 3, 3};

  float score = Align_Stack_MR_F(substack1, substack2, intv, 1, offset,
				 &unnorm_maxcorr);

  printf("%d %d %d\n", offset[0] - stack1->width + 1, offset[1]  - stack1->height + 1, offset[2] - stack1->depth + 1);

  //printf("fixed (RC_common)\n");
  printf("%lld\n", toc());

  Kill_Stack(stack1);
  Kill_Stack(stack2);

  return 0;
}


/******************************************************************************

  (c) 2017 - 2019 Scientific Computation Research Center,
      Rensselaer Polytechnic Institute. All rights reserved.

  This work is open source software, licensed under the terms of the
  BSD license as described in the LICENSE file in the top-level directory.

*******************************************************************************/
#ifndef MSI_PETSC_H
#define MSI_PETSC_H
#include "msi.h"
#include "msi_solver.h"
#include <pumi.h>
#include <petsc.h>
#include <map>
#include <vector>
// Added for the synchronization function
// TODO : make it so these are not necessary
#include "apfFieldData.h"
#include "apfNew.h"
#include "apfNumbering.h"
#include "apfNumberingClass.h"
#include "apfShape.h"
int copyField2PetscVec(pField f, Vec& petscVec);
int copyPetscVec2Field(Vec& petscVec, pField f, bool);
void printMemStat( );
// NOTE: all field related interaction is done through msi api rather than apf
class msi_matrix
{
  public:
  msi_matrix(pField f);
  virtual ~msi_matrix( );
  virtual int initialize( ) = 0;  // create a matrix and solver object
  int destroy( );                 // delete a matrix and solver object
  int set_value(msi_int row,
                msi_int col,
                int operation,
                double real_val,
                double imag_val);  // insertion/addition with global numbering
  // values use column-wise, size * size block
  int add_values(msi_int rsize,
                 msi_int* rows,
                 msi_int csize,
                 msi_int* columns,
                 double* values);
  int get_values(std::vector<msi_int>& rows,
                 std::vector<msi_int>& n_columns,
                 std::vector<msi_int>& columns,
                 std::vector<double>& values);
  void set_status(int s) { mat_status = s; }
  int get_status( ) { return mat_status; }
  int get_scalar_type( )
  {
#ifdef MSI_COMPLEX
    return 1;
#else
    return 0;
#endif
  }
  pField get_field( ) { return field; }
  int write(const char* file_name);
  virtual int get_type( ) const = 0;
  virtual int assemble( ) = 0;
  virtual int setupMat( ) = 0;
  virtual int preAllocate( ) = 0;
  virtual int flushAssembly( );
  int printInfo( );
  // PETSc data structures
  Mat* A;
  protected:
  int setupSeqMat( );
  int setupParaMat( );
  int preAllocateSeqMat( );
  int preAllocateParaMat( );
  int mat_status;
  pField field;  // the field that provide numbering
};
class matrix_mult : public msi_matrix
{
  public:
  matrix_mult(pField f) : msi_matrix(f), localMat(1) { initialize( ); }
  virtual int initialize( );
  void set_mat_local(bool flag) { localMat = flag; }
  int is_mat_local( ) { return localMat; }
  int multiply(pField in_f, pField out_f, bool sync = true);
  virtual int get_type( ) const { return 0; }  // MSI_MULTIPLY; }
  virtual int assemble( );
  virtual int setupMat( );
  virtual int preAllocate( );
  private:
  bool localMat;
};
class matrix_solve : public msi_matrix
{
  public:
  matrix_solve(pField f);
  virtual int initialize( );
  virtual ~matrix_solve( );
  int solve(pField rhs, pField sol, bool sync = true);
  int set_bc(msi_int row);
  int set_row(msi_int row, msi_int numVals, msi_int* colums, double* vals);
  int add_blockvalues(msi_int rbsize,
                      msi_int* rows,
                      msi_int cbsize,
                      msi_int* columns,
                      double* values);
  virtual int get_type( ) const { return 1; }
  virtual int assemble( );
  virtual int setupMat( );
  virtual int preAllocate( );
  int iterNum;
  private:
  int setUpRemoteAStruct( );
  int setUpRemoteAStructParaMat( );
  int setKspType( );
  int kspSet;
  KSP* ksp;
  Mat remoteA;
  std::set<int> remotePidOwned;
  std::map<int, std::map<int, int> >
    remoteNodeRow;  // <pid, <locnode>, numAdj >
  std::map<int, int> remoteNodeRowSize;
};
#endif


/**
 *
 * @file core_dssssm.c
 *
 *  PLASMA core_blas kernel
 *  PLASMA is a software package provided by Univ. of Tennessee,
 *  Univ. of California Berkeley and Univ. of Colorado Denver
 *
 * @version 2.6.0
 * @author Hatem Ltaief
 * @author Mathieu Faverge
 * @author Jakub Kurzak
 * @date 2010-11-15
 * @generated d Tue Jan  7 11:44:45 2014
 *
 **/
#include <cblas.h>
#include "common.h"

/***************************************************************************//**
 *
 * @ingroup CORE_double
 *
 *  CORE_dssssm applies the LU factorization update from a complex
 *  matrix formed by a lower triangular IB-by-K tile L1 on top of a
 *  M2-by-K tile L2 to a second complex matrix formed by a M1-by-N1
 *  tile A1 on top of a M2-by-N2 tile A2 (N1 == N2).
 *
 *  This is the right-looking Level 2.5 BLAS version of the algorithm.
 *
 *******************************************************************************
 *
 * @param[in] M1
 *         The number of rows of the tile A1.  M1 >= 0.
 *
 * @param[in] N1
 *         The number of columns of the tile A1.  N1 >= 0.
 *
 * @param[in] M2
 *         The number of rows of the tile A2 and of the tile L2.
 *         M2 >= 0.
 *
 * @param[in] N2
 *         The number of columns of the tile A2.  N2 >= 0.
 *
 * @param[in] K
 *         The number of columns of the tiles L1 and L2.  K >= 0.
 *
 * @param[in] IB
 *         The inner-blocking size.  IB >= 0.
 *
 * @param[in,out] A1
 *         On entry, the M1-by-N1 tile A1.
 *         On exit, A1 is updated by the application of L (L1 L2).
 *
 * @param[in] LDA1
 *         The leading dimension of the array A1.  LDA1 >= max(1,M1).
 *
 * @param[in,out] A2
 *         On entry, the M2-by-N2 tile A2.
 *         On exit, A2 is updated by the application of L (L1 L2).
 *
 * @param[in] LDA2
 *         The leading dimension of the array A2.  LDA2 >= max(1,M2).
 *
 * @param[in] L1
 *         The IB-by-K lower triangular tile as returned by
 *         CORE_dtstrf.
 *
 * @param[in] LDL1
 *         The leading dimension of the array L1.  LDL1 >= max(1,IB).
 *
 * @param[in] L2
 *         The M2-by-K tile as returned by CORE_dtstrf.
 *
 * @param[in] LDL2
 *         The leading dimension of the array L2.  LDL2 >= max(1,M2).
 *
 * @param[in] IPIV
 *         The pivot indices array of size K as returned by
 *         CORE_dtstrf.
 *
 *******************************************************************************
 *
 * @return
 *         \retval PLASMA_SUCCESS successful exit
 *         \retval <0 if INFO = -k, the k-th argument had an illegal value
 *
 ******************************************************************************/
#if defined(PLASMA_HAVE_WEAK)
#pragma weak CORE_dssssm = PCORE_dssssm
#define CORE_dssssm PCORE_dssssm
#endif
int CORE_dssssm(int M1, int N1, int M2, int N2, int K, int IB,
                double *A1, int LDA1,
                double *A2, int LDA2,
                const double *L1, int LDL1,
                const double *L2, int LDL2,
                const int *IPIV)
{
    static double zone  = 1.0;
    static double mzone =-1.0;

    int i, ii, sb;
    int im, ip;

    /* Check input arguments */
    if (M1 < 0) {
        coreblas_error(1, "Illegal value of M1");
        return -1;
    }
    if (N1 < 0) {
        coreblas_error(2, "Illegal value of N1");
        return -2;
    }
    if (M2 < 0) {
        coreblas_error(3, "Illegal value of M2");
        return -3;
    }
    if (N2 < 0) {
        coreblas_error(4, "Illegal value of N2");
        return -4;
    }
    if (K < 0) {
        coreblas_error(5, "Illegal value of K");
        return -5;
    }
    if (IB < 0) {
        coreblas_error(6, "Illegal value of IB");
        return -6;
    }
    if (LDA1 < max(1,M1)) {
        coreblas_error(8, "Illegal value of LDA1");
        return -8;
    }
    if (LDA2 < max(1,M2)) {
        coreblas_error(10, "Illegal value of LDA2");
        return -10;
    }
    if (LDL1 < max(1,IB)) {
        coreblas_error(12, "Illegal value of LDL1");
        return -12;
    }
    if (LDL2 < max(1,M2)) {
        coreblas_error(14, "Illegal value of LDL2");
        return -14;
    }

    /* Quick return */
    if ((M1 == 0) || (N1 == 0) || (M2 == 0) || (N2 == 0) || (K == 0) || (IB == 0))
        return PLASMA_SUCCESS;

    ip = 0;

    for(ii = 0; ii < K; ii += IB) {
        sb = min(K-ii, IB);

        for(i = 0; i < sb; i++) {
            im = IPIV[ip]-1;

            if (im != (ii+i)) {
                im = im - M1;
                cblas_dswap(N1, &A1[ii+i], LDA1, &A2[im], LDA2);
            }
            ip = ip + 1;
        }

        cblas_dtrsm(
            CblasColMajor, CblasLeft, CblasLower,
            CblasNoTrans, CblasUnit,
            sb, N1, (zone),
            &L1[LDL1*ii], LDL1,
            &A1[ii], LDA1);

        cblas_dgemm(
            CblasColMajor, CblasNoTrans, CblasNoTrans,
            M2, N2, sb,
            (mzone), &L2[LDL2*ii], LDL2,
            &A1[ii], LDA1,
            (zone), A2, LDA2);
    }
    return PLASMA_SUCCESS;
}


#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <gsl/gsl_math.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "allvars.h"
#include "proto.h"

#ifdef OUTPUTLINEOFSIGHT

#ifdef OUTPUTLINEOFSIGHT_SPECTRUM
#define  PIXELS 512		/* number of bins along line of sight */
#else
#define  PIXELS 1
#endif

#define  N_LOS  10		/* number of lines of sight selected  */

static double H_a, Wmax;

struct line_of_sight
{
  int xaxis, yaxis, zaxis;
  double Xpos, Ypos;		/* relative position of line-of-sight on face of box */
  double BoxSize, Wmax, Time;

  /* total gas density */
  double Rho[PIXELS];
  double Vpec[PIXELS];
  double Temp[PIXELS];
  double Metallicity[PIXELS];

  /* neutral hydrogen */
  double RhoHI[PIXELS];
  double NHI[PIXELS];
  double VpecHI[PIXELS];
  double TempHI[PIXELS];
  double TauHI[PIXELS];

  /* HeII quantities */
  double RhoHeII[PIXELS];
  double NHeII[PIXELS];
  double VpecHeII[PIXELS];
  double TempHeII[PIXELS];
  double TauHeII[PIXELS];
}
 *Los, *LosGlobal;

struct line_of_sight_particles
{
  MyFloat Pos[3];
  MyFloat Hsml;
  MyFloat Vz;
  MyFloat Utherm;
  MyFloat Mass;
  MyFloat Metallicity;
}
 *particles;

void lineofsight_output(void)
{
  char buf[500];
  int n, s, next;
  double ti;

  next = find_next_lineofsighttime(All.Ti_nextlineofsight);

  ti = All.TimeBegin * exp(next * All.Timebase_interval);

  if(ThisTask == 0)
    {
      printf("Line of sight output! ThisTask=%d Time=%g  NextTime=%g\n", ThisTask, All.Time, ti);
      fflush(stdout);
    }

  H_a = hubble_function(All.Time);
  Wmax = All.Time * H_a * All.BoxSize;

  if(ThisTask == 0)
    {
      sprintf(buf, "%s/los", All.OutputDir);
      mkdir(buf, 02755);
    }

  Los = mymalloc(sizeof(struct line_of_sight));
  LosGlobal = mymalloc(sizeof(struct line_of_sight));

  for(n = 0, s = 0; n < N_LOS; n++)
    {
      if(s + 3 >= RNDTABLE)
	{
	  set_random_numbers();
	  s = 0;
	}

      Los->zaxis = (int) (3.0 * get_random_number(s++));
      switch (Los->zaxis)
	{
	case 2:
	  Los->xaxis = 0;
	  Los->yaxis = 1;
	  break;
	case 0:
	  Los->xaxis = 1;
	  Los->yaxis = 2;
	  break;
	case 1:
	  Los->xaxis = 2;
	  Los->yaxis = 0;
	  break;
	}

      Los->Xpos = All.BoxSize * get_random_number(s++);
      Los->Ypos = All.BoxSize * get_random_number(s++);

#ifdef OUTPUTLINEOFSIGHT_SPECTRUM
      add_along_lines_of_sight();
      sum_over_processors_and_normalize();
      absorb_along_lines_of_sight();
      output_lines_of_sight(n);
#endif

#ifdef OUTPUTLINEOFSIGHT_PARTICLES
      find_particles_and_save_them(n);
#endif
    }

  myfree(LosGlobal);
  myfree(Los);
}

void find_particles_and_save_them(int num)
{
  int n, k, count_local, *countlist, counttot, rep;
  double a3inv;
  MyFloat dx, dy, r2;
  char fname[1000];
  MPI_Status status;
  FILE *fd = 0;

  countlist = mymalloc(sizeof(int) * NTask);
  particles = mymalloc(sizeof(struct line_of_sight_particles) * N_gas);

  a3inv = 1.0 / (All.Time * All.Time * All.Time);

  for(n = 0, count_local = 0; n < N_gas; n++)
    {
      if(P[n].Type == 0)
	{
	  dx = los_periodic(P[n].Pos[Los->xaxis] - Los->Xpos);
	  dy = los_periodic(P[n].Pos[Los->yaxis] - Los->Ypos);

	  r2 = dx * dx + dy * dy;

	  if(r2 < PPP[n].Hsml * PPP[n].Hsml)
	    {
	      for(k = 0; k < 3; k++)
		particles[count_local].Pos[k] = P[n].Pos[k];

	      particles[count_local].Hsml = PPP[n].Hsml;
	      particles[count_local].Vz = P[n].Vel[Los->zaxis];
	      particles[count_local].Utherm = SphP[n].Entropy / GAMMA_MINUS1 * pow(SphP[n].d.Density *
										   a3inv, GAMMA_MINUS1);
	      particles[count_local].Mass = P[n].Mass;
	      particles[count_local].Metallicity = P[n].Metallicity;

	      count_local++;
	    }
	}
    }

  MPI_Gather(&count_local, 1, MPI_INT, countlist, 1, MPI_INT, 0, MPI_COMM_WORLD);

  if(ThisTask == 0)
    {
      sprintf(fname, "%s/los/part_los_z%05.3f_%03d.dat", All.OutputDir, 1 / All.Time - 1, num);

      if(!(fd = fopen(fname, "w")))
	{
	  printf("can't open file `%s`\n", fname);
	  endrun(112);
	}

      fwrite(&count_local, sizeof(int), 1, fd);	/* will be overwritten later */
      fwrite(&LosGlobal->xaxis, sizeof(int), 1, fd);
      fwrite(&LosGlobal->yaxis, sizeof(int), 1, fd);
      fwrite(&LosGlobal->zaxis, sizeof(int), 1, fd);
      fwrite(&LosGlobal->Xpos, sizeof(double), 1, fd);
      fwrite(&LosGlobal->Ypos, sizeof(double), 1, fd);
      fwrite(&LosGlobal->BoxSize, sizeof(double), 1, fd);
      fwrite(&LosGlobal->Wmax, sizeof(double), 1, fd);
      fwrite(&LosGlobal->Time, sizeof(double), 1, fd);
    }

  for(rep = 0, counttot = 0; rep < NTask; rep++)
    {
      if(ThisTask != 0 && rep == ThisTask && count_local > 0)
	MPI_Ssend(particles, sizeof(struct line_of_sight_particles) * count_local, MPI_BYTE, 0,
		  TAG_PDATA, MPI_COMM_WORLD);

      if(ThisTask == 0)
	{
	  if(rep > 0 && countlist[rep] > 0)
	    MPI_Recv(particles, sizeof(struct line_of_sight_particles) * countlist[rep],
		     MPI_BYTE, rep, TAG_PDATA, MPI_COMM_WORLD, &status);

	  fwrite(particles, sizeof(struct line_of_sight_particles), countlist[rep], fd);

	  counttot += countlist[rep];
	}
    }

  if(ThisTask == 0)
    {
      fclose(fd);
      if(!(fd = fopen(fname, "r+")))
	{
	  printf("can't open file `%s'\n", fname);
	  endrun(113);
	}

      fseek(fd, 0, SEEK_CUR);
      fwrite(&counttot, sizeof(int), 1, fd);
      fclose(fd);
    }

  myfree(particles);
  myfree(countlist);
}

void add_along_lines_of_sight(void)
{
  int n, bin, i, iz0, iz1, iz;
  double dx, dy, dz, r, r2, ne, nh0, nHeII, utherm, temp, meanWeight;
  double u, wk, weight, a3inv, h3inv;
  double z0, z1, dmax1, dmax2;

  for(i = 0; i < PIXELS; i++)
    {
      Los->Rho[i] = 0;
      Los->Vpec[i] = 0;
      Los->Temp[i] = 0;
      Los->Metallicity[i] = 0;

      Los->RhoHI[i] = 0;
      Los->NHI[i] = 0;
      Los->VpecHI[i] = 0;
      Los->TempHI[i] = 0;
      Los->TauHI[i] = 0;

      Los->RhoHeII[i] = 0;
      Los->NHeII[i] = 0;
      Los->VpecHeII[i] = 0;
      Los->TempHeII[i] = 0;
      Los->TauHeII[i] = 0;
    }

  a3inv = 1.0 / (All.Time * All.Time * All.Time);

  for(n = 0; n < N_gas; n++)
    {
      if(P[n].Type == 0)
	{
	  dx = los_periodic(P[n].Pos[Los->xaxis] - Los->Xpos);
	  dy = los_periodic(P[n].Pos[Los->yaxis] - Los->Ypos);

	  r2 = dx * dx + dy * dy;

	  if(r2 < PPP[n].Hsml * PPP[n].Hsml)
	    {
	      z0 = (P[n].Pos[Los->zaxis] - PPP[n].Hsml) / All.BoxSize * PIXELS;
	      z1 = (P[n].Pos[Los->zaxis] + PPP[n].Hsml) / All.BoxSize * PIXELS;
	      iz0 = (int) z0;
	      iz1 = (int) z1;
	      if(z0 < 0)
		iz0 -= 1;

	      for(iz = iz0; iz <= iz1; iz++)
		{
		  dz = los_periodic((iz + 0.5) / PIXELS * All.BoxSize - P[n].Pos[Los->zaxis]);
		  r = sqrt(r2 + dz * dz);

		  if(PPP[n].Hsml > All.BoxSize)
		    {
		      printf("Here:%d  n=%d %g\n", ThisTask, n, PPP[n].Hsml);
		      endrun(89);
		    }

		  if(r < PPP[n].Hsml)
		    {
		      u = r / PPP[n].Hsml;
		      h3inv = 1.0 / (PPP[n].Hsml * PPP[n].Hsml * PPP[n].Hsml);

		      if(u < 0.5)
			wk = h3inv * (KERNEL_COEFF_1 + KERNEL_COEFF_2 * (u - 1) * u * u);
		      else
			wk = h3inv * KERNEL_COEFF_5 * (1.0 - u) * (1.0 - u) * (1.0 - u);

		      bin = iz;
		      while(bin >= PIXELS)
			bin -= PIXELS;
		      while(bin < 0)
			bin += PIXELS;

		      ne = SphP[n].Ne;
		      utherm = DMAX(All.MinEgySpec,
				    SphP[n].Entropy / GAMMA_MINUS1 * pow(SphP[n].d.Density *
									 a3inv, GAMMA_MINUS1));

		      AbundanceRatios(utherm, SphP[n].d.Density * a3inv, &ne, &nh0, &nHeII);

		      meanWeight = 4.0 / (3 * HYDROGEN_MASSFRAC + 1 + 4 * HYDROGEN_MASSFRAC * ne);

		      temp = meanWeight * PROTONMASS / BOLTZMANN * GAMMA_MINUS1 * utherm
			* All.UnitEnergy_in_cgs / All.UnitMass_in_g;

		      /* do total gas */
		      weight = P[n].Mass * wk;
		      Los->Rho[bin] += weight;
		      Los->Metallicity[bin] += P[n].Metallicity * weight;
		      Los->Temp[bin] += temp * weight;
		      Los->Vpec[bin] += P[n].Vel[Los->zaxis] * weight;

		      /* do neutral hydrogen */
		      weight = nh0 * HYDROGEN_MASSFRAC * P[n].Mass * wk;
		      Los->RhoHI[bin] += weight;
		      Los->TempHI[bin] += temp * weight;
		      Los->VpecHI[bin] += P[n].Vel[Los->zaxis] * weight;

		      /* do HeII */
		      weight = 4 * nHeII * HYDROGEN_MASSFRAC * P[n].Mass * wk;
		      Los->RhoHeII[bin] += weight;
		      Los->TempHeII[bin] += temp * weight;
		      Los->VpecHeII[bin] += P[n].Vel[Los->zaxis] * weight;
		    }
		}
	    }
	}
    }
}

void sum_over_processors_and_normalize(void)
{
  int bin;

  MPI_Reduce(Los->Rho, LosGlobal->Rho, PIXELS, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
  MPI_Reduce(Los->Metallicity, LosGlobal->Metallicity, PIXELS, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
  MPI_Reduce(Los->Temp, LosGlobal->Temp, PIXELS, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
  MPI_Reduce(Los->Vpec, LosGlobal->Vpec, PIXELS, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);

  MPI_Reduce(Los->RhoHI, LosGlobal->RhoHI, PIXELS, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
  MPI_Reduce(Los->TempHI, LosGlobal->TempHI, PIXELS, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
  MPI_Reduce(Los->VpecHI, LosGlobal->VpecHI, PIXELS, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);

  MPI_Reduce(Los->RhoHeII, LosGlobal->RhoHeII, PIXELS, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
  MPI_Reduce(Los->TempHeII, LosGlobal->TempHeII, PIXELS, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
  MPI_Reduce(Los->VpecHeII, LosGlobal->VpecHeII, PIXELS, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);

  if(ThisTask == 0)
    {
      /* normalize results by the weights */
      for(bin = 0; bin < PIXELS; bin++)
	{
	  /* total gas density */
	  LosGlobal->Metallicity[bin] /= LosGlobal->Rho[bin];
	  LosGlobal->Temp[bin] /= LosGlobal->Rho[bin];
	  LosGlobal->Vpec[bin] /= (All.Time * LosGlobal->Rho[bin]);

	  /* neutral hydrogen quantities */
	  LosGlobal->VpecHI[bin] /= LosGlobal->RhoHI[bin];
	  LosGlobal->TempHI[bin] /= LosGlobal->RhoHI[bin];
	  LosGlobal->NHI[bin] = LosGlobal->RhoHI[bin] * (All.UnitMass_in_g / PROTONMASS);

	  /* HeII quantities */
	  LosGlobal->VpecHeII[bin] /= (All.Time * LosGlobal->RhoHeII[bin]);
	  LosGlobal->TempHeII[bin] /= LosGlobal->RhoHeII[bin];
	  LosGlobal->NHeII[bin] = LosGlobal->RhoHeII[bin] * (All.UnitMass_in_g / (4 * PROTONMASS));
	}
    }
}

void absorb_along_lines_of_sight(void)
{
  double dz, dv, b, fac, fac_HeII;
  int bin, k;

  if(ThisTask == 0)
    {
      dz = All.BoxSize / PIXELS;

      for(bin = 0; bin < PIXELS; bin++)
	{
	  LosGlobal->TauHI[bin] = 0;
	  LosGlobal->TauHeII[bin] = 0;

	  for(k = 0; k < PIXELS; k++)
	    {
	      dv = (k - bin);

	      while(dv < -PIXELS / 2)
		dv += PIXELS;
	      while(dv > PIXELS / 2)
		dv -= PIXELS;

	      dv = (dv * Wmax / PIXELS + LosGlobal->VpecHI[k]) * All.UnitVelocity_in_cm_per_s;

	      b = sqrt(2 * BOLTZMANN * LosGlobal->TempHI[k] / PROTONMASS);

	      LosGlobal->TauHI[bin] += LosGlobal->NHI[k] * exp(-dv * dv / (b * b)) / b * dz;

	      /* now HeII */
	      dv = (k - bin);

	      while(dv < -PIXELS / 2)
		dv += PIXELS;
	      while(dv > PIXELS / 2)
		dv -= PIXELS;

	      dv = (dv * Wmax / PIXELS + LosGlobal->VpecHeII[k]) * All.UnitVelocity_in_cm_per_s;

	      b = sqrt(2 * BOLTZMANN * LosGlobal->TempHeII[k] / (4 * PROTONMASS));

	      LosGlobal->TauHeII[bin] += LosGlobal->NHeII[k] * exp(-dv * dv / (b * b)) / b * dz;
	    }
	}

      /* multiply with correct prefactors */

      /*  to get things into cgs units */
      fac = 1 / pow(All.UnitLength_in_cm, 2);

      fac *= All.HubbleParam * All.HubbleParam;

      fac *= OSCILLATOR_STRENGTH * M_PI * LYMAN_ALPHA * sqrt(3 * THOMPSON / (8 * M_PI));	/* Ly-alpha cross section */

      fac *= C / (All.Time * All.Time) / sqrt(M_PI);

      /* Note: For HeII, the oscillator strength is equal to that of HI,
         and the Lyman-alpha wavelength is 4 times shorter */

      fac_HeII = fac * (OSCILLATOR_STRENGTH_HeII / OSCILLATOR_STRENGTH) * (LYMAN_ALPHA_HeII / LYMAN_ALPHA);

      for(bin = 0; bin < PIXELS; bin++)
	{
	  LosGlobal->TauHI[bin] *= fac;
	  LosGlobal->TauHeII[bin] *= fac_HeII;
	}

      LosGlobal->BoxSize = All.BoxSize;
      LosGlobal->Wmax = Wmax;
      LosGlobal->Time = All.Time;
    }

}

void output_lines_of_sight(int num)
{
  FILE *fd;
  int dummy;
  char fname[400];

  sprintf(fname, "%s/los/spec_los_z%05.3f_%03d.dat", All.OutputDir, 1 / All.Time - 1, num);

  if(!(fd = fopen(fname, "w")))
    {
      printf("can't open file `%s`\n", fname);
      exit(1);
    }

  dummy = PIXELS;
  fwrite(&dummy, sizeof(int), 1, fd);
  fwrite(&LosGlobal->BoxSize, sizeof(double), 1, fd);
  fwrite(&LosGlobal->Wmax, sizeof(double), 1, fd);
  fwrite(&LosGlobal->Time, sizeof(double), 1, fd);
  fwrite(&LosGlobal->Xpos, sizeof(double), 1, fd);
  fwrite(&LosGlobal->Ypos, sizeof(double), 1, fd);
  fwrite(&LosGlobal->xaxis, sizeof(int), 1, fd);
  fwrite(&LosGlobal->yaxis, sizeof(int), 1, fd);
  fwrite(&LosGlobal->zaxis, sizeof(int), 1, fd);

  fwrite(LosGlobal->TauHI, sizeof(double), PIXELS, fd);
  fwrite(LosGlobal->TempHI, sizeof(double), PIXELS, fd);
  fwrite(LosGlobal->VpecHI, sizeof(double), PIXELS, fd);
  fwrite(LosGlobal->NHI, sizeof(double), PIXELS, fd);

  fwrite(LosGlobal->TauHeII, sizeof(double), PIXELS, fd);
  fwrite(LosGlobal->TempHeII, sizeof(double), PIXELS, fd);
  fwrite(LosGlobal->VpecHeII, sizeof(double), PIXELS, fd);
  fwrite(LosGlobal->NHeII, sizeof(double), PIXELS, fd);

  fwrite(LosGlobal->Rho, sizeof(double), PIXELS, fd);
  fwrite(LosGlobal->Vpec, sizeof(double), PIXELS, fd);
  fwrite(LosGlobal->Temp, sizeof(double), PIXELS, fd);
  fwrite(LosGlobal->Metallicity, sizeof(double), PIXELS, fd);

  fclose(fd);
}

int find_next_lineofsighttime(int time0)
{
  double a1, a2, df1, df2, u1, u2;
  double logTimeBegin, logTimeMax;
  int i1, i2, im, time1;

  logTimeBegin = log(All.TimeBegin);
  logTimeMax = log(All.TimeMax);

  a1 = logTimeBegin + time0 * All.Timebase_interval;

  u1 = (a1 - logTimeBegin) / (logTimeMax - logTimeBegin) * DRIFT_TABLE_LENGTH;
  i1 = (int) u1;
  if(i1 >= DRIFT_TABLE_LENGTH)
    i1 = DRIFT_TABLE_LENGTH - 1;

  if(i1 <= 1)
    df1 = u1 * GravKickTable[0];
  else
    df1 = GravKickTable[i1 - 1] + (GravKickTable[i1] - GravKickTable[i1 - 1]) * (u1 - i1);

  df2 = df1 + All.BoxSize / (C / All.UnitVelocity_in_cm_per_s);

  i2 = DRIFT_TABLE_LENGTH - 1;

  while(i2 - i1 > 0)
    {
      im = (i1 - 1 + i2) / 2;

      if(GravKickTable[im] > df2)
	i2 = im;
      else
	i1 = im + 1;
    }

  u2 = (df2 - GravKickTable[i2 - 1]) / (GravKickTable[i2] - GravKickTable[i2 - 1]) + i2;

  a2 = u2 * (logTimeMax - logTimeBegin) / DRIFT_TABLE_LENGTH + logTimeBegin;

  time1 = (int) ((a2 - logTimeBegin) / All.Timebase_interval + 0.5);

  return time1;
}

double los_periodic(double x)
{
  if(x >= 0.5 * All.BoxSize)
    x -= All.BoxSize;
  if(x < -0.5 * All.BoxSize)
    x += All.BoxSize;

  return x;
}

#endif


#include <complex.h>
#include <math.h>
#include <petsc.h>
#include <stdint.h>
#define LOOPY_CALL_WITH_INTEGER_TYPES(MACRO_NAME) \
    MACRO_NAME(int8, char) \
    MACRO_NAME(int16, short) \
    MACRO_NAME(int32, int) \
    MACRO_NAME(int64, long)
#define LOOPY_DEFINE_FLOOR_DIV_POS_B(SUFFIX, TYPE) \
    static inline TYPE loopy_floor_div_pos_b_##SUFFIX(TYPE a, TYPE b) \
    { \
        if (a<0) \
            a = a - (b-1); \
        return a/b; \
    }
LOOPY_CALL_WITH_INTEGER_TYPES(LOOPY_DEFINE_FLOOR_DIV_POS_B)
#undef LOOPY_DEFINE_FLOOR_DIV_POS_B
#undef LOOPY_CALL_WITH_INTEGER_TYPES
#define BYTES8 (8*8)
typedef double double8 __attribute__ ((vector_size (BYTES8)));
#define BYTES4 (8*4)
typedef int int8 __attribute__ ((vector_size (BYTES4)));

void wrap_form0_cell_integral_otherwise(int const start, int const end, double *__restrict__ dat2, double const *__restrict__ dat1, double const *__restrict__ glob0, double const *__restrict__ dat0, int const *__restrict__ map0, int const *__restrict__ map1)
{
  double8 form_form0_cell_integral_otherwise __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_0 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_1 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_10 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_11 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_12 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_13 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_14 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_15 __attribute__ ((aligned (64)));
  double const form_form0_cell_integral_otherwise_16[4] __attribute__ ((aligned (64))) = { 0.041666666666666664, 0.041666666666666664, 0.041666666666666664, 0.041666666666666664 };
  double const form_form0_cell_integral_otherwise_17[4 * 4] __attribute__ ((aligned (64))) = { 0.13819660112500914, 0.585410196624969, 0.138196601125011, 0.13819660112501098, 0.13819660112500912, 0.13819660112501092, 0.585410196624969, 0.13819660112501098, 0.13819660112500914, 0.13819660112501095, 0.138196601125011, 0.585410196624969, 0.5854101966249672, 0.13819660112501092, 0.138196601125011, 0.13819660112501098 };
  double8 form_form0_cell_integral_otherwise_18 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_19 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_2 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_20 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_21 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_22[4] __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_23 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_24 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_25 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_26 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_27 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_28 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_29 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_3 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_30 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_31 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_32 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_33 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_34 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_35 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_36 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_37 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_38 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_39 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_4 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_40 __attribute__ ((aligned (64)));
  double const form_form0_cell_integral_otherwise_41[4] __attribute__ ((aligned (64))) = { -1.0, 0.0, 0.0, 1.0 };
  double8 form_form0_cell_integral_otherwise_42 __attribute__ ((aligned (64)));
  double const form_form0_cell_integral_otherwise_43[4] __attribute__ ((aligned (64))) = { -1.0, 0.0, 1.0, 0.0 };
  double8 form_form0_cell_integral_otherwise_44 __attribute__ ((aligned (64)));
  double const form_form0_cell_integral_otherwise_45[4] __attribute__ ((aligned (64))) = { -1.0, 1.0, 0.0, 0.0 };
  double8 form_form0_cell_integral_otherwise_5 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_6 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_7 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_8 __attribute__ ((aligned (64)));
  double8 form_form0_cell_integral_otherwise_9 __attribute__ ((aligned (64)));
  double8 t0[4] __attribute__ ((aligned (64)));
  double8 t1[4 * 3] __attribute__ ((aligned (64)));
  double8 t2[4] __attribute__ ((aligned (64)));
  double8 const zero_vec_float64 = { 0.0 };

  {
    /* initial slab for 'n_outer' */
    /*   */
  }
  {
    int const n_outer = start + -1 * ((7 + 7 * start) / 8);

    if (-1 + end + -1 * start >= 0)
    {
      for (int i2 = 0; i2 <= 3; ++i2)
        t2[i2] = zero_vec_float64;
      for (int i0 = 0; i0 <= 3; ++i0)
      {
        for (int i1 = 0; i1 <= 2; ++i1)
          for (int n_batch = start + -8 * n_outer; n_batch <= (-8 + end + -8 * n_outer >= 0 ? 7 : -1 + end + -8 * n_outer); ++n_batch)
            t1[(3 * i0 + i1)][n_batch] = dat1[3 * map1[32 * n_outer + 4 * n_batch + i0] + i1];
        for (int n_batch = start + -8 * n_outer; n_batch <= (-8 + end + -8 * n_outer >= 0 ? 7 : -1 + end + -8 * n_outer); ++n_batch)
          t0[i0][n_batch] = dat0[map0[32 * n_outer + 4 * n_batch + i0]];
      }
      for (int n_batch = start + -8 * n_outer; n_batch <= (-8 + end + -8 * n_outer >= 0 ? 7 : -1 + end + -8 * n_outer); ++n_batch)
      {
        /* no-op (insn=form__start) */
        /*   */
      }
      form_form0_cell_integral_otherwise_20 = zero_vec_float64;
      form_form0_cell_integral_otherwise = -1.0 * t1[0];
      form_form0_cell_integral_otherwise_0 = form_form0_cell_integral_otherwise + t1[3];
      form_form0_cell_integral_otherwise_1 = -1.0 * t1[1];
      form_form0_cell_integral_otherwise_2 = form_form0_cell_integral_otherwise_1 + t1[7];
      form_form0_cell_integral_otherwise_3 = -1.0 * t1[2];
      form_form0_cell_integral_otherwise_4 = form_form0_cell_integral_otherwise_3 + t1[11];
      form_form0_cell_integral_otherwise_5 = form_form0_cell_integral_otherwise_1 + t1[10];
      form_form0_cell_integral_otherwise_6 = form_form0_cell_integral_otherwise_3 + t1[8];
      form_form0_cell_integral_otherwise_7 = form_form0_cell_integral_otherwise_2 * form_form0_cell_integral_otherwise_4 + -1.0 * form_form0_cell_integral_otherwise_5 * form_form0_cell_integral_otherwise_6;
      form_form0_cell_integral_otherwise_8 = form_form0_cell_integral_otherwise + t1[6];
      form_form0_cell_integral_otherwise_9 = form_form0_cell_integral_otherwise_3 + t1[5];
      form_form0_cell_integral_otherwise_10 = form_form0_cell_integral_otherwise_5 * form_form0_cell_integral_otherwise_9;
      form_form0_cell_integral_otherwise_11 = form_form0_cell_integral_otherwise_1 + t1[4];
      form_form0_cell_integral_otherwise_12 = form_form0_cell_integral_otherwise + t1[9];
      form_form0_cell_integral_otherwise_13 = form_form0_cell_integral_otherwise_11 * form_form0_cell_integral_otherwise_6 + -1.0 * form_form0_cell_integral_otherwise_2 * form_form0_cell_integral_otherwise_9;
      form_form0_cell_integral_otherwise_14 = form_form0_cell_integral_otherwise_12 * form_form0_cell_integral_otherwise_13 + form_form0_cell_integral_otherwise_0 * form_form0_cell_integral_otherwise_7 + form_form0_cell_integral_otherwise_8 * (form_form0_cell_integral_otherwise_10 + -1.0 * form_form0_cell_integral_otherwise_11 * form_form0_cell_integral_otherwise_4);
      #pragma omp simd
      for (int n_batch_simd = start + -8 * n_outer; n_batch_simd <= (-8 + end + -8 * n_outer >= 0 ? 7 : -1 + end + -8 * n_outer); ++n_batch_simd)
        form_form0_cell_integral_otherwise_15[n_batch_simd] = fabs(form_form0_cell_integral_otherwise_14[n_batch_simd]);
      for (int form_j = 0; form_j <= 3; ++form_j)
        form_form0_cell_integral_otherwise_22[form_j] = zero_vec_float64;
      for (int form_ip = 0; form_ip <= 3; ++form_ip)
      {
        form_form0_cell_integral_otherwise_18 = zero_vec_float64;
        for (int form_i = 0; form_i <= 3; ++form_i)
          form_form0_cell_integral_otherwise_18 = form_form0_cell_integral_otherwise_18 + form_form0_cell_integral_otherwise_17[4 * form_ip + form_i] * t0[form_i];
        form_form0_cell_integral_otherwise_19 = form_form0_cell_integral_otherwise_16[form_ip] * form_form0_cell_integral_otherwise_15;
        form_form0_cell_integral_otherwise_20 = form_form0_cell_integral_otherwise_20 + form_form0_cell_integral_otherwise_19;
        form_form0_cell_integral_otherwise_21 = form_form0_cell_integral_otherwise_19 * glob0[0] * form_form0_cell_integral_otherwise_18;
        for (int form_j_0 = 0; form_j_0 <= 3; ++form_j_0)
          form_form0_cell_integral_otherwise_22[form_j_0] = form_form0_cell_integral_otherwise_22[form_j_0] + form_form0_cell_integral_otherwise_17[4 * form_ip + form_j_0] * form_form0_cell_integral_otherwise_21;
      }
      form_form0_cell_integral_otherwise_23 = 1.0 / form_form0_cell_integral_otherwise_14;
      form_form0_cell_integral_otherwise_24 = form_form0_cell_integral_otherwise_7 * form_form0_cell_integral_otherwise_23;
      form_form0_cell_integral_otherwise_25 = -1.0 * t0[0];
      form_form0_cell_integral_otherwise_26 = form_form0_cell_integral_otherwise_25 + t0[1];
      form_form0_cell_integral_otherwise_27 = (form_form0_cell_integral_otherwise_10 + form_form0_cell_integral_otherwise_11 * -1.0 * form_form0_cell_integral_otherwise_4) * form_form0_cell_integral_otherwise_23;
      form_form0_cell_integral_otherwise_28 = form_form0_cell_integral_otherwise_25 + t0[2];
      form_form0_cell_integral_otherwise_29 = form_form0_cell_integral_otherwise_13 * form_form0_cell_integral_otherwise_23;
      form_form0_cell_integral_otherwise_30 = form_form0_cell_integral_otherwise_25 + t0[3];
      form_form0_cell_integral_otherwise_31 = form_form0_cell_integral_otherwise_29 * form_form0_cell_integral_otherwise_30 + form_form0_cell_integral_otherwise_24 * form_form0_cell_integral_otherwise_26 + form_form0_cell_integral_otherwise_27 * form_form0_cell_integral_otherwise_28;
      form_form0_cell_integral_otherwise_32 = (form_form0_cell_integral_otherwise_12 * form_form0_cell_integral_otherwise_6 + form_form0_cell_integral_otherwise_4 * -1.0 * form_form0_cell_integral_otherwise_8) * form_form0_cell_integral_otherwise_23;
      form_form0_cell_integral_otherwise_33 = (form_form0_cell_integral_otherwise_0 * form_form0_cell_integral_otherwise_4 + form_form0_cell_integral_otherwise_9 * -1.0 * form_form0_cell_integral_otherwise_12) * form_form0_cell_integral_otherwise_23;
      form_form0_cell_integral_otherwise_34 = (form_form0_cell_integral_otherwise_8 * form_form0_cell_integral_otherwise_9 + -1.0 * form_form0_cell_integral_otherwise_0 * form_form0_cell_integral_otherwise_6) * form_form0_cell_integral_otherwise_23;
      form_form0_cell_integral_otherwise_35 = form_form0_cell_integral_otherwise_34 * form_form0_cell_integral_otherwise_30 + form_form0_cell_integral_otherwise_32 * form_form0_cell_integral_otherwise_26 + form_form0_cell_integral_otherwise_33 * form_form0_cell_integral_otherwise_28;
      form_form0_cell_integral_otherwise_36 = (form_form0_cell_integral_otherwise_8 * form_form0_cell_integral_otherwise_5 + -1.0 * form_form0_cell_integral_otherwise_12 * form_form0_cell_integral_otherwise_2) * form_form0_cell_integral_otherwise_23;
      form_form0_cell_integral_otherwise_37 = (form_form0_cell_integral_otherwise_12 * form_form0_cell_integral_otherwise_11 + -1.0 * form_form0_cell_integral_otherwise_0 * form_form0_cell_integral_otherwise_5) * form_form0_cell_integral_otherwise_23;
      form_form0_cell_integral_otherwise_38 = (form_form0_cell_integral_otherwise_0 * form_form0_cell_integral_otherwise_2 + -1.0 * form_form0_cell_integral_otherwise_8 * form_form0_cell_integral_otherwise_11) * form_form0_cell_integral_otherwise_23;
      form_form0_cell_integral_otherwise_39 = form_form0_cell_integral_otherwise_38 * form_form0_cell_integral_otherwise_30 + form_form0_cell_integral_otherwise_36 * form_form0_cell_integral_otherwise_26 + form_form0_cell_integral_otherwise_37 * form_form0_cell_integral_otherwise_28;
      form_form0_cell_integral_otherwise_40 = (form_form0_cell_integral_otherwise_39 * form_form0_cell_integral_otherwise_38 + form_form0_cell_integral_otherwise_31 * form_form0_cell_integral_otherwise_29 + form_form0_cell_integral_otherwise_35 * form_form0_cell_integral_otherwise_34) * form_form0_cell_integral_otherwise_20;
      form_form0_cell_integral_otherwise_42 = (form_form0_cell_integral_otherwise_39 * form_form0_cell_integral_otherwise_37 + form_form0_cell_integral_otherwise_31 * form_form0_cell_integral_otherwise_27 + form_form0_cell_integral_otherwise_35 * form_form0_cell_integral_otherwise_33) * form_form0_cell_integral_otherwise_20;
      form_form0_cell_integral_otherwise_44 = (form_form0_cell_integral_otherwise_39 * form_form0_cell_integral_otherwise_36 + form_form0_cell_integral_otherwise_31 * form_form0_cell_integral_otherwise_24 + form_form0_cell_integral_otherwise_35 * form_form0_cell_integral_otherwise_32) * form_form0_cell_integral_otherwise_20;
      for (int form_j_1 = 0; form_j_1 <= 3; ++form_j_1)
        t2[form_j_1] = t2[form_j_1] + form_form0_cell_integral_otherwise_41[form_j_1] * form_form0_cell_integral_otherwise_40 + form_form0_cell_integral_otherwise_43[form_j_1] * form_form0_cell_integral_otherwise_42 + form_form0_cell_integral_otherwise_22[form_j_1] + form_form0_cell_integral_otherwise_45[form_j_1] * form_form0_cell_integral_otherwise_44;
      for (int n_batch = start + -8 * n_outer; n_batch <= (-8 + end + -8 * n_outer >= 0 ? 7 : -1 + end + -8 * n_outer); ++n_batch)
      {
        /* no-op (insn=statement4) */
        /*   */
      }
      for (int i3 = 0; i3 <= 3; ++i3)
        for (int n_batch = start + -8 * n_outer; n_batch <= (-8 + end + -8 * n_outer >= 0 ? 7 : -1 + end + -8 * n_outer); ++n_batch)
          dat2[map0[32 * n_outer + 4 * n_batch + i3]] = dat2[map0[32 * n_outer + 4 * n_batch + i3]] + t2[i3][n_batch];
    }
  }
  {
    /* bulk slab for 'n_outer' */
    /*   */
  }
  for (int n_outer = 1 + start + -1 * ((7 + 7 * start) / 8); n_outer <= -2 + (7 + end) / 8; ++n_outer)
  {
    for (int i2 = 0; i2 <= 3; ++i2)
      t2[i2] = zero_vec_float64;
    for (int i0 = 0; i0 <= 3; ++i0)
    {
      for (int i1 = 0; i1 <= 2; ++i1)
        for (int n_batch = 0; n_batch <= 7; ++n_batch)
          t1[(3 * i0 + i1)][n_batch] = dat1[3 * map1[32 * n_outer + 4 * n_batch + i0] + i1];
      for (int n_batch = 0; n_batch <= 7; ++n_batch)
        t0[i0][n_batch] = dat0[map0[32 * n_outer + 4 * n_batch + i0]];
    }
    for (int n_batch = 0; n_batch <= 7; ++n_batch)
    {
      /* no-op (insn=form__start) */
      /*   */
    }
    form_form0_cell_integral_otherwise_20 = zero_vec_float64;
    form_form0_cell_integral_otherwise = -1.0 * t1[0];
    form_form0_cell_integral_otherwise_0 = form_form0_cell_integral_otherwise + t1[3];
    form_form0_cell_integral_otherwise_1 = -1.0 * t1[1];
    form_form0_cell_integral_otherwise_2 = form_form0_cell_integral_otherwise_1 + t1[7];
    form_form0_cell_integral_otherwise_3 = -1.0 * t1[2];
    form_form0_cell_integral_otherwise_4 = form_form0_cell_integral_otherwise_3 + t1[11];
    form_form0_cell_integral_otherwise_5 = form_form0_cell_integral_otherwise_1 + t1[10];
    form_form0_cell_integral_otherwise_6 = form_form0_cell_integral_otherwise_3 + t1[8];
    form_form0_cell_integral_otherwise_7 = form_form0_cell_integral_otherwise_2 * form_form0_cell_integral_otherwise_4 + -1.0 * form_form0_cell_integral_otherwise_5 * form_form0_cell_integral_otherwise_6;
    form_form0_cell_integral_otherwise_8 = form_form0_cell_integral_otherwise + t1[6];
    form_form0_cell_integral_otherwise_9 = form_form0_cell_integral_otherwise_3 + t1[5];
    form_form0_cell_integral_otherwise_10 = form_form0_cell_integral_otherwise_5 * form_form0_cell_integral_otherwise_9;
    form_form0_cell_integral_otherwise_11 = form_form0_cell_integral_otherwise_1 + t1[4];
    form_form0_cell_integral_otherwise_12 = form_form0_cell_integral_otherwise + t1[9];
    form_form0_cell_integral_otherwise_13 = form_form0_cell_integral_otherwise_11 * form_form0_cell_integral_otherwise_6 + -1.0 * form_form0_cell_integral_otherwise_2 * form_form0_cell_integral_otherwise_9;
    form_form0_cell_integral_otherwise_14 = form_form0_cell_integral_otherwise_12 * form_form0_cell_integral_otherwise_13 + form_form0_cell_integral_otherwise_0 * form_form0_cell_integral_otherwise_7 + form_form0_cell_integral_otherwise_8 * (form_form0_cell_integral_otherwise_10 + -1.0 * form_form0_cell_integral_otherwise_11 * form_form0_cell_integral_otherwise_4);
    #pragma omp simd
    for (int n_batch_simd = 0; n_batch_simd <= 7; ++n_batch_simd)
      form_form0_cell_integral_otherwise_15[n_batch_simd] = fabs(form_form0_cell_integral_otherwise_14[n_batch_simd]);
    for (int form_j = 0; form_j <= 3; ++form_j)
      form_form0_cell_integral_otherwise_22[form_j] = zero_vec_float64;
    for (int form_ip = 0; form_ip <= 3; ++form_ip)
    {
      form_form0_cell_integral_otherwise_18 = zero_vec_float64;
      for (int form_i = 0; form_i <= 3; ++form_i)
        form_form0_cell_integral_otherwise_18 = form_form0_cell_integral_otherwise_18 + form_form0_cell_integral_otherwise_17[4 * form_ip + form_i] * t0[form_i];
      form_form0_cell_integral_otherwise_19 = form_form0_cell_integral_otherwise_16[form_ip] * form_form0_cell_integral_otherwise_15;
      form_form0_cell_integral_otherwise_20 = form_form0_cell_integral_otherwise_20 + form_form0_cell_integral_otherwise_19;
      form_form0_cell_integral_otherwise_21 = form_form0_cell_integral_otherwise_19 * glob0[0] * form_form0_cell_integral_otherwise_18;
      for (int form_j_0 = 0; form_j_0 <= 3; ++form_j_0)
        form_form0_cell_integral_otherwise_22[form_j_0] = form_form0_cell_integral_otherwise_22[form_j_0] + form_form0_cell_integral_otherwise_17[4 * form_ip + form_j_0] * form_form0_cell_integral_otherwise_21;
    }
    form_form0_cell_integral_otherwise_23 = 1.0 / form_form0_cell_integral_otherwise_14;
    form_form0_cell_integral_otherwise_24 = form_form0_cell_integral_otherwise_7 * form_form0_cell_integral_otherwise_23;
    form_form0_cell_integral_otherwise_25 = -1.0 * t0[0];
    form_form0_cell_integral_otherwise_26 = form_form0_cell_integral_otherwise_25 + t0[1];
    form_form0_cell_integral_otherwise_27 = (form_form0_cell_integral_otherwise_10 + form_form0_cell_integral_otherwise_11 * -1.0 * form_form0_cell_integral_otherwise_4) * form_form0_cell_integral_otherwise_23;
    form_form0_cell_integral_otherwise_28 = form_form0_cell_integral_otherwise_25 + t0[2];
    form_form0_cell_integral_otherwise_29 = form_form0_cell_integral_otherwise_13 * form_form0_cell_integral_otherwise_23;
    form_form0_cell_integral_otherwise_30 = form_form0_cell_integral_otherwise_25 + t0[3];
    form_form0_cell_integral_otherwise_31 = form_form0_cell_integral_otherwise_29 * form_form0_cell_integral_otherwise_30 + form_form0_cell_integral_otherwise_24 * form_form0_cell_integral_otherwise_26 + form_form0_cell_integral_otherwise_27 * form_form0_cell_integral_otherwise_28;
    form_form0_cell_integral_otherwise_32 = (form_form0_cell_integral_otherwise_12 * form_form0_cell_integral_otherwise_6 + form_form0_cell_integral_otherwise_4 * -1.0 * form_form0_cell_integral_otherwise_8) * form_form0_cell_integral_otherwise_23;
    form_form0_cell_integral_otherwise_33 = (form_form0_cell_integral_otherwise_0 * form_form0_cell_integral_otherwise_4 + form_form0_cell_integral_otherwise_9 * -1.0 * form_form0_cell_integral_otherwise_12) * form_form0_cell_integral_otherwise_23;
    form_form0_cell_integral_otherwise_34 = (form_form0_cell_integral_otherwise_8 * form_form0_cell_integral_otherwise_9 + -1.0 * form_form0_cell_integral_otherwise_0 * form_form0_cell_integral_otherwise_6) * form_form0_cell_integral_otherwise_23;
    form_form0_cell_integral_otherwise_35 = form_form0_cell_integral_otherwise_34 * form_form0_cell_integral_otherwise_30 + form_form0_cell_integral_otherwise_32 * form_form0_cell_integral_otherwise_26 + form_form0_cell_integral_otherwise_33 * form_form0_cell_integral_otherwise_28;
    form_form0_cell_integral_otherwise_36 = (form_form0_cell_integral_otherwise_8 * form_form0_cell_integral_otherwise_5 + -1.0 * form_form0_cell_integral_otherwise_12 * form_form0_cell_integral_otherwise_2) * form_form0_cell_integral_otherwise_23;
    form_form0_cell_integral_otherwise_37 = (form_form0_cell_integral_otherwise_12 * form_form0_cell_integral_otherwise_11 + -1.0 * form_form0_cell_integral_otherwise_0 * form_form0_cell_integral_otherwise_5) * form_form0_cell_integral_otherwise_23;
    form_form0_cell_integral_otherwise_38 = (form_form0_cell_integral_otherwise_0 * form_form0_cell_integral_otherwise_2 + -1.0 * form_form0_cell_integral_otherwise_8 * form_form0_cell_integral_otherwise_11) * form_form0_cell_integral_otherwise_23;
    form_form0_cell_integral_otherwise_39 = form_form0_cell_integral_otherwise_38 * form_form0_cell_integral_otherwise_30 + form_form0_cell_integral_otherwise_36 * form_form0_cell_integral_otherwise_26 + form_form0_cell_integral_otherwise_37 * form_form0_cell_integral_otherwise_28;
    form_form0_cell_integral_otherwise_40 = (form_form0_cell_integral_otherwise_39 * form_form0_cell_integral_otherwise_38 + form_form0_cell_integral_otherwise_31 * form_form0_cell_integral_otherwise_29 + form_form0_cell_integral_otherwise_35 * form_form0_cell_integral_otherwise_34) * form_form0_cell_integral_otherwise_20;
    form_form0_cell_integral_otherwise_42 = (form_form0_cell_integral_otherwise_39 * form_form0_cell_integral_otherwise_37 + form_form0_cell_integral_otherwise_31 * form_form0_cell_integral_otherwise_27 + form_form0_cell_integral_otherwise_35 * form_form0_cell_integral_otherwise_33) * form_form0_cell_integral_otherwise_20;
    form_form0_cell_integral_otherwise_44 = (form_form0_cell_integral_otherwise_39 * form_form0_cell_integral_otherwise_36 + form_form0_cell_integral_otherwise_31 * form_form0_cell_integral_otherwise_24 + form_form0_cell_integral_otherwise_35 * form_form0_cell_integral_otherwise_32) * form_form0_cell_integral_otherwise_20;
    for (int form_j_1 = 0; form_j_1 <= 3; ++form_j_1)
      t2[form_j_1] = t2[form_j_1] + form_form0_cell_integral_otherwise_41[form_j_1] * form_form0_cell_integral_otherwise_40 + form_form0_cell_integral_otherwise_43[form_j_1] * form_form0_cell_integral_otherwise_42 + form_form0_cell_integral_otherwise_22[form_j_1] + form_form0_cell_integral_otherwise_45[form_j_1] * form_form0_cell_integral_otherwise_44;
    for (int n_batch = 0; n_batch <= 7; ++n_batch)
    {
      /* no-op (insn=statement4) */
      /*   */
    }
    for (int i3 = 0; i3 <= 3; ++i3)
      for (int n_batch = 0; n_batch <= 7; ++n_batch)
        dat2[map0[32 * n_outer + 4 * n_batch + i3]] = dat2[map0[32 * n_outer + 4 * n_batch + i3]] + t2[i3][n_batch];
  }
  {
    /* final slab for 'n_outer' */
    /*   */
  }
  {
    int const n_outer = loopy_floor_div_pos_b_int32(-1 + end, 8);

    if (-1 + 8 * n_outer + -1 * start >= 0)
    {
      for (int i2 = 0; i2 <= 3; ++i2)
        t2[i2] = zero_vec_float64;
      for (int i0 = 0; i0 <= 3; ++i0)
      {
        for (int i1 = 0; i1 <= 2; ++i1)
          for (int n_batch = 0; n_batch <= -1 + end + -8 * n_outer; ++n_batch)
            t1[(3 * i0 + i1)][n_batch] = dat1[3 * map1[32 * n_outer + 4 * n_batch + i0] + i1];
        for (int n_batch = 0; n_batch <= -1 + end + -8 * n_outer; ++n_batch)
          t0[i0][n_batch] = dat0[map0[32 * n_outer + 4 * n_batch + i0]];
      }
      for (int n_batch = 0; n_batch <= -1 + end + -8 * n_outer; ++n_batch)
      {
        /* no-op (insn=form__start) */
        /*   */
      }
      form_form0_cell_integral_otherwise_20 = zero_vec_float64;
      form_form0_cell_integral_otherwise = -1.0 * t1[0];
      form_form0_cell_integral_otherwise_0 = form_form0_cell_integral_otherwise + t1[3];
      form_form0_cell_integral_otherwise_1 = -1.0 * t1[1];
      form_form0_cell_integral_otherwise_2 = form_form0_cell_integral_otherwise_1 + t1[7];
      form_form0_cell_integral_otherwise_3 = -1.0 * t1[2];
      form_form0_cell_integral_otherwise_4 = form_form0_cell_integral_otherwise_3 + t1[11];
      form_form0_cell_integral_otherwise_5 = form_form0_cell_integral_otherwise_1 + t1[10];
      form_form0_cell_integral_otherwise_6 = form_form0_cell_integral_otherwise_3 + t1[8];
      form_form0_cell_integral_otherwise_7 = form_form0_cell_integral_otherwise_2 * form_form0_cell_integral_otherwise_4 + -1.0 * form_form0_cell_integral_otherwise_5 * form_form0_cell_integral_otherwise_6;
      form_form0_cell_integral_otherwise_8 = form_form0_cell_integral_otherwise + t1[6];
      form_form0_cell_integral_otherwise_9 = form_form0_cell_integral_otherwise_3 + t1[5];
      form_form0_cell_integral_otherwise_10 = form_form0_cell_integral_otherwise_5 * form_form0_cell_integral_otherwise_9;
      form_form0_cell_integral_otherwise_11 = form_form0_cell_integral_otherwise_1 + t1[4];
      form_form0_cell_integral_otherwise_12 = form_form0_cell_integral_otherwise + t1[9];
      form_form0_cell_integral_otherwise_13 = form_form0_cell_integral_otherwise_11 * form_form0_cell_integral_otherwise_6 + -1.0 * form_form0_cell_integral_otherwise_2 * form_form0_cell_integral_otherwise_9;
      form_form0_cell_integral_otherwise_14 = form_form0_cell_integral_otherwise_12 * form_form0_cell_integral_otherwise_13 + form_form0_cell_integral_otherwise_0 * form_form0_cell_integral_otherwise_7 + form_form0_cell_integral_otherwise_8 * (form_form0_cell_integral_otherwise_10 + -1.0 * form_form0_cell_integral_otherwise_11 * form_form0_cell_integral_otherwise_4);
      #pragma omp simd
      for (int n_batch_simd = 0; n_batch_simd <= -1 + end + -8 * n_outer; ++n_batch_simd)
        form_form0_cell_integral_otherwise_15[n_batch_simd] = fabs(form_form0_cell_integral_otherwise_14[n_batch_simd]);
      for (int form_j = 0; form_j <= 3; ++form_j)
        form_form0_cell_integral_otherwise_22[form_j] = zero_vec_float64;
      for (int form_ip = 0; form_ip <= 3; ++form_ip)
      {
        form_form0_cell_integral_otherwise_18 = zero_vec_float64;
        for (int form_i = 0; form_i <= 3; ++form_i)
          form_form0_cell_integral_otherwise_18 = form_form0_cell_integral_otherwise_18 + form_form0_cell_integral_otherwise_17[4 * form_ip + form_i] * t0[form_i];
        form_form0_cell_integral_otherwise_19 = form_form0_cell_integral_otherwise_16[form_ip] * form_form0_cell_integral_otherwise_15;
        form_form0_cell_integral_otherwise_20 = form_form0_cell_integral_otherwise_20 + form_form0_cell_integral_otherwise_19;
        form_form0_cell_integral_otherwise_21 = form_form0_cell_integral_otherwise_19 * glob0[0] * form_form0_cell_integral_otherwise_18;
        for (int form_j_0 = 0; form_j_0 <= 3; ++form_j_0)
          form_form0_cell_integral_otherwise_22[form_j_0] = form_form0_cell_integral_otherwise_22[form_j_0] + form_form0_cell_integral_otherwise_17[4 * form_ip + form_j_0] * form_form0_cell_integral_otherwise_21;
      }
      form_form0_cell_integral_otherwise_23 = 1.0 / form_form0_cell_integral_otherwise_14;
      form_form0_cell_integral_otherwise_24 = form_form0_cell_integral_otherwise_7 * form_form0_cell_integral_otherwise_23;
      form_form0_cell_integral_otherwise_25 = -1.0 * t0[0];
      form_form0_cell_integral_otherwise_26 = form_form0_cell_integral_otherwise_25 + t0[1];
      form_form0_cell_integral_otherwise_27 = (form_form0_cell_integral_otherwise_10 + form_form0_cell_integral_otherwise_11 * -1.0 * form_form0_cell_integral_otherwise_4) * form_form0_cell_integral_otherwise_23;
      form_form0_cell_integral_otherwise_28 = form_form0_cell_integral_otherwise_25 + t0[2];
      form_form0_cell_integral_otherwise_29 = form_form0_cell_integral_otherwise_13 * form_form0_cell_integral_otherwise_23;
      form_form0_cell_integral_otherwise_30 = form_form0_cell_integral_otherwise_25 + t0[3];
      form_form0_cell_integral_otherwise_31 = form_form0_cell_integral_otherwise_29 * form_form0_cell_integral_otherwise_30 + form_form0_cell_integral_otherwise_24 * form_form0_cell_integral_otherwise_26 + form_form0_cell_integral_otherwise_27 * form_form0_cell_integral_otherwise_28;
      form_form0_cell_integral_otherwise_32 = (form_form0_cell_integral_otherwise_12 * form_form0_cell_integral_otherwise_6 + form_form0_cell_integral_otherwise_4 * -1.0 * form_form0_cell_integral_otherwise_8) * form_form0_cell_integral_otherwise_23;
      form_form0_cell_integral_otherwise_33 = (form_form0_cell_integral_otherwise_0 * form_form0_cell_integral_otherwise_4 + form_form0_cell_integral_otherwise_9 * -1.0 * form_form0_cell_integral_otherwise_12) * form_form0_cell_integral_otherwise_23;
      form_form0_cell_integral_otherwise_34 = (form_form0_cell_integral_otherwise_8 * form_form0_cell_integral_otherwise_9 + -1.0 * form_form0_cell_integral_otherwise_0 * form_form0_cell_integral_otherwise_6) * form_form0_cell_integral_otherwise_23;
      form_form0_cell_integral_otherwise_35 = form_form0_cell_integral_otherwise_34 * form_form0_cell_integral_otherwise_30 + form_form0_cell_integral_otherwise_32 * form_form0_cell_integral_otherwise_26 + form_form0_cell_integral_otherwise_33 * form_form0_cell_integral_otherwise_28;
      form_form0_cell_integral_otherwise_36 = (form_form0_cell_integral_otherwise_8 * form_form0_cell_integral_otherwise_5 + -1.0 * form_form0_cell_integral_otherwise_12 * form_form0_cell_integral_otherwise_2) * form_form0_cell_integral_otherwise_23;
      form_form0_cell_integral_otherwise_37 = (form_form0_cell_integral_otherwise_12 * form_form0_cell_integral_otherwise_11 + -1.0 * form_form0_cell_integral_otherwise_0 * form_form0_cell_integral_otherwise_5) * form_form0_cell_integral_otherwise_23;
      form_form0_cell_integral_otherwise_38 = (form_form0_cell_integral_otherwise_0 * form_form0_cell_integral_otherwise_2 + -1.0 * form_form0_cell_integral_otherwise_8 * form_form0_cell_integral_otherwise_11) * form_form0_cell_integral_otherwise_23;
      form_form0_cell_integral_otherwise_39 = form_form0_cell_integral_otherwise_38 * form_form0_cell_integral_otherwise_30 + form_form0_cell_integral_otherwise_36 * form_form0_cell_integral_otherwise_26 + form_form0_cell_integral_otherwise_37 * form_form0_cell_integral_otherwise_28;
      form_form0_cell_integral_otherwise_40 = (form_form0_cell_integral_otherwise_39 * form_form0_cell_integral_otherwise_38 + form_form0_cell_integral_otherwise_31 * form_form0_cell_integral_otherwise_29 + form_form0_cell_integral_otherwise_35 * form_form0_cell_integral_otherwise_34) * form_form0_cell_integral_otherwise_20;
      form_form0_cell_integral_otherwise_42 = (form_form0_cell_integral_otherwise_39 * form_form0_cell_integral_otherwise_37 + form_form0_cell_integral_otherwise_31 * form_form0_cell_integral_otherwise_27 + form_form0_cell_integral_otherwise_35 * form_form0_cell_integral_otherwise_33) * form_form0_cell_integral_otherwise_20;
      form_form0_cell_integral_otherwise_44 = (form_form0_cell_integral_otherwise_39 * form_form0_cell_integral_otherwise_36 + form_form0_cell_integral_otherwise_31 * form_form0_cell_integral_otherwise_24 + form_form0_cell_integral_otherwise_35 * form_form0_cell_integral_otherwise_32) * form_form0_cell_integral_otherwise_20;
      for (int form_j_1 = 0; form_j_1 <= 3; ++form_j_1)
        t2[form_j_1] = t2[form_j_1] + form_form0_cell_integral_otherwise_41[form_j_1] * form_form0_cell_integral_otherwise_40 + form_form0_cell_integral_otherwise_43[form_j_1] * form_form0_cell_integral_otherwise_42 + form_form0_cell_integral_otherwise_22[form_j_1] + form_form0_cell_integral_otherwise_45[form_j_1] * form_form0_cell_integral_otherwise_44;
      for (int n_batch = 0; n_batch <= -1 + end + -8 * n_outer; ++n_batch)
      {
        /* no-op (insn=statement4) */
        /*   */
      }
      for (int i3 = 0; i3 <= 3; ++i3)
        for (int n_batch = 0; n_batch <= -1 + end + -8 * n_outer; ++n_batch)
          dat2[map0[32 * n_outer + 4 * n_batch + i3]] = dat2[map0[32 * n_outer + 4 * n_batch + i3]] + t2[i3][n_batch];
    }
  }
}