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

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

#if (defined(SMOOTH_PHI) || defined(SMOOTH_ROTB) || defined(BSMOOTH))

/*! Structure for communication during the density computation. Holds data that is sent to other processors.
 */
static struct smoothdata_in
{
  MyDouble Pos[3];
  MyFloat Hsml;
  int NodeList[NODELISTLENGTH];
}
 *SmoothDataIn, *SmoothDataGet;

static struct smoothdata_out
{
#ifdef SMOOTH_PHI
  MyFloat SmoothPhi;
  MyFloat SmoothDivB;
#endif
#ifdef SMOOTH_ROTB
  MyFloat SmoothRotB[3];
#endif
#ifdef BSMOOTH
  MyFloat BSmooth[3];
#endif
#if defined(BLACK_HOLES)
  MyLongDouble SmoothedEntr;
#endif
  MyFloat DensityNorm;
}
 *SmoothDataResult, *SmoothDataOut;

void smoothed_values(void)
{
  int ngrp, sendTask, recvTask, place, nexport, nimport;
  int i, j, ndone, ndone_flag, dummy;
  double timeall = 0, timecomp1 = 0, timecomp2 = 0, timecommsumm1 = 0, timecommsumm2 = 0, timewait1 =
    0, timewait2 = 0;
  double timecomp, timecomm, timewait;
  double tstart, tend, t0, t1;

#ifdef BSMOOTH
  int Smooth_Flag = 0;
  double dB[3];
#endif

  /* Display information message that this step is executed on Task 0 ... */
  if(ThisTask == 0)
    {
      printf("Updating SPH interpolants for:"
#ifdef SMOOTH_PHI
	     " (divB and Phi(Dedner)"
#endif /* SMOOTH_PHI */
#ifdef SMOOTH_ROTB
	     " (rotB)"
#endif /* SMOOTH_ROTB */
#ifdef BSMOOTH
	     " (B)"
#endif /* BSMOOTH */
	     "\n");
#ifdef BSMOOTH
      printf("Flag_FullStep = %d, Main TimestepCounts = %d\n", Flag_FullStep, All.MainTimestepCounts);
#endif
    }
#ifdef BSMOOTH
  if(Flag_FullStep == 1)
    {
      if((All.MainTimestepCounts % All.BSmoothInt == 0) && (All.BSmoothInt >= 0))
	{
	  Smooth_Flag = 1;
	  if(ThisTask == 0)
	    printf("Smoothing B %d, %f\n", All.BSmoothInt, All.BSmoothFrac);
	}
      All.MainTimestepCounts++;
    }
#endif

  Ngblist = (int *) mymalloc(NumPart * sizeof(int));

  All.BunchSize =
    (int) ((All.BufferSize * 1024 * 1024) / (sizeof(struct data_index) + sizeof(struct data_nodelist) +
					     sizeof(struct smoothdata_in) + sizeof(struct smoothdata_out) +
					     sizemax(sizeof(struct smoothdata_in),
						     sizeof(struct smoothdata_out))));
  DataIndexTable = (struct data_index *) mymalloc(All.BunchSize * sizeof(struct data_index));
  DataNodeList = (struct data_nodelist *) mymalloc(All.BunchSize * sizeof(struct data_nodelist));

  CPU_Step[CPU_SMTHMISC] += measure_time();
  t0 = second();

  i = FirstActiveParticle;	/* begin with this index */

  do
    {
      for(j = 0; j < NTask; j++)
	{
	  Send_count[j] = 0;
	  Exportflag[j] = -1;
	}

      /* do local particles and prepare export list */
      tstart = second();
      for(nexport = 0; i >= 0; i = NextActiveParticle[i])
	{
	  if(density_isactive(i))
	    {
	      if(smoothed_evaluate(i, 0, &nexport, Send_count) < 0)
		break;
	    }
	}
      tend = second();
      timecomp1 += timediff(tstart, tend);

#ifdef MYSORT
      mysort_dataindex(DataIndexTable, nexport, sizeof(struct data_index), data_index_compare);
#else
      qsort(DataIndexTable, nexport, sizeof(struct data_index), data_index_compare);
#endif

      tstart = second();

      MPI_Allgather(Send_count, NTask, MPI_INT, Sendcount_matrix, NTask, MPI_INT, MPI_COMM_WORLD);

      tend = second();
      timewait1 += timediff(tstart, tend);

      for(j = 0, nimport = 0, Recv_offset[0] = 0, Send_offset[0] = 0; j < NTask; j++)
	{
	  Recv_count[j] = Sendcount_matrix[j * NTask + ThisTask];
	  nimport += Recv_count[j];

	  if(j > 0)
	    {
	      Send_offset[j] = Send_offset[j - 1] + Send_count[j - 1];
	      Recv_offset[j] = Recv_offset[j - 1] + Recv_count[j - 1];
	    }
	}

      SmoothDataGet = (struct smoothdata_in *) mymalloc(nimport * sizeof(struct smoothdata_in));
      SmoothDataIn = (struct smoothdata_in *) mymalloc(nexport * sizeof(struct smoothdata_in));

      /* prepare particle data for export */
      for(j = 0; j < nexport; j++)
	{
	  place = DataIndexTable[j].Index;

	  SmoothDataIn[j].Pos[0] = P[place].Pos[0];
	  SmoothDataIn[j].Pos[1] = P[place].Pos[1];
	  SmoothDataIn[j].Pos[2] = P[place].Pos[2];
	  SmoothDataIn[j].Hsml = PPP[place].Hsml;

	  memcpy(SmoothDataIn[j].NodeList,
		 DataNodeList[DataIndexTable[j].IndexGet].NodeList, NODELISTLENGTH * sizeof(int));
	}

      /* exchange particle data */
      tstart = second();
      for(ngrp = 1; ngrp < (1 << PTask); ngrp++)
	{
	  sendTask = ThisTask;
	  recvTask = ThisTask ^ ngrp;

	  if(recvTask < NTask)
	    {
	      if(Send_count[recvTask] > 0 || Recv_count[recvTask] > 0)
		{
		  /* get the particles */
		  MPI_Sendrecv(&SmoothDataIn[Send_offset[recvTask]],
			       Send_count[recvTask] * sizeof(struct smoothdata_in), MPI_BYTE,
			       recvTask, TAG_SMOOTH_A,
			       &SmoothDataGet[Recv_offset[recvTask]],
			       Recv_count[recvTask] * sizeof(struct smoothdata_in), MPI_BYTE,
			       recvTask, TAG_SMOOTH_A, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
		}
	    }
	}
      tend = second();
      timecommsumm1 += timediff(tstart, tend);

      myfree(SmoothDataIn);
      SmoothDataResult = (struct smoothdata_out *) mymalloc(nimport * sizeof(struct smoothdata_out));
      SmoothDataOut = (struct smoothdata_out *) mymalloc(nexport * sizeof(struct smoothdata_out));

      /* now do the particles that were sent to us */

      tstart = second();
      for(j = 0; j < nimport; j++)
	smoothed_evaluate(j, 1, &dummy, &dummy);
      tend = second();
      timecomp2 += timediff(tstart, tend);

      if(i < 0)
	ndone_flag = 1;
      else
	ndone_flag = 0;

      tstart = second();
      MPI_Allreduce(&ndone_flag, &ndone, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
      tend = second();
      timewait2 += timediff(tstart, tend);

      /* get the result */
      tstart = second();
      for(ngrp = 1; ngrp < (1 << PTask); ngrp++)
	{
	  sendTask = ThisTask;
	  recvTask = ThisTask ^ ngrp;
	  if(recvTask < NTask)
	    {
	      if(Send_count[recvTask] > 0 || Recv_count[recvTask] > 0)
		{
		  /* send the results */
		  MPI_Sendrecv(&SmoothDataResult[Recv_offset[recvTask]],
			       Recv_count[recvTask] * sizeof(struct smoothdata_out),
			       MPI_BYTE, recvTask, TAG_SMOOTH_B,
			       &SmoothDataOut[Send_offset[recvTask]],
			       Send_count[recvTask] * sizeof(struct smoothdata_out),
			       MPI_BYTE, recvTask, TAG_SMOOTH_B, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
		}
	    }
	}
      tend = second();
      timecommsumm2 += timediff(tstart, tend);

      /* add the result to the local particles */
      tstart = second();
      for(j = 0; j < nexport; j++)
	{
	  place = DataIndexTable[j].Index;

	  if(P[place].Type == 0)
	    {
#ifdef SMOOTH_PHI
	      SphP[place].SmoothPhi += SmoothDataOut[j].SmoothPhi;
	      SphP[place].SmoothDivB += SmoothDataOut[j].SmoothDivB;
#endif /* SMOOTH_PHI */

#ifdef SMOOTH_ROTB
	      SphP[place].SmoothedRotB[0] += SmoothDataOut[j].SmoothRotB[0];
	      SphP[place].SmoothedRotB[1] += SmoothDataOut[j].SmoothRotB[1];
	      SphP[place].SmoothedRotB[2] += SmoothDataOut[j].SmoothRotB[2];
#endif /* SMOOTH_ROTB */

#ifdef BSMOOTH
	      SphP[place].BSmooth[0] += SmoothDataOut[j].BSmooth[0];
	      SphP[place].BSmooth[1] += SmoothDataOut[j].BSmooth[1];
	      SphP[place].BSmooth[2] += SmoothDataOut[j].BSmooth[2];
#endif /* BSMOOTH */

	      SphP[place].DensityNorm += SmoothDataOut[j].DensityNorm;
	    }
	}
      tend = second();
      timecomp1 += timediff(tstart, tend);

      myfree(SmoothDataOut);
      myfree(SmoothDataResult);
      myfree(SmoothDataGet);
    }
  while(ndone < NTask);

  myfree(DataNodeList);
  myfree(DataIndexTable);
  myfree(Ngblist);

  /* do final operations on results */
  tstart = second();
  for(i = FirstActiveParticle; i >= 0; i = NextActiveParticle[i])
    {
      if(density_isactive(i))
	if(P[i].Type == 0)
	  {
#ifdef SMOOTH_PHI
	    SphP[i].SmoothPhi /= SphP[i].DensityNorm;
	    SphP[i].SmoothDivB /= SphP[i].DensityNorm;
#endif /* SMOOTH_PHI */

#ifdef SMOOTH_ROTB
	    SphP[i].SmoothedRotB[0] /= SphP[i].DensityNorm;
	    SphP[i].SmoothedRotB[1] /= SphP[i].DensityNorm;
	    SphP[i].SmoothedRotB[2] /= SphP[i].DensityNorm;
#endif /* SMOOTH_ROTB */

#ifdef BSMOOTH
	    SphP[i].BSmooth[0] /= SphP[i].DensityNorm;
	    SphP[i].BSmooth[1] /= SphP[i].DensityNorm;
	    SphP[i].BSmooth[2] /= SphP[i].DensityNorm;

	    if(Smooth_Flag == 1)
	      {
		dB[0] = All.BSmoothFrac * (SphP[i].BSmooth[0] - SphP[i].BPred[0]);
		dB[1] = All.BSmoothFrac * (SphP[i].BSmooth[1] - SphP[i].BPred[1]);
		dB[2] = All.BSmoothFrac * (SphP[i].BSmooth[2] - SphP[i].BPred[2]);
		SphP[i].BPred[0] += dB[0];
		SphP[i].BPred[1] += dB[1];
		SphP[i].BPred[2] += dB[2];
#ifndef EULERPOTENTIALS
		SphP[i].B[0] += dB[0];
		SphP[i].B[1] += dB[1];
		SphP[i].B[2] += dB[2];
#endif
	      }
#endif /* BSMOOTH */
	  }
    }
  tend = second();
  timecomp1 += timediff(tstart, tend);

  /* collect some timing information */

  t1 = WallclockTime = second();
  timeall += timediff(t0, t1);

  timecomp = timecomp1 + timecomp2;
  timewait = timewait1 + timewait2;
  timecomm = timecommsumm1 + timecommsumm2;

  CPU_Step[CPU_SMTHCOMPUTE] += timecomp;
  CPU_Step[CPU_SMTHWAIT] += timewait;
  CPU_Step[CPU_SMTHCOMM] += timecomm;
  CPU_Step[CPU_SMTHMISC] += timeall - (timecomp + timewait + timecomm);
}

/*! This function represents the core of the SPH density computation. The
*  target particle may either be local, or reside in the communication
*  buffer.
*/
int smoothed_evaluate(int target, int mode, int *nexport, int *nsend_local)
{
  int j, n, listindex = 0;
  int startnode, numngb_inbox;
  double h, h2, hinv, hinv3, hinv4;
  double wk, dwk;
  double dx, dy, dz, r, r2, u, mass_j;
  MyFloat *pos;
  double DensityNorm = 0;

#ifdef SMOOTH_PHI
  double SmoothPhi = 0.0;
  double SmoothDivB = 0.0;
#endif /* SMOOTH_PHI */

#ifdef SMOOTH_ROTB
  double smoothrotb[3];
#endif /* SMOOTH_ROTB */

#ifdef BSMOOTH
  double BSmooth[3];
#endif /* BSMOOTH */

#ifdef SMOOTH_ROTB
  smoothrotb[0] = smoothrotb[1] = smoothrotb[2] = 0;
#endif /* SMOOTH_ROTB */

#ifdef BSMOOTH
  BSmooth[0] = BSmooth[1] = BSmooth[2] = 0;
#endif /* BSMOOTH */

  if(mode == 0)
    {
      pos = P[target].Pos;
      h = PPP[target].Hsml;
    }
  else
    {
      pos = SmoothDataGet[target].Pos;
      h = SmoothDataGet[target].Hsml;
    }

  h2 = h * h;
  hinv = 1.0 / h;
#ifndef  TWODIMS
  hinv3 = hinv * hinv * hinv;
#else
  hinv3 = hinv * hinv / boxSize_Z;
#endif
  hinv4 = hinv3 * hinv;

  if(mode == 0)
    {
      startnode = All.MaxPart;	/* root node */
    }
  else
    {
      startnode = SmoothDataGet[target].NodeList[0];
      startnode = Nodes[startnode].u.d.nextnode;	/* open it */
    }

  while(startnode >= 0)
    {
      while(startnode >= 0)
	{
	  numngb_inbox = ngb_treefind_variable(&pos[0], h, target, &startnode, mode, nexport, nsend_local);

	  if(numngb_inbox < 0)
	    return -1;

	  for(n = 0; n < numngb_inbox; n++)
	    {
	      j = Ngblist[n];

	      dx = pos[0] - P[j].Pos[0];
	      dy = pos[1] - P[j].Pos[1];
	      dz = pos[2] - P[j].Pos[2];

#ifdef PERIODIC			/*  now find the closest image in the given box size  */
	      if(dx > boxHalf_X)
		dx -= boxSize_X;
	      if(dx < -boxHalf_X)
		dx += boxSize_X;
	      if(dy > boxHalf_Y)
		dy -= boxSize_Y;
	      if(dy < -boxHalf_Y)
		dy += boxSize_Y;
	      if(dz > boxHalf_Z)
		dz -= boxSize_Z;
	      if(dz < -boxHalf_Z)
		dz += boxSize_Z;
#endif
	      r2 = dx * dx + dy * dy + dz * dz;

	      if(r2 < h2)
		{
		  r = sqrt(r2);

		  u = r * hinv;

		  if(u < 0.5)
		    {
		      wk = hinv3 * (KERNEL_COEFF_1 + KERNEL_COEFF_2 * (u - 1) * u * u);
		      dwk = hinv4 * u * (KERNEL_COEFF_3 * u - KERNEL_COEFF_4);
		    }
		  else
		    {
		      wk = hinv3 * KERNEL_COEFF_5 * (1.0 - u) * (1.0 - u) * (1.0 - u);
		      dwk = hinv4 * KERNEL_COEFF_6 * (1.0 - u) * (1.0 - u);
		    }

		  mass_j = P[j].Mass;
		  wk /= SphP[j].d.Density;

#ifdef SMOOTH_PHI
		  SmoothPhi += mass_j * wk * SphP[j].PhiPred;
		  SmoothDivB += mass_j * wk * SphP[j].divB;
#endif /* SMOOTH_PHI */

#ifdef SMOOTH_ROTB
		  smoothrotb[0] += mass_j * wk * SphP[j].RotB[0];
		  smoothrotb[1] += mass_j * wk * SphP[j].RotB[1];
		  smoothrotb[2] += mass_j * wk * SphP[j].RotB[2];
#endif /* SMOOTH_ROTB */

#ifdef BSMOOTH
		  BSmooth[0] += mass_j * wk * SphP[j].BPred[0];
		  BSmooth[1] += mass_j * wk * SphP[j].BPred[1];
		  BSmooth[2] += mass_j * wk * SphP[j].BPred[2];
#endif /* BSMOOTH */

		  DensityNorm += mass_j * wk;
		}
	    }
	}
      if(mode == 1)
	{
	  listindex++;
	  if(listindex < NODELISTLENGTH)
	    {
	      startnode = SmoothDataGet[target].NodeList[listindex];
	      if(startnode >= 0)
		startnode = Nodes[startnode].u.d.nextnode;	/* open it */
	    }
	}
    }

  if(mode == 0)
    {
#ifdef SMOOTH_PHI
      SphP[target].SmoothPhi = SmoothPhi;
      SphP[target].SmoothDivB = SmoothDivB;
#endif /* SMOOTH_PHI */

#ifdef SMOOTH_ROTB
      SphP[target].SmoothedRotB[0] = smoothrotb[0];
      SphP[target].SmoothedRotB[1] = smoothrotb[1];
      SphP[target].SmoothedRotB[2] = smoothrotb[2];
#endif /* SMOOTH_ROTB */

#ifdef BSMOOTH
      SphP[target].BSmooth[0] = BSmooth[0];
      SphP[target].BSmooth[1] = BSmooth[1];
      SphP[target].BSmooth[2] = BSmooth[2];
#endif /* BSMOOTH */
      SphP[target].DensityNorm = DensityNorm;
    }
  else
    {
#ifdef SMOOTH_PHI
      SmoothDataResult[target].SmoothPhi = SmoothPhi;
      SmoothDataResult[target].SmoothDivB = SmoothDivB;
#endif /* SMOOTH_PHI */

#ifdef SMOOTH_ROTB
      SmoothDataResult[target].SmoothRotB[0] = smoothrotb[0];
      SmoothDataResult[target].SmoothRotB[1] = smoothrotb[1];
      SmoothDataResult[target].SmoothRotB[2] = smoothrotb[2];
#endif /* SMOOTH_ROTB */

#ifdef BSMOOTH
      SmoothDataResult[target].BSmooth[0] = BSmooth[0];
      SmoothDataResult[target].BSmooth[1] = BSmooth[1];
      SmoothDataResult[target].BSmooth[2] = BSmooth[2];
#endif /* BSMOOTH */
      SmoothDataResult[target].DensityNorm = DensityNorm;
    }

  return 0;
}

#endif


///
/// @file
///
/// @brief This file contains math functions that are shared among all
/// components of the library.
///
/// @author Mirko Myllykoski (mirkom@cs.umu.se), Umeå University
///
/// @internal LICENSE
///
/// Copyright (c) 2019-2020, Umeå Universitet
///
/// Redistribution and use in source and binary forms, with or without
/// modification, are permitted provided that the following conditions are met:
///
/// 1. Redistributions of source code must retain the above copyright notice,
///    this list of conditions and the following disclaimer.
///
/// 2. Redistributions in binary form must reproduce the above copyright notice,
///    this list of conditions and the following disclaimer in the documentation
///    and/or other materials provided with the distribution.
///
/// 3. Neither the name of the copyright holder nor the names of its
///    contributors may be used to endorse or promote products derived from this
///    software without specific prior written permission.
///
/// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
/// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
/// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
/// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
/// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
/// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
/// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
/// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
/// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
/// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
/// POSSIBILITY OF SUCH DAMAGE.
///

#include <starneig_config.h>
#include <starneig/configuration.h>
#include "math.h"
#include "common.h"
#include "sanity.h"
#include <stddef.h>
#include <string.h>
#include <cblas.h>

int starneig_largers_factor(int a, int b)
{
    while (b != 0) {
        int t = b;
        b = a % b;
        a = t;
    }
    return a;
}

void starneig_init_local_q(int n, size_t ldA, double *A)
{
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            A[i*ldA+j] = 0.0;

    for (int i = 0; i < n; i++)
        A[i*ldA+i] = 1.0;
}

void starneig_small_left_gemm_update(int rbegin, int rend, int cbegin, int cend,
    size_t ldQ, size_t ldA, size_t ldT, double const *Q, double *A, double *T) {

    STARNEIG_SANITY_CHECK_INF(rbegin, rend, cbegin, cend, ldA, A, "A (in)");
    STARNEIG_SANITY_CHECK_INF(0, rend-rbegin, 0, rend-rbegin, ldQ, Q, "Q");

    int m = rend-rbegin;
    int n = cend-cbegin;
    int k = rend-rbegin;

    if (m == 0 || n == 0)
        return;

    starneig_copy_matrix(
        k, n, ldA, ldT, sizeof(double), A+cbegin*ldA+rbegin, T);

    cblas_dgemm(
        CblasColMajor, CblasTrans, CblasNoTrans, m, n, k, 1.0, Q, ldQ, T, ldT,
        0.0, A+cbegin*ldA+rbegin, ldA);

    STARNEIG_SANITY_CHECK_INF(rbegin, rend, cbegin, cend, ldA, A, "A (out)");
}

void starneig_small_right_gemm_update(
    int rbegin, int rend, int cbegin, int cend,
    size_t ldQ, size_t ldA, size_t ldT, double const *Q, double *A, double *T) {

    STARNEIG_SANITY_CHECK_INF(rbegin, rend, cbegin, cend, ldA, A, "A (in)");
    STARNEIG_SANITY_CHECK_INF(0, cend-cbegin, 0, cend-cbegin, ldQ, Q, "Q");

    int m = rend-rbegin;
    int n = cend-cbegin;
    int k = cend-cbegin;

    if (m == 0 || n == 0)
        return;

    starneig_copy_matrix(
        m, k, ldA, ldT, sizeof(double), A+cbegin*ldA+rbegin, T);

    cblas_dgemm(
        CblasColMajor, CblasNoTrans, CblasNoTrans, m, n, k, 1.0, T, ldT, Q, ldQ,
        0.0, A+cbegin*ldA+rbegin, ldA);

    STARNEIG_SANITY_CHECK_INF(rbegin, rend, cbegin, cend, ldA, A, "A (out)");
}

void starneig_small_gemm_updates(
    int begin, int end, int n, size_t ldlQ, size_t ldlZ, size_t ldQ, size_t ldZ,
    size_t ldA, size_t ldB, size_t ldhT, size_t ldvT, double const *lQ,
    double const *lZ, double *Q, double *Z, double *A, double *B,
    double *hT, double *vT)
{
    // apply the local transformation matrices lQ and lZ to Q and Z
    if (Q != NULL)
        starneig_small_right_gemm_update(
            0, n, begin, end, ldlQ, ldQ, ldvT, lQ, Q, vT);
    if (Z != NULL && Z != Q)
        starneig_small_right_gemm_update(
            0, n, begin, end, ldlZ, ldZ, ldvT, lZ, Z, vT);

    // apply the local transformation matrices lQ and lZ to A
    if (A != NULL) {
        starneig_small_right_gemm_update(
            0, begin, begin, end, ldlZ, ldA, ldvT, lZ, A, vT);
        starneig_small_left_gemm_update(
            begin, end, end, n, ldlQ, ldA, ldhT, lQ, A, hT);
    }

    // apply the local transformation matrices lQ and lZ to B
    if (B != NULL) {
        starneig_small_right_gemm_update(
            0, begin, begin, end, ldlZ, ldB, ldvT, lZ, B, vT);
        starneig_small_left_gemm_update(
            begin, end, end, n, ldlQ, ldB, ldhT, lQ, B, hT);
    }
}

void starneig_compute_complex_eigenvalue(
    int ldA, int ldB, double const *A, double const *B,
    double *real1, double *imag1, double *real2, double *imag2,
    double *beta1, double *beta2)
{
    if (B != NULL) {
        extern void dlag2_(double const *, int const *, double const *,
            int const *, double const *, double*, double *, double *, double *,
            double *);

        extern double dlamch_(char const *);
        const double safmin = dlamch_("S");

        double _real1, _real2, _beta1, _beta2, wi;
        dlag2_(
            A, &ldA, B, &ldB, &safmin, &_beta1, &_beta2, &_real1, &_real2, &wi);

        if (beta1) {
            *real1 = _real1;
            *real2 = _real2;
            *imag1 = wi;
            *imag2 = -wi;
            *beta1 = _beta1;
            *beta2 = _beta2;
        }
        else {
            *real1 = _real1/_beta1;
            *real2 = _real2/_beta2;
            *imag1 = wi/_beta1;
            *imag2 = -wi/_beta2;
        }
    }
    else {
        extern void dlanv2_(
            double *, double *, double *, double *, double *,
            double *, double *, double *, double *, double *);

        double a[] = { A[0], A[1], A[ldA], A[ldA+1] };
        double cs, ss;

        dlanv2_(
            &a[0], &a[2], &a[1], &a[3], real1, imag1, real2, imag2, &cs, &ss);

        if (beta1) {
            *beta1 = 1.0;
            *beta2 = 1.0;
        }
    }
}


#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;
}

}


/**
 *
 * @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 <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];
    }
  }
}


/*
* -----------------------------------------------------------------
*  pmsr_lib.c
*  Pairwise Mixing Stirred Reactor Library
*  Version: 2.0
*  Last Update: Oct 1, 2019
*
*  This is a computational library with routines to implement
*  a Pairwise Mixing Stirred Reactor (PMSR) model.
* -----------------------------------------------------------------
*  Programmer: Americo Barbosa da Cunha Junior
*              americo.cunhajr@gmail.com
* -----------------------------------------------------------------
*  Copyright (c) 2019 by Americo Barbosa da Cunha Junior
* -----------------------------------------------------------------
*/

#include <math.h>
#include <time.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_errno.h>

#include "../include/pmsr_lib.h"

/*
* -----------------------------------------------------------------
*   pmsr_title
*
*   This function prints the program title on the screen.
*
*   last update: Oct 20, 2009
* -----------------------------------------------------------------
*/

void pmsr_title()
{
    time_t curtime = time (NULL);
    struct tm *loctime = localtime (&curtime);

    printf("\n");
    printf("=============================================");
    printf("\n Pairwise Mixing Stirred Reactor --- PMSR\n");
    printf("\n by");
    printf("\n Americo Barbosa da Cunha Junior");
    printf("\n americo.cunhajr@gmail.com\n");
    printf("\n Date:");
    printf("\n %s", asctime(loctime) );
    printf("=============================================");
    printf("\n");

    return;
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*   pmsr_input
*
*   This function receives PMSR parameters from user
*   and returns GSL_SUCCESS if there is no error.
*
*   Input:
*   seed         - RNG seed
*   Np           - # of particles
*   Ndt          - # of time steps
*   t0           - initial time
*   delta_t      - time step
*   tau_res      - residence time
*   tau_mix      - mixture time
*   tau_pair     - pairwise time
*
*   Output:
*   success or error
*
*   last update: Oct 1, 2019
* -----------------------------------------------------------------
*/

int pmsr_input(unsigned long int *seed,unsigned int *Np,unsigned int *Ndt,
	       double *t0,double *delta_t,double *tau_res,double *tau_mix,
	       double *tau_pair,double *Tmax,double *Tmin,
               double *atol,double *rtol)
{
    printf("\n Input PMSR parameters:\n");

    printf("\n RNG seed:");
    scanf("%lu", seed);
    printf("\n %lu\n", *seed);
    if( *seed <= 0 )
	*seed = time(NULL);

    printf("\n # of particles:");
    scanf("%d", Np);
    printf("\n %d\n", *Np);
    if( *Np <= 0 || *Np % 2 != 0 )
	GSL_ERROR(" Np must be a positive even integer",GSL_EINVAL);

    printf("\n # of time steps:");
    scanf("%d", Ndt);
    printf("\n %d\n", *Ndt);
    if( *Ndt <= 0 )
	GSL_ERROR(" Ndt must be a positive integer",GSL_EINVAL);

    printf("\n initial time:");
    scanf("%lf", t0);
    printf("\n %+.1e\n", *t0);
    if( *t0 < 0.0 )
	GSL_ERROR(" t0 must be a grather than zero",GSL_EINVAL);

    printf("\n time step:");
    scanf("%lf", delta_t);
    printf("\n %+.1e\n", *delta_t);
    if( *delta_t <= 0.0 )
	GSL_ERROR(" delta_t must be a grather than zero",GSL_EINVAL);

    printf("\n residence time:");
    scanf("%lf", tau_res);
    printf("\n %+.1e\n", *tau_res);
    if( *tau_res <= 0.0 )
	GSL_ERROR(" tau_res must be a grather than zero",GSL_EINVAL);

    printf("\n mixture time:");
    scanf("%lf", tau_mix);
    printf("\n %+.1e\n", *tau_mix);
    if( *tau_mix <= 0.0 )
	GSL_ERROR(" tau_mix must be a grather than zero",GSL_EINVAL);

    printf("\n pairwise time:");
    scanf("%lf", tau_pair);
    printf("\n %+.1e\n", *tau_pair);
    if( *tau_pair <= 0.0 )
	GSL_ERROR(" tau_pair must be a grather than zero",GSL_EINVAL);

    printf("\n temperature upper bound:");
    scanf("%lf", Tmax);
    printf("\n %+.1e\n", *Tmax);
    if( *Tmax <= 0.0 )
	GSL_ERROR(" Tmax must be a grather than zero",GSL_EINVAL);

    printf("\n temperature lower bound:");
    scanf("%lf", Tmin);
    printf("\n %+.1e\n", *Tmin);
    if( *Tmin < 0.0 || *Tmin > *Tmax )
	GSL_ERROR(" Tmin must be less than Tmax and a grather than zero",GSL_EINVAL);

    printf("\n absolute tolerance:");
    scanf("%lf", atol);
    printf("\n %+.1e\n", *atol);
    if( *atol <= 0.0 )
	GSL_ERROR(" atol must be a grather than zero",GSL_EINVAL);

    printf("\n relative tolerance:");
    scanf("%lf", rtol);
    printf("\n %+.1e\n", *rtol);
    if( *rtol <= 0.0 )
	GSL_ERROR(" rtol must be a grather than zero",GSL_EINVAL);

    return GSL_SUCCESS;
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*  pmsr_alloc
*
*  This function allocates an internal memory block for
*  pmsr workspace struct. If successful, returns a pointer to
*  pmsr_wrk. If a startup error occurs returns NULL.
*
*   last update: Nov 5, 2009
* -----------------------------------------------------------------
*/

pmsr_wrk *pmsr_alloc()
{
    pmsr_wrk *pmsr = NULL;

    /* memory allocation for pmsr struct */
    pmsr = (pmsr_wrk *) malloc(sizeof(pmsr_wrk));
    if ( pmsr == NULL )
        return NULL;

    /* setting pmsr_wrk elements */
    pmsr->pmsr_idx = NULL;
    pmsr->pmsr_ps  = NULL;

    return pmsr;
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*  pmsr_init
*
*  This function initiates the system of particles
*  with an initial composition phi.
*
*   Input:
*   Np   - # of particles
*   Neq  - # of equations
*   pmsr - pointer to a struct pmsr
*
*   Output:
*   success or error
*
*  last update: Sep 22, 2009
* -----------------------------------------------------------------
*/

int pmsr_init(int Np,int Neq,pmsr_wrk *pmsr)
{
    int i;

    /* checking if pmsr is NULL */
    if ( pmsr == NULL )
        return GSL_EINVAL;

    /* memory allocation for the system of particles */
    pmsr->pmsr_ps = (gsl_vector **) malloc(Np*sizeof(gsl_vector *));
    if ( pmsr->pmsr_ps == NULL )
	return GSL_ENOMEM;

    for ( i = 0; i < Np; i++ )
        pmsr->pmsr_ps[i] = gsl_vector_calloc(Neq);

    /* memory allocation for the vector of indices */
    pmsr->pmsr_idx = (int *) malloc(Np*sizeof(int));
    if ( pmsr->pmsr_idx == NULL )
    {
	for ( i = 0; i < Np; i++ )
	{
	    gsl_vector_free(pmsr->pmsr_ps[i]);
	    pmsr->pmsr_ps[i] = NULL;
	}

        free(pmsr->pmsr_ps);
	pmsr->pmsr_ps = NULL;

	return GSL_ENOMEM;
    }

    /* initial configuration of particles pairwise */
    for ( i = 0; i < Np; i++ )
        pmsr->pmsr_idx[i] = i;

    return GSL_SUCCESS;
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*   pmsr_free
*
*   This routine frees the memory allocated by pmsr_alloc.
*
*   Input:
*   pmsr - pointer to a struct pmsr
*
*   Output:
*   void
*
*   last update: May 10, 2009
* -----------------------------------------------------------------
*/

void pmsr_free(int Np,void **pmsr_bl)
{
    int i;
    pmsr_wrk *pmsr = NULL;

    /* checking if pmsr_bl is NULL */
    if ( *pmsr_bl == NULL )
        return;

    /* checking if Np is positive */
    if ( Np <= 0 )
        return;

    pmsr = (pmsr_wrk *) (*pmsr_bl);

    /* releasing pmsr elements */
    if ( pmsr->pmsr_ps != NULL )
    {
	for ( i = 0; i < Np; i++ )
	    if ( pmsr->pmsr_ps[i] != NULL )
	    {
	        gsl_vector_free(pmsr->pmsr_ps[i]);
	        pmsr->pmsr_ps[i] = NULL;
	    }
	/* releasing pmsr struct */
	free(pmsr->pmsr_ps);
	pmsr->pmsr_ps = NULL;
    }

    if ( pmsr->pmsr_idx != NULL )
    {
	free(pmsr->pmsr_idx);
	pmsr->pmsr_idx = NULL;
    }

    free(*pmsr_bl);
    *pmsr_bl = NULL;

    return;
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*  pmsr_set_all
*
*  This function sets the system of particles with a given
*  composition phi.
*
*   Input:
*   pmsr - pointer to a struct pmsr
*   phi  - composition
*
*   Output:
*   success or error
*
*  last update: Mar 2, 2010
* -----------------------------------------------------------------
*/

void pmsr_set_all(int Np,gsl_vector *phi,pmsr_wrk *pmsr)
{
    int i;

    /* setting all particles with an initial phi */
    for ( i = 0; i < Np; i++ )
        gsl_vector_memcpy(pmsr->pmsr_ps[i],phi);

    return;
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*  pmsr_Nexc
*
*  This function computes the number of particles to be exchanged,
*  Nexc, corresponding to outﬂow/inﬂow particles.
*
*  Nexc  = ceil( 1/2 Np delta_t/taus_res )
*
*   Input:
*   Np      - # of particles
*   delta_t - time step
*   tau_res - residence time
*
*   Output:
*   Nexc    - # of particles to be exchanged
*
*   last update: Feb 12, 2009
* -----------------------------------------------------------------
*/

int pmsr_Nexc(int Np,double delta_t,double tau_res)
{
    return ceil( (Np/2)*(delta_t/tau_res) );
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*  pmsr_Npair
*
*  This function computes the number of particles to be pairwised,
*  Npair, corresponding to pairing particles.
*
*  Npair = ceil( 1/2 Np delta_t/taus_pair )
*
*   Input:
*   Np       - # of particles
*   delta_t  - time step
*   tau_pair - pairwise time
*
*   Output:
*   Npair    - # of particles to be pairwised
*
*   last update: Feb 12, 2009
* -----------------------------------------------------------------
*/

int pmsr_Npair(int Np,double delta_t,double tau_pair)
{
    return ceil( (Np/2)*(delta_t/tau_pair) );
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*   pmsr_meanvar
*
*   This function computes the mean and the variance
*   of a particular property in a system of particles
*   using the algorithm of Knuth/Welford.
*
*   Ref.:
*   D. E. Knuth (1998)
*   The Art of Computer Programming,
*   volume 2: Seminumerical Algorithms
*   3rd ed., Addison-Wesley.
*
*   B. P. Welford (1962).
*   "Note on a method for calculating corrected sums
*   of squares and products". Technometrics 4(3):419–420
*
*   Input:
*   Np - # of particles
*   k  - property index
*   ps - system of particle
*
*   Output:
*   mean - propertie mean value
*   var  - propertie variance
*
*   last update: Mar 4, 2010
* -----------------------------------------------------------------
*/

void pmsr_meanvar(int Np,int k,gsl_vector **ps,double *mean,double *var)
{
    int i;
    double M     = 0.0;
    double S     = 0.0;
    double delta = 0.0;

    for ( i = 0; i < Np; i++ )
    {
        delta = ps[i]->data[k] - M;
	M    += delta/(double) (i+1);
        S    += delta*(ps[i]->data[k] - M);
    }

    /* mean */
    *mean  = M;

    /* variance */
    *var   = S/(double) (i-1);

    return;
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*   pmsr_mixture
*
*   This function computes the mixture step in PMSR model.
*
*   Input:
*   Np      - # of particles
*   Neq     - # of equations
*   delta_t - time step
*   tau_mix - mixture time
*   idx     - particles index vector
*
*   Output:
*   ps      - system of particles
*
*   last update: Mar 9, 2009
* -----------------------------------------------------------------
*/

void pmsr_mixture(int Np,int Neq,double delta_t,double tau_mix,
					    int *idx,gsl_vector **ps)
{
    int k, i;
    double sum, dif;

    /* particle index */
    k = 0;

    /* mixture of the Np particles */
    while( k < Np/2 )
    {
        /* mixture of two particles */
        for ( i = 0; i < Neq; i++ )
        {
            /* sum = 1/2(phi_p0 + phi_q0) */
            sum = 0.5*( ps[idx[2*k]]->data[i] + ps[idx[2*k+1]]->data[i] );

            /* dif = 1/2(phi_p0 - phi_q0) */
            dif = 0.5*( ps[idx[2*k]]->data[i] - ps[idx[2*k+1]]->data[i] );

            /* phi_p =  dif.exp(-2/tau delta_t) + sum */
            ps[idx[2*k]]->data[i] =
                        dif*exp(-(2.0/tau_mix)*delta_t) + sum;

            /* phi_q = -dif.exp(-2/tau delta_t) + sum */
            ps[idx[2*k+1]]->data[i] =
                        -dif*exp(-(2.0/tau_mix)*delta_t) + sum;
        }
        k++;
    }

    return;
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*   pmsr_iops
*
*   This function executes the inflow/outflow,
*   pairwise and shuflle steps.
*
*   Input:
*   Np      - # of particles
*   Nexc    - # of particles to be exchanged
*   Npair   - # of particles to be pairwised
*   rand    - rng workspace
*   phi     - inflow composition
*
*   Output:
*   p       - system of particles
*   pairs   - pairs of particles index
*
*   last update: Mar 1, 2010
* -----------------------------------------------------------------
*/

void pmsr_iops(int Np,int Nexc,int Npair,gsl_rng *rand,
				    gsl_vector *phi,pmsr_wrk *pmsr)
{
    int i;
    int idx1[Nexc+Npair];
    int idx2[2*(Nexc+Npair)];
    int *aux = NULL;

    /* memory allocation */
    aux = (int *) malloc((Np/2)*sizeof(int));

    /* setting aux vector with integers from 0 to Np/2-1 */
    for ( i = 0; i < Np/2; i++ )
        aux[i] = i;

    /* selecting Nexc + Npair pairs at random */
    gsl_ran_choose(rand,idx1,Nexc+Npair,aux,Np/2,sizeof(int));
    for ( i = 0; i < Nexc + Npair; i++ )
    {
        idx2[2*i  ] = pmsr->pmsr_idx[2*idx1[i]  ];
        idx2[2*i+1] = pmsr->pmsr_idx[2*idx1[i]+1];
    }

    /* changing composition of Nexc pairs (inflow / outflow) */
    for ( i = 0; i < Nexc; i++ )
    {
        gsl_vector_memcpy(pmsr->pmsr_ps[idx2[2*i]  ],phi);
        gsl_vector_memcpy(pmsr->pmsr_ps[idx2[2*i+1]],phi);
    }

    /* shuffling the selected pairs */
    gsl_ran_shuffle(rand,idx2,2*(Nexc+Npair),sizeof(int));

    /* setting the new partners in the system */
    for ( i = 0; i < Nexc + Npair; i++ )
    {
        pmsr->pmsr_idx[2*idx1[i]  ] = idx2[2*i  ];
        pmsr->pmsr_idx[2*idx1[i]+1] = idx2[2*i+1];
    }

    /* releasing allocated memory */
    free(aux);
    aux = NULL;

    return;
}
/*----------------------------------------------------------------*/