Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2025-08-05 08:15:39

0001 #include "AnnularFieldSim.h"
0002 
0003 #include "AnalyticFieldModel.h"
0004 #include "ChargeMapReader.h"
0005 #include "MultiArray.h"  //for TH3 alternative
0006 #include "Rossegger.h"
0007 
0008 #include <TCanvas.h>
0009 #include <TFile.h>
0010 #include <TH1.h>
0011 #include <TH2.h>
0012 #include <TH3.h>
0013 #include <TLatex.h>
0014 #include <TPad.h>
0015 #include <TStyle.h>
0016 #include <TTree.h>
0017 #include <TVector3.h>
0018 
0019 #include <boost/format.hpp>
0020 
0021 #include <cassert>  // for assert
0022 #include <cmath>
0023 #include <cstdint>
0024 #include <cstdlib>
0025 #include <iostream>
0026 
0027 #define ALMOST_ZERO 0.00001
0028 
0029 AnnularFieldSim::AnnularFieldSim(float in_innerRadius, float in_outerRadius, float in_outerZ,
0030                                  int r, int roi_r0, int roi_r1, int /*in_rLowSpacing*/, int /*in_rHighSize*/,
0031                                  int phi, int roi_phi0, int roi_phi1, int /*in_phiLowSpacing*/, int /*in_phiHighSize*/,
0032                                  int z, int roi_z0, int roi_z1, int /*in_zLowSpacing*/, int /*in_zHighSize*/,
0033                                  float vdr, LookupCase in_lookupCase, ChargeCase in_chargeCase)
0034 {
0035   // check well-ordering:
0036   if (roi_r0 >= r || roi_r1 > r || roi_r0 >= roi_r1)
0037   {
0038     exit(1);
0039   }
0040   if (roi_phi0 >= phi || roi_phi1 > phi || roi_phi0 >= roi_phi1)
0041   {
0042     std::cout << "phi roi is out of range or spans the wrap-around.  Please spare me that math." << std::endl;
0043     exit(1);
0044   }
0045   if (roi_z0 >= z || roi_z1 > z || roi_z0 >= roi_z1)
0046   {
0047     exit(1);
0048   }
0049 
0050   hasTwin = false;  // we never have a twin to start with.
0051   green_shift = 0;  // and so we don't shift our greens functions unless told to.
0052 
0053   std::cout << boost::str(boost::format("AnnularFieldSim::AnnularFieldSim with (%dx%dx%d) grid") % r % phi % z) << std::endl;
0054   std::cout << boost::str(boost::format("units m=%1.2E, cm=%1.2E, mm=%1.2E, um=%1.2E,") % m % cm % mm % um) << std::endl;
0055   std::cout << boost::str(boost::format("units s=%1.2E, us=%1.2E, ns=%1.2E,") % s % us % ns) << std::endl;
0056   std::cout << boost::str(boost::format("units C=%1.2E, nC=%1.2E, fC=%1.2E, ") % C % nC % fC) << std::endl;
0057   std::cout << boost::str(boost::format("units Tesla=%1.2E, kGauss=%1.2E") % Tesla % kGauss) << std::endl;
0058 
0059   // debug defaults:
0060   //
0061   debug_printActionEveryN = -1;
0062   debug_printCounter = 0;
0063   debug_distortionScale.SetXYZ(0, 0, 0);
0064   debug_npercent = 5;
0065 
0066   // internal states used for the debug flag
0067   // goes with: if(debugFlag()) print_need_cout("%d: blah\n",__LINE__);
0068 
0069   // load constants of motion, dimensions, etc:
0070   Enominal = 400 * V / cm;  // v/cm
0071   Bnominal = 1.4 * Tesla;   // Tesla
0072   vdrift = vdr * cm / s;    // cm/s
0073   langevin_T1 = 1.0;
0074   langevin_T2 = 1.0;
0075   omegatau_nominal = -10 * (Bnominal / kGauss) * (vdrift / (cm / us)) / (Enominal / (V / cm));  // to match to the familiar formula
0076 
0077   rmin = in_innerRadius * cm;
0078   rmax = in_outerRadius * cm;
0079   zmin = 0;
0080   zmax = in_outerZ * cm;
0081   if (zmin > zmax)
0082   {
0083     zmin = zmax;
0084     zmax = 0;  // for now, we continue to assume one end of the TPC is at z=0.
0085   }
0086   zero_vector.SetXYZ(0, 0, 0);
0087 
0088   // define the size of the volume:
0089   dim.SetXYZ(1, 0, 0);
0090   dim.SetPerp(rmax - rmin);
0091   dim.SetPhi(0);
0092   phispan = 2 * M_PI;
0093   dim.SetZ(zmax - zmin);
0094 
0095   // set the green's functions model:
0096   // UseFreeSpaceGreens();
0097   // blah
0098   green = nullptr;
0099 
0100   // load parameters of the whole-volume tiling
0101   nr = r;
0102   nphi = phi;
0103   nz = z;  // number of fundamental bins (f-bins) in each direction
0104   std::cout << boost::str(boost::format("AnnularFieldSim::AnnularFieldSim set variables nr=%d, nphi=%d, nz=%d") % nr % nphi % nz) << std::endl;
0105   // and set the default truncation distance:
0106   truncation_length = -1;  // anything <1 and it won't truncate.
0107 
0108   // calculate the size of an f-bin:
0109   // note that you have to set a non-zero value to start or perp won't update.
0110   step.SetXYZ(1, 0, 0);
0111   step.SetPerp(dim.Perp() / nr);
0112   step.SetPhi(phispan / nphi);
0113   step.SetZ(dim.Z() / nz);
0114   std::cout << boost::str(boost::format("f-bin size:  r=%f,phi=%f, z=%f, wanted %f,%f") % step.Perp() % step.Phi() % ((rmax - rmin) / nr) % (phispan / nphi) % ((zmax - zmin) / nz)) << std::endl;
0115 
0116   // create an array to store the charge in each f-bin
0117   //  q = new MultiArray<double>(nr, nphi, nz);
0118   // q->SetAll(0);
0119   q = new ChargeMapReader(nr, rmin, rmax, nphi, 0, phispan, nz, zmin, zmax);
0120   chargestring = "No spacecharge present.";
0121 
0122   // load parameters of our region of interest
0123   rmin_roi = roi_r0;
0124   phimin_roi = roi_phi0;
0125   zmin_roi = roi_z0;  // lower edge of our region of interest, measured in f-bins
0126   rmax_roi = roi_r1;
0127   phimax_roi = roi_phi1;
0128   zmax_roi = roi_z1;  // exlcuded upper edge of our region of interest, measured in f-bins
0129   std::cout << boost::str(boost::format("AnnularFieldSim::AnnularFieldSim set roi variables rmin=%d phimin=%d zmin=%d rmax=%d phimax=%d zmax=%d") % rmin_roi % phimin_roi % zmin_roi % rmax_roi % phimax_roi % zmax_roi) << std::endl;
0130   // calculate the dimensions, in f-bins in our region of interest
0131   nr_roi = rmax_roi - rmin_roi;
0132   nphi_roi = phimax_roi - phimin_roi;
0133   nz_roi = zmax_roi - zmin_roi;
0134   std::cout << boost::str(boost::format("AnnularFieldSim::AnnularFieldSim calc'd roi variables nr=%d nphi=%d nz=%d") % nr_roi % nphi_roi % nz_roi) << std::endl;
0135 
0136   // create an array to hold the SC-induced electric field in the roi with the specified dimensions
0137   Efield = new MultiArray<TVector3>(nr_roi, nphi_roi, nz_roi);
0138   for (int i = 0; i < Efield->Length(); i++)
0139   {
0140     Efield->GetFlat(i)->SetXYZ(0, 0, 0);
0141   }
0142 
0143   // and to hold the external electric fieldmap over the region of interest
0144   Eexternal = new MultiArray<TVector3>(nr_roi, nphi_roi, nz_roi);
0145   for (int i = 0; i < Eexternal->Length(); i++)
0146   {
0147     Eexternal->GetFlat(i)->SetXYZ(0, 0, 0);
0148   }
0149 
0150   // ditto the external magnetic fieldmap
0151   Bfield = new MultiArray<TVector3>(nr_roi, nphi_roi, nz_roi);
0152   for (int i = 0; i < Bfield->Length(); i++)
0153   {
0154     Bfield->GetFlat(i)->SetXYZ(0, 0, 0);
0155   }
0156 
0157   // handle the lookup table construction:
0158   lookupCase = in_lookupCase;
0159   chargeCase = in_chargeCase;
0160   if (chargeCase == ChargeCase::NoSpacecharge)
0161   {
0162     lookupCase = LookupCase::NoLookup;  // don't build a lookup model if there's no charge.  It just wastes time.
0163   }
0164 
0165   if (lookupCase == Full3D)
0166   {
0167     std::cout << boost::str(boost::format("AnnularFieldSim::AnnularFieldSim building Epartial (full3D) with  nr_roi=%d nphi_roi=%d nz_roi=%d  =~%2.2fM TVector3 objects") % nr_roi % nphi_roi % nz_roi % (nr_roi * nphi_roi * nz_roi * nr * nphi * nz / (1.0e6))) << std::endl;
0168 
0169     Epartial = new MultiArray<TVector3>(nr_roi, nphi_roi, nz_roi, nr, nphi, nz);
0170     for (int i = 0; i < Epartial->Length(); i++)
0171     {
0172       Epartial->GetFlat(i)->SetXYZ(0, 0, 0);
0173     }
0174     // and kill the arrays we shouldn't be using:
0175     Epartial_highres = new MultiArray<TVector3>(1);
0176     Epartial_highres->GetFlat(0)->SetXYZ(0, 0, 0);
0177 
0178     Epartial_lowres = new MultiArray<TVector3>(1);
0179     Epartial_lowres->GetFlat(0)->SetXYZ(0, 0, 0);
0180 
0181     Epartial_phislice = new MultiArray<TVector3>(1);
0182     Epartial_phislice->GetFlat(0)->SetXYZ(0, 0, 0);
0183     q_lowres = new MultiArray<double>(1);
0184     *(q_lowres->GetFlat(0)) = 0;
0185     q_local = new MultiArray<double>(1);
0186     *(q_local->GetFlat(0)) = 0;
0187   }
0188   else if (lookupCase == HybridRes)
0189   {
0190     std::cout << "lookupCase==HybridRes" << std::endl;
0191     // zero out the other two:
0192     Epartial = new MultiArray<TVector3>(1);
0193     Epartial->GetFlat(0)->SetXYZ(0, 0, 0);
0194 
0195     Epartial_phislice = new MultiArray<TVector3>(1);
0196     Epartial_phislice->GetFlat(0)->SetXYZ(0, 0, 0);
0197   }
0198   else if (lookupCase == PhiSlice)
0199   {
0200     std::cout << "lookupCase==PhiSlice" << std::endl;
0201 
0202     Epartial_phislice = new MultiArray<TVector3>(nr_roi, 1, nz_roi, nr, nphi, nz);
0203     for (int i = 0; i < Epartial_phislice->Length(); i++)
0204     {
0205       Epartial_phislice->GetFlat(i)->SetXYZ(0, 0, 0);
0206     }
0207     // zero out the other two:
0208     Epartial = new MultiArray<TVector3>(1);
0209     Epartial->GetFlat(0)->SetXYZ(0, 0, 0);
0210     Epartial_highres = new MultiArray<TVector3>(1);
0211     Epartial_highres->GetFlat(0)->SetXYZ(0, 0, 0);
0212 
0213     Epartial_lowres = new MultiArray<TVector3>(1);
0214     Epartial_lowres->GetFlat(0)->SetXYZ(0, 0, 0);
0215 
0216     q_lowres = new MultiArray<double>(1);
0217     *(q_lowres->GetFlat(0)) = 0;
0218     q_local = new MultiArray<double>(1);
0219     *(q_local->GetFlat(0)) = 0;
0220   }
0221   else if (lookupCase == Analytic || lookupCase == NoLookup)
0222   {
0223     std::cout << "lookupCase==Analytic (or NoLookup)" << std::endl;
0224 
0225     // zero them all out:
0226     Epartial_phislice = new MultiArray<TVector3>(1);
0227     Epartial_phislice->GetFlat(0)->SetXYZ(0, 0, 0);
0228 
0229     Epartial = new MultiArray<TVector3>(1);
0230     Epartial->GetFlat(0)->SetXYZ(0, 0, 0);
0231 
0232     Epartial_highres = new MultiArray<TVector3>(1);
0233     Epartial_highres->GetFlat(0)->SetXYZ(0, 0, 0);
0234 
0235     Epartial_lowres = new MultiArray<TVector3>(1);
0236     Epartial_lowres->GetFlat(0)->SetXYZ(0, 0, 0);
0237 
0238     q_lowres = new MultiArray<double>(1);
0239     *(q_lowres->GetFlat(0)) = 0;
0240     q_local = new MultiArray<double>(1);
0241     *(q_local->GetFlat(0)) = 0;
0242   }
0243   else
0244   {
0245     std::cout << "Ran into wrong lookupCase logic in constructor." << std::endl;
0246     exit(1);
0247   }
0248 
0249   return;
0250 }
0251 AnnularFieldSim::AnnularFieldSim(float in_innerRadius, float in_outerRadius, float in_outerZ,
0252                                  int r, int roi_r0, int roi_r1, int in_rLowSpacing, int in_rHighSize,
0253                                  int phi, int roi_phi0, int roi_phi1, int in_phiLowSpacing, int in_phiHighSize,
0254                                  int z, int roi_z0, int roi_z1, int in_zLowSpacing, int in_zHighSize,
0255                                  float vdr, LookupCase in_lookupCase)
0256   : AnnularFieldSim(in_innerRadius, in_outerRadius, in_outerZ,
0257                     r, roi_r0, roi_r1, in_rLowSpacing, in_rHighSize,
0258                     phi, roi_phi0, roi_phi1, in_phiLowSpacing, in_phiHighSize,
0259                     z, roi_z0, roi_z1, in_zLowSpacing, in_zHighSize,
0260                     vdr, in_lookupCase, ChargeCase::FromFile)
0261 {
0262   std::cout << "AnnularFieldSim::OldConstructor building AnnularFieldSim with ChargeCase::FromFile" << std::endl;
0263   return;
0264 }
0265 AnnularFieldSim::AnnularFieldSim(float in_innerRadius, float in_outerRadius, float in_outerZ,
0266                                  int r, int roi_r0, int roi_r1,
0267                                  int phi, int roi_phi0, int roi_phi1,
0268                                  int z, int roi_z0, int roi_z1,
0269                                  float vdr, LookupCase in_lookupCase)
0270   : AnnularFieldSim(in_innerRadius, in_outerRadius, in_outerZ,
0271                     r, roi_r0, roi_r1, 1, 3,
0272                     phi, roi_phi0, roi_phi1, 1, 3,
0273                     z, roi_z0, roi_z1, 1, 3,
0274                     vdr, in_lookupCase, ChargeCase::FromFile)
0275 {
0276   // just passing through for the old version again
0277   // creates a region with high-res size of 3 (enough to definitely cover the eight local l-bins) and low-res spacing of 1, which ought to match the behavior (with a little more overhead) from when there was no highres-lowres distinction
0278   std::cout << "AnnularFieldSim::OldConstructor building AnnularFieldSim with local_size=1 in all dimensions, lowres_spacing=1 in all dimensions" << std::endl;
0279   return;
0280 }
0281 AnnularFieldSim::AnnularFieldSim(float rin, float rout, float dz, int r, int phi, int z, float vdr)
0282   : AnnularFieldSim(rin, rout, dz,
0283                     r, 0, r,
0284                     phi, 0, phi,
0285                     z, 0, z,
0286                     vdr, LookupCase::PhiSlice)
0287 {
0288   // just a pass-through to go from the old style to the more detailed version.
0289   std::cout << "AnnularFieldSim::OldConstructor building AnnularFieldSim with roi=full in all dimensions" << std::endl;
0290   return;
0291 }
0292 
0293 TVector3 AnnularFieldSim::calc_unit_field(TVector3 at, TVector3 from)
0294 {
0295   // if(debugFlag()) print_need_cout("%d: AnnularFieldSim::calc_unit_field(at=(r=%f,phi=%f,z=%f))\n",__LINE__,at.Perp(),at.Phi(),at.Z());
0296   int r_position;
0297   if (GetRindexAndCheckBounds(at.Perp(), &r_position) != InBounds)
0298   {
0299     std::cout << boost::str(boost::format("something's asking for 'at' with r=%f, which is index=%d") % at.Perp() % r_position) << std::endl;
0300     exit(1);
0301   }
0302   // note this is the field due to a fixed point charge in free space.
0303   // if doing cylindrical calcs with different boundary conditions, this needs to change.
0304 
0305   // this could check roi bounds before returning, if things start acting funny.
0306 
0307   TVector3 field(0, 0, 0);
0308 
0309   // if we're more than truncation_length away, don't bother? rcc food for thought.  right now _length is in bins, so tricky.
0310 
0311   // if the green's function class is not present use free space:
0312   if (green == nullptr)
0313   {
0314     TVector3 delr = at - from;
0315     field = delr;  // to set the direction.
0316     if (delr.Mag() < ALMOST_ZERO * ALMOST_ZERO)
0317     {  // note that this has blurred units -- it should scale with all three dimensions of stepsize.  For lots of phi bins, especially, this might start to read as small before it's really small.
0318        // do nothing.  the vector is already zero, which will be our approximation.
0319        // field.SetMag(0);//no contribution if we're in the same cell. -- but root warns if trying to resize something of magnitude zero.
0320     }
0321     else
0322     {
0323       field.SetMag(k_perm * 1 / (delr * delr));  // scalar product on the bottom. unitful, since we defined k_perm and delr with their correct units. (native units V=1 C=1 cm=1)
0324     }
0325     // print_need_cout("calc_unit_field at (%2.2f,%2.2f,%2.2f) from  (%2.2f,%2.2f,%2.2f).  Mag=%2.4fe-9\n",at.x(),at.Y(),at.Z(),from.X(),from.Y(),from.Z(),field.Mag()*1e9);
0326   }
0327   else
0328   {
0329     double atphi = FilterPhiPos(at.Phi());
0330     double fromphi = FilterPhiPos(from.Phi());
0331     double delphi = std::abs(at.DeltaPhi(from));
0332     // to allow us to use the same greens function set for both sides of the tpc, we shift into the valid greens region if needed:
0333     at.SetZ(at.Z() + green_shift);
0334     from.SetZ(from.Z() + green_shift);
0335     double Er = green->Er(at.Perp(), atphi, at.Z(), from.Perp(), fromphi, from.Z());
0336     // don't try to compute Ephi if the delta phi is too small, since it will be zero (and will have a div0 somewhere):
0337     double Ephi = 0;
0338     if (delphi > ALMOST_ZERO)
0339     {
0340       Ephi = green->Ephi(at.Perp(), atphi, at.Z(), from.Perp(), fromphi, from.Z());
0341     }
0342     double Ez = green->Ez(at.Perp(), atphi, at.Z(), from.Perp(), fromphi, from.Z());
0343     field.SetXYZ(-Er, -Ephi, -Ez);  // now these are the correct components if our test point is at y=0 (hence phi=0);
0344     field = field * epsinv;         // scale field strength, since the greens functions as of Apr 1 2020 do not build-in this factor.
0345     field.RotateZ(at.Phi());        // rotate to the coordinates of our 'at' point, which is a small rotation for the phislice case.
0346     // but this does mean we need to be careful! If we are rotating so that this is pointing not in the R direction, then with azimuthally symmetric charge we will have some cancellation we don't want, maybe?  Make sure this matches how we rotate to sum_field!
0347   }
0348   return field;
0349 }
0350 
0351 
0352 
0353 TVector3 AnnularFieldSim::GetLocalFieldComponents(const TVector3 &field, const TVector3 &pos, const TVector3 &origin)
0354 {
0355   // this function returns the components of the field in the local coordinate system, which is in cylindrical coords
0356   // and has a specified origin.
0357   // it assumes that the field and all coordinates are given in the global, cartesian coordinate system.
0358   // it returns a TVector3 with the radial, azimuthal, and z components of the field in the local coordinate system.
0359 
0360   TVector3 local_pos = pos - origin;  // shift the position to the local coordinate system
0361   //get the cylindrical radial vector pointing from the origin to the position:
0362   TVector3 radial_vector = local_pos;
0363   radial_vector.SetZ(0);  // zero out the z component, so we have a radial vector in the xy plane.
0364   if(radial_vector.Mag()==0){
0365     //if it's zero, make it arbitrary direction.
0366     radial_vector.SetXYZ(1, 0, 0);  // set it to point in the x direction.
0367   }else {
0368   radial_vector.SetMag(1);  // normalize it to unit length, so we can use it to rotate the field vector.
0369   }
0370   // get the cylindrical azimuthal vector, which is perpendicular to the radial vector:
0371   TVector3 azimuthal_vector = radial_vector;
0372   azimuthal_vector.SetXYZ(-radial_vector.Y(), radial_vector.X(), 0);  // rotate by 90 degrees to get the azimuthal vector in the xy plane.
0373   // now we can project the field vector onto the radial and azimuthal vectors to get the components in the local coordinate system, and return a TVector3 with those components:
0374   TVector3 local_field;
0375   local_field.SetXYZ(field.Dot(radial_vector), field.Dot(azimuthal_vector), field.Z());  // radial, azimuthal, and z components
0376   return local_field;
0377 }
0378 
0379 
0380 double AnnularFieldSim::FilterPhiPos(double phi)
0381 {
0382   // this primarily takes the region [-pi,0] and maps it to [pi,2pi] by adding 2pi to it.
0383   // if math has pushed us past 2pi, it also subtracts to try to get us in range.
0384   double p = phi;
0385   if (p >= 2 * M_PI)  // phispan)
0386   {
0387     p -= 2 * M_PI;
0388   }
0389   if (p < 0)
0390   {
0391     p += 2 * M_PI;
0392   }
0393   if (p >= 2 * M_PI || p < 0)
0394   {
0395     std::cout << boost::str(boost::format("AnnularFieldSim::FilterPhiPos asked to filter %f, which is more than range=%f out of bounds.  Check what called this.") % phi % (2 * M_PI)) << std::endl;
0396     exit(1);
0397   }
0398   return p;
0399 }
0400 int AnnularFieldSim::FilterPhiIndex(int phi, int range = -1)
0401 {
0402   if (range < 0)
0403   {
0404     range = nphi;  // default input is range=-1.  in that case, use the intrinsic resolution of the q grid.
0405   }
0406 
0407   // shifts phi up or down by nphi (=2pi in phi index space), and complains if this doesn't put it in range.
0408   int p = phi;
0409   if (p >= range)
0410   {
0411     p -= range;
0412   }
0413   if (p < 0)
0414   {
0415     p += range;
0416   }
0417   if (p >= range || p < 0)
0418   {
0419     std::cout << boost::str(boost::format("AnnularFieldSim::FilterPhiIndex asked to filter %d, which is more than range=%d out of bounds.  Check what called this.") % phi % range) << std::endl;
0420     exit(1);
0421   }
0422   return p;
0423 }
0424 
0425 int AnnularFieldSim::GetRindex(float pos)
0426 {
0427   float r0f = (pos - rmin) / step.Perp();  // the position in r, in units of step, starting from the low edge of the 0th bin.
0428   int r0 = std::floor(r0f);
0429   return r0;
0430 }
0431 
0432 int AnnularFieldSim::GetPhiIndex(float pos)
0433 {
0434   float p0f = (pos) / step.Phi();  // the position in phi, in units of step, starting from the low edge of the 0th bin.
0435   int phitemp = std::floor(p0f);
0436   int p0 = FilterPhiIndex(phitemp);
0437   return p0;
0438 }
0439 
0440 int AnnularFieldSim::GetZindex(float pos)
0441 {
0442   float z0f = (pos - zmin) / step.Z();  // the position in z, in units of step, starting from the low edge of the 0th bin.
0443   int z0 = std::floor(z0f);
0444   return z0;
0445 }
0446 
0447 AnnularFieldSim::BoundsCase AnnularFieldSim::GetRindexAndCheckBounds(float pos, int *r)
0448 {
0449   // if(debugFlag()) print_need_cout("%d: AnnularFieldSim::GetRindexAndCheckBounds(r=%f)\n",__LINE__,pos);
0450 
0451   float r0f = (pos - rmin) / step.Perp();  // the position in r, in units of step, starting from the low edge of the 0th bin.
0452   int r0 = std::floor(r0f);
0453   *r = r0;
0454 
0455   int r0lowered_slightly = std::floor(r0f - ALMOST_ZERO);
0456   int r0raised_slightly = std::floor(r0f + ALMOST_ZERO);
0457   if (r0lowered_slightly >= rmax_roi || r0raised_slightly < rmin_roi)
0458   {
0459     return OutOfBounds;
0460   }
0461 
0462   // now if we are out of bounds, it is because we are on an edge, within range of ALMOST_ZERO of being in fair territory.
0463   if (r0 >= rmax_roi)
0464   {
0465     return OnHighEdge;
0466   }
0467   if (r0 < rmin_roi)
0468   {
0469     return OnLowEdge;
0470   }
0471   // if we're still here, we're in bounds.
0472   return InBounds;
0473 }
0474 AnnularFieldSim::BoundsCase AnnularFieldSim::GetPhiIndexAndCheckBounds(float pos, int *phi)
0475 {
0476   // if(debugFlag()) print_need_cout("%d: AnnularFieldSim::GetPhiIndexAndCheckBounds(phi=%f)\n\n",__LINE__,pos);
0477   float p0f = (pos) / step.Phi();  // the position in phi, in units of step, starting from the low edge of the 0th bin.
0478   int phitemp = std::floor(p0f);
0479   int p0 = FilterPhiIndex(phitemp);
0480   *phi = p0;
0481 
0482   phitemp = std::floor(p0f - ALMOST_ZERO);
0483   int p0lowered_slightly = FilterPhiIndex(phitemp);
0484   phitemp = std::floor(p0f + ALMOST_ZERO);
0485   int p0raised_slightly = FilterPhiIndex(phitemp);
0486   // annoying detail:  if we are at index 0, we might go above pmax by going down.
0487   //  and if we are at nphi-1, we might go below pmin by going up.
0488   //  if we are not at p0=0 or nphi-1, the slight wiggles won't move us.
0489   //  if we are at p0=0, we are not at or above the max, and lowering slightly won't change that,
0490   //  and is we are at p0=nphi-1, we are not below the min, and raising slightly won't change that
0491   if ((p0lowered_slightly >= phimax_roi && p0 != 0) || (p0raised_slightly < phimin_roi && p0 != (nphi - 1)))
0492   {
0493     return OutOfBounds;
0494   }
0495   // now if we are out of bounds, it is because we are on an edge, within range of ALMOST_ZERO of being in fair territory.
0496   if (p0 >= phimax_roi)
0497   {
0498     return OnHighEdge;
0499   }
0500   if (p0 < phimin_roi)
0501   {
0502     return OnLowEdge;
0503   }
0504   // if we're still here, we're in bounds.
0505   return InBounds;
0506 }
0507 
0508 AnnularFieldSim::BoundsCase AnnularFieldSim::GetZindexAndCheckBounds(float pos, int *z)
0509 {
0510   // if(debugFlag()) print_need_cout("%d: AnnularFieldSim::GetZindexAndCheckBounds(z=%f)\n\n",__LINE__,pos);
0511   float z0f = (pos - zmin) / step.Z();  // the position in z, in units of step, starting from the low edge of the 0th bin.
0512   int z0 = std::floor(z0f);
0513   *z = z0;
0514 
0515   int z0lowered_slightly = std::floor(z0f - ALMOST_ZERO);
0516   int z0raised_slightly = std::floor(z0f + ALMOST_ZERO);
0517 
0518   if (z0lowered_slightly >= zmax_roi || z0raised_slightly < zmin_roi)
0519   {
0520     return OutOfBounds;
0521   }
0522   // now if we are out of bounds, it is because we are on an edge, within range of ALMOST_ZERO of being in fair territory.
0523   if (z0 >= zmax_roi)
0524   {
0525     return OnHighEdge;
0526   }
0527   if (z0 < zmin_roi)
0528   {
0529     return OnLowEdge;
0530   }
0531   // if we're still here, we're in bounds.
0532   return InBounds;
0533 }
0534 
0535 TVector3 AnnularFieldSim::analyticFieldIntegral(float zdest, TVector3 start, MultiArray<TVector3> *field)
0536 {
0537   // integrates E dz, from the starting point to the selected z position.  The path is assumed to be along z for each step, with adjustments to x and y accumulated after each step.
0538   // if(debugFlag()) print_need_cout("%d: AnnularFieldSim::fieldIntegral(x=%f,y=%f, z=%f) to z=%f\n\n",__LINE__,start.X(),start.Y(),start.Z(),zdest);
0539   //   print_need_cout("AnnularFieldSim::analyticFieldIntegral calculating from (%f,%f,%f) (rphiz)=(%f,%f,%f) to z=%f.\n",start.X(),start.Y(),start.Z(),start.Perp(),start.Phi(),start.Z(),zdest);
0540 
0541   // coordinates are assumed to be in native units (cm=1);
0542 
0543   int r, phi;
0544   bool rOkay = (GetRindexAndCheckBounds(start.Perp(), &r) == InBounds);
0545   bool phiOkay = (GetPhiIndexAndCheckBounds(FilterPhiPos(start.Phi()), &phi) == InBounds);
0546 
0547   // bool isE=(field==Efield);
0548   // bool isB=(field==Bfield);
0549 
0550   // print_need_cout("anaFieldInt:  isE=%d, isB=%d, start=(%f,%f,%f)\n",isE,isB,start.X(),start.Y(),start.Z());
0551 
0552   if (!rOkay || !phiOkay)
0553   {
0554     std::cout << boost::str(boost::format("AnnularFieldSim::analyticFieldIntegral asked to integrate along (r=%f,phi=%f), index=(%d,%d), which is out of bounds.  returning starting position.") % start.Perp() % start.Phi() % r % phi) << std::endl;
0555     return start;
0556   }
0557 
0558   int dir = (start.Z() > zdest ? -1 : 1);  //+1 if going to larger z, -1 if going to smaller;  if they're the same, the sense doesn't matter.
0559 
0560   int zi, zf;
0561   double startz, endz;
0562   BoundsCase startBound, endBound;
0563 
0564   // make sure 'zi' is always the smaller of the two numbers, for handling the partial-steps.
0565   if (dir > 0)
0566   {
0567     startBound = GetZindexAndCheckBounds(start.Z(), &zi);  // highest cell with lower bound less than lower bound of integral
0568     endBound = GetZindexAndCheckBounds(zdest, &zf);        // highest cell with lower bound less than upper lower bound of integral
0569     startz = start.Z();
0570     endz = zdest;
0571   }
0572   else
0573   {
0574     endBound = GetZindexAndCheckBounds(start.Z(), &zf);  // highest cell with lower bound less than lower bound of integral
0575     startBound = GetZindexAndCheckBounds(zdest, &zi);    // highest cell with lower bound less than upper lower bound of integral
0576     startz = zdest;
0577     endz = start.Z();
0578   }
0579   bool startOkay = (startBound == InBounds);
0580   bool endOkay = (endBound == InBounds || endBound == OnHighEdge);  // if we just barely touch out-of-bounds on the high end, we can skip that piece of the integral
0581 
0582   if (!startOkay || !endOkay)
0583   {
0584     std::cout << boost::str(boost::format("AnnularFieldSim::analyticFieldIntegral asked to integrate from z=%f to %f, index=%d to %d), which is out of bounds.  returning starting position.") % startz % endz % zi % zf) << std::endl;
0585     return start;
0586   }
0587 
0588   start.SetZ(startz);
0589   TVector3 integral;
0590   if (field == Efield)
0591   {
0592     integral = aliceModel->Eint(endz, start) + Eexternal->Get(r - rmin_roi, phi - phimin_roi, zi - zmin_roi) * (endz - startz);
0593     return dir * integral;
0594   }
0595   else if (field == Bfield)
0596   {
0597     return interpolatedFieldIntegral(zdest, start, Bfield);
0598   }
0599   return integral;
0600 }
0601 
0602 TVector3 AnnularFieldSim::fieldIntegral(float zdest, const TVector3 &start, MultiArray<TVector3> *field)
0603 {
0604   // integrates E dz, from the starting point to the selected z position.  The path is assumed to be along z for each step, with adjustments to x and y accumulated after each step.
0605   // if(debugFlag()) print_need_cout("%d: AnnularFieldSim::fieldIntegral(x=%f,y=%f, z=%f) to z=%f\n\n",__LINE__,start.X(),start.Y(),start.Z(),zdest);
0606 
0607   int r, phi;
0608   bool rOkay = (GetRindexAndCheckBounds(start.Perp(), &r) == InBounds);
0609   bool phiOkay = (GetPhiIndexAndCheckBounds(FilterPhiPos(start.Phi()), &phi) == InBounds);
0610 
0611   if (!rOkay || !phiOkay)
0612   {
0613     std::cout << boost::str(boost::format("AnnularFieldSim::fieldIntegral asked to integrate along (r=%f,phi=%f), index=(%d,%d), which is out of bounds.  returning starting position.") % start.Perp() % start.Phi() % r % phi) << std::endl;
0614     return start;
0615   }
0616 
0617   int dir = (start.Z() > zdest ? -1 : 1);  //+1 if going to larger z, -1 if going to smaller;  if they're the same, the sense doesn't matter.
0618 
0619   int zi, zf;
0620   double startz, endz;
0621   BoundsCase startBound, endBound;
0622 
0623   // make sure 'zi' is always the smaller of the two numbers, for handling the partial-steps.
0624   if (dir > 0)
0625   {
0626     startBound = GetZindexAndCheckBounds(start.Z(), &zi);  // highest cell with lower bound less than lower bound of integral
0627     endBound = GetZindexAndCheckBounds(zdest, &zf);        // highest cell with lower bound less than upper lower bound of integral
0628     startz = start.Z();
0629     endz = zdest;
0630   }
0631   else
0632   {
0633     endBound = GetZindexAndCheckBounds(start.Z(), &zf);  // highest cell with lower bound less than lower bound of integral
0634     startBound = GetZindexAndCheckBounds(zdest, &zi);    // highest cell with lower bound less than upper lower bound of integral
0635     startz = zdest;
0636     endz = start.Z();
0637   }
0638   bool startOkay = (startBound == InBounds);
0639   bool endOkay = (endBound == InBounds || endBound == OnHighEdge);  // if we just barely touch out-of-bounds on the high end, we can skip that piece of the integral
0640 
0641   if (!startOkay || !endOkay)
0642   {
0643     std::cout << boost::str(boost::format("AnnularFieldSim::fieldIntegral asked to integrate from z=%f to %f, index=%d to %d), which is out of bounds.  returning starting position.") % startz % endz % zi % zf) << std::endl;
0644     return start;
0645   }
0646 
0647   TVector3 fieldInt(0, 0, 0);
0648   // print_need_cout("AnnularFieldSim::fieldIntegral requesting (%d,%d,%d)-(%d,%d,%d) (inclusive) cells\n",r,phi,zi,r,phi,zf-1);
0649   TVector3 tf;
0650   for (int i = zi; i < zf; i++)
0651   {  // count the whole cell of the lower end, and skip the whole cell of the high end.
0652     tf = field->Get(r - rmin_roi, phi - phimin_roi, i - zmin_roi);
0653     // print_need_cout("fieldAt (%d,%d,%d)=(%f,%f,%f) step=%f\n",r,phi,i,tf.X(),tf.Y(),tf.Z(),step.Z());
0654     fieldInt += tf * step.Z();
0655   }
0656 
0657   // since bins contain their lower bound, but not their upper, I can safely remove the unused portion of the lower cell:
0658   fieldInt -= field->Get(r - rmin_roi, phi - phimin_roi, zi - zmin_roi) * (startz - zi * step.Z());  // remove the part of the low end cell we didn't travel through
0659 
0660   // but only need to add the used portion of the upper cell if we go past the edge of it meaningfully:
0661   if (endz / step.Z() - zf > ALMOST_ZERO)
0662   {
0663     // print_need_cout("endz/step.Z()=%f, zf=%f\n",endz/step.Z(),zf*1.0);
0664     // if our final step is actually in the next step.
0665     fieldInt += field->Get(r - rmin_roi, phi - phimin_roi, zf - zmin_roi) * (endz - zf * step.Z());  // add the part of the high end cell we did travel through
0666   }
0667 
0668   return dir * fieldInt;
0669 }
0670 
0671 TVector3 AnnularFieldSim::GetCellCenter(int r, int phi, int z)
0672 {
0673   // returns the midpoint of the cell (halfway between each edge, not weighted center)
0674 
0675   TVector3 c(1, 0, 0);
0676   c.SetPerp((r + 0.5) * step.Perp() + rmin);
0677   c.SetPhi((phi + 0.5) * step.Phi());
0678   c.SetZ((z + 0.5) * step.Z() + zmin);
0679 
0680   return c;
0681 }
0682 
0683 TVector3 AnnularFieldSim::GetRoiCellCenter(int r, int phi, int z)
0684 {
0685   // returns the midpoint of the cell relative to the roi (halfway between each edge, not weighted center)
0686 
0687   // return zero if it's out of bounds:
0688   if (r > nr_roi || r < 0 || phi > nphi_roi || phi < 0 || z > nz_roi || z < 0)
0689   {
0690     return zero_vector;
0691   }
0692 
0693   TVector3 c(1, 0, 0);
0694   c.SetPerp((r + rmin_roi + 0.5) * step.Perp() + rmin);
0695   c.SetPhi((phi + phimin_roi + 0.5) * step.Phi());
0696   c.SetZ((z + zmin_roi + 0.5) * step.Z() + zmin);
0697 
0698   return c;
0699 }
0700 
0701 TVector3 AnnularFieldSim::GetGroupCellCenter(int r0, int r1, int phi0, int phi1, int z0, int z1)
0702 {
0703   // returns the midpoint of the cell (halfway between each edge, not weighted center)
0704   float ravg = (r0 + r1) / 2.0 + 0.5;
0705   if (phi0 > phi1)
0706   {
0707     phi1 += nphi;
0708   }
0709   if (phi0 > phi1)
0710   {
0711     std::cout << boost::str(boost::format("phi1(%d)<=phi0(%d) even after boosting phi1.  check what called this!") % phi1 % phi0) << std::endl;
0712     exit(1);
0713   }
0714   float phiavg = (r0 + r1) / 2.0 + 0.5;
0715   if (phiavg >= nphi)
0716   {
0717     phiavg -= nphi;
0718   }
0719 
0720   float zavg = (z0 + z1) / 2.0 + 0.5;
0721 
0722   TVector3 c(1, 0, 0);
0723   c.SetPerp((ravg) *step.Perp() + rmin);
0724   c.SetPhi((phiavg) *step.Phi());
0725   c.SetZ((zavg) *step.Z() + zmin);
0726 
0727   return c;
0728 }
0729 
0730 TVector3 AnnularFieldSim::GetWeightedCellCenter(int r, int phi, int z)
0731 {
0732   // returns the weighted center of the cell by volume.
0733   // todo:  this is vaguely hefty, and might be worth storing the result of, if speed is needed
0734   TVector3 c(1, 0, 0);
0735 
0736   float rin = r * step.Perp() + rmin;
0737   float rout = rin + step.Perp();
0738 
0739   float rMid = (4 * sin(step.Phi() / 2) * (pow(rout, 3) - pow(rin, 3)) / (3 * step.Phi() * (pow(rout, 2) - pow(rin, 2))));
0740   c.SetPerp(rMid);
0741   c.SetPhi((phi + 0.5) * step.Phi());
0742   c.SetZ((z + 0.5) * step.Z());
0743 
0744   return c;
0745 }
0746 
0747 TVector3 AnnularFieldSim::interpolatedFieldIntegral(float zdest, const TVector3 &start, MultiArray<TVector3> *field)
0748 {
0749   // print_need_cout("AnnularFieldSim::interpolatedFieldIntegral(x=%f,y=%f, z=%f)\n",start.X(),start.Y(),start.Z());
0750 
0751   float r0 = (start.Perp() - rmin) / step.Perp() - 0.5;  // the position in r, in units of step, starting from the center of the 0th bin.
0752   int r0i = std::floor(r0);                                   // the integer portion of the position. -- what center is below our position?
0753   float r0d = r0 - r0i;                                  // the decimal portion of the position. -- how far past center are we?
0754   int ri[4];                                             // the r position of the four cell centers to consider
0755   ri[0] = ri[1] = r0i;
0756   ri[2] = ri[3] = r0i + 1;
0757   float rw[4];              // the weight of that cell, linear in distance from the center of it
0758   rw[0] = rw[1] = 1 - r0d;  // 1 when we're on it, 0 when we're at the other one.
0759   rw[2] = rw[3] = r0d;      // 1 when we're on it, 0 when we're at the other one
0760 
0761   bool skip[] = {false, false, false, false};
0762   if (ri[0] < rmin_roi)
0763   {
0764     skip[0] = true;  // don't bother handling 0 and 1 in the coordinates.
0765     skip[1] = true;
0766     rw[2] = rw[3] = 1;  // and weight like we're dead-center on the outer cells.
0767   }
0768   if (ri[2] >= rmax_roi)
0769   {
0770     skip[2] = true;  // don't bother handling 2 and 3 in the coordinates.
0771     skip[3] = true;
0772     rw[0] = rw[1] = 1;  // and weight like we're dead-center on the inner cells.
0773   }
0774 
0775   // now repeat that structure for phi:
0776   float p0 = (start.Phi()) / step.Phi() - 0.5;  // the position in phi, in units of step, starting from the center of the 0th bin.
0777   int p0i = std::floor(p0);                          // the integer portion of the position. -- what center is below our position?
0778   float p0d = p0 - p0i;                         // the decimal portion of the position. -- how far past center are we?
0779   int pi[4];                                    // the phi position of the four cell centers to consider
0780   pi[0] = pi[2] = FilterPhiIndex(p0i);
0781   pi[1] = pi[3] = FilterPhiIndex(p0i + 1);
0782   float pw[4];              // the weight of that cell, linear in distance from the center of it
0783   pw[0] = pw[2] = 1 - p0d;  // 1 when we're on it, 0 when we're at the other one.
0784   pw[1] = pw[3] = p0d;      // 1 when we're on it, 0 when we're at the other one
0785 
0786   // to handle wrap-around
0787   if (pi[0] < phimin_roi || pi[0] >= phimax_roi)
0788   {
0789     skip[0] = true;  // don't bother handling 0 and 1 in the coordinates.
0790     skip[2] = true;
0791     pw[1] = pw[3] = 1;  // and weight like we're dead-center on the outer cells.
0792   }
0793   if (pi[1] >= phimax_roi || pi[1] < phimin_roi)
0794   {
0795     skip[1] = true;  // don't bother handling 2 and 3 in the coordinates.
0796     skip[3] = true;
0797     pw[0] = pw[2] = 1;  // and weight like we're dead-center on the inner cells.
0798   }
0799 
0800   int dir = (start.Z() < zdest ? 1 : -1);  //+1 if going to larger z, -1 if going to smaller;  if they're the same, the sense doesn't matter.
0801 
0802   int zi, zf;
0803   double startz, endz;
0804   BoundsCase startBound, endBound;
0805 
0806   // make sure 'zi' is always the smaller of the two numbers, for handling the partial-steps.
0807   if (dir > 0)
0808   {
0809     startBound = GetZindexAndCheckBounds(start.Z(), &zi);  // highest cell with lower bound less than lower bound of integral
0810     endBound = GetZindexAndCheckBounds(zdest, &zf);        // highest cell with lower bound less than upper bound of integral
0811     startz = start.Z();
0812     endz = zdest;
0813   }
0814   else
0815   {
0816     endBound = GetZindexAndCheckBounds(start.Z(), &zf);  // highest cell with lower bound less than lower bound of integral
0817     startBound = GetZindexAndCheckBounds(zdest, &zi);    // highest cell with lower bound less than upper bound of integral
0818     startz = zdest;
0819     endz = start.Z();
0820   }
0821   bool startOkay = (startBound == InBounds || startBound == OnLowEdge);  // maybe todo: add handling for being just below the low edge.
0822   bool endOkay = (endBound == InBounds || endBound == OnHighEdge);       // if we just barely touch out-of-bounds on the high end, we can skip that piece of the integral
0823 
0824   if (!startOkay || !endOkay)
0825   {
0826     std::cout << boost::str(boost::format("AnnularFieldSim::InterpolatedFieldIntegral asked to integrate from z=%f to %f, index=%d to %d), which is out of bounds.  returning zero") % startz % endz % zi % zf) << std::endl;
0827     return zero_vector;
0828   }
0829 
0830   if (startBound == OnLowEdge)
0831   {
0832     // we were just below the low edge, so we will be asked to sample a bin in z we're not actually using
0833     zi++;  // avoid it.  We weren't integrating anything in it anyway.
0834   }
0835 
0836   if (false)
0837   {  // debugging options
0838     bool isE = (field == Efield);
0839     bool isB = (field == Bfield);
0840     std::cout << boost::str(boost::format("interpFieldInt:  isE=%d, isB=%d, start=(%2.4f,%2.4f,%2.4f) dest=%2.4f") % isE % isB % start.X() % start.Y() % start.Z() % zdest) << std::endl;
0841     std::cout << boost::str(boost::format("interpolating fieldInt for %s at  r=%f,phi=%f") % ((field == Efield) ? "Efield" : "Bfield") % r0 % p0) << std::endl;
0842     std::cout << boost::str(boost::format("direction=%d, startz=%2.4f, endz=%2.4f, zi=%d, zf=%d") % dir % startz % endz % zi % zf) << std::endl;
0843     for (int i = 0; i < 4; i++)
0844     {
0845       std::cout << boost::str(boost::format("skip[%d]=%d\tw[i]=%2.2f, (pw=%2.2f, rw=%2.2f)") % i % skip[i] % (rw[i] * pw[i]) % pw[i] % rw[i]) << std::endl;
0846     }
0847   }
0848 
0849   TVector3 fieldInt(0, 0, 0), partialInt;  // where we'll store integrals as we generate them.
0850 
0851   for (int i = 0; i < 4; i++)
0852   {
0853     if (skip[i])
0854     {
0855       // print_need_cout("skipping element r=%d,phi=%d\n",ri[i],pi[i]);
0856       continue;  // we invalidated this one for some reason.
0857     }
0858     partialInt.SetXYZ(0, 0, 0);
0859     for (int j = zi; j < zf; j++)
0860     {  // count the whole cell of the lower end, and skip the whole cell of the high end.
0861 
0862       partialInt += field->Get(ri[i] - rmin_roi, pi[i] - phimin_roi, j - zmin_roi) * step.Z();
0863     }
0864     if (startBound != OnLowEdge)
0865     {
0866       partialInt -= field->Get(ri[i] - rmin_roi, pi[i] - phimin_roi, zi - zmin_roi) * (startz - (zi * step.Z() + zmin));  // remove the part of the low end cell we didn't travel through
0867       // print_need_cout("removing low end of cell we didn't travel through (zi-zmin_roi=%d, length=%f)\n",zi-zmin_roi, startz-(zi*step.Z()+zmin));
0868     }
0869     if ((endz - zmin) / step.Z() - zf > ALMOST_ZERO)
0870     {
0871       partialInt += field->Get(ri[i] - rmin_roi, pi[i] - phimin_roi, zf - zmin_roi) * (endz - (zf * step.Z() + zmin));  // add the part of the high end cell we did travel through
0872       // print_need_cout("adding low end of cell we did travel through (zf-zmin_roi=%d, length=%f)\n",zf-zmin_roi, endz-(zf*step.Z()+zmin));
0873     }
0874     // print_need_cout("element r=%d,phi=%d, w=%f partialInt=(%2.2E,%2.2E,%2.2E)\n",ri[i],pi[i],rw[i]*pw[i],partialInt.X(),partialInt.Y(),partialInt.Z());
0875 
0876     fieldInt += rw[i] * pw[i] * partialInt;
0877   }
0878 
0879   return dir * fieldInt;
0880 }
0881 
0882 void AnnularFieldSim::load_analytic_spacecharge(float scalefactor = 1)
0883 {
0884   // scalefactor should be chosen so Rho(pos) returns C/cm^3
0885 
0886   // sphenix:
0887   // double ifc_radius=20;
0888   // double ofc_radius=78;
0889   //   double tpc_halfz=
0890 
0891   double ifc_radius = 83.5 * cm;
0892   double ofc_radius = 254.5 * cm;
0893   double tpc_halfz = 250 * cm;
0894 
0895   aliceModel = new AnalyticFieldModel(ifc_radius / cm, ofc_radius / cm, tpc_halfz / cm, scalefactor);
0896   double totalcharge = 0;
0897   double localcharge = 0;
0898 
0899   TVector3 pos;
0900   for (int ifr = 0; ifr < nr; ifr++)
0901   {
0902     for (int ifphi = 0; ifphi < nphi; ifphi++)
0903     {
0904       for (int ifz = 0; ifz < nz; ifz++)
0905       {
0906         pos = GetCellCenter(ifr, ifphi, ifz);
0907         pos = pos * (1.0 / cm);  // divide by cm so we're in units of cm when we query the charge model.
0908         double vol = step.Z() * step.Phi() * (2 * (ifr * step.Perp() + rmin) + step.Perp()) * step.Perp();
0909         // if(debugFlag()) print_need_cout("%d: AnnularFieldSim::load_analytic_spacecharge adding Q=%f into cell (%d,%d,%d)\n",__LINE__,qbin,i,j,k,localr,localphi,localz);
0910         localcharge = vol * aliceModel->Rho(pos);  // TODO:  figure out what units this is in.
0911         totalcharge += localcharge;
0912         // q->Add(ifr, ifphi, ifz, localcharge);  //scalefactor must be applied to charge _and_ field, and so is handled in the aliceModel code.
0913         q->AddChargeInBin(ifr, ifphi, ifz, localcharge);  // scalefactor must be applied to charge _and_ field, and so is handled in the aliceModel code.
0914       }
0915     }
0916   }
0917   std::cout << boost::str(boost::format("AnnularFieldSim::load_analytic_spacecharge:  Total charge Q=%E Coulombs") % totalcharge) << std::endl;
0918 
0919   if (lookupCase == HybridRes)
0920   {
0921     // go through the q array and build q_lowres.
0922     for (int i = 0; i < q_lowres->Length(); i++)
0923     {
0924       *(q_lowres->GetFlat(i)) = 0;
0925     }
0926 
0927     // fill our low-res
0928     // note that this assumes the last bin is short or normal length, not long.
0929     for (int ifr = 0; ifr < nr; ifr++)
0930     {
0931       int r_low = ifr / r_spacing;  // index of our l-bin is just the integer division of the index of our f-bin
0932       for (int ifphi = 0; ifphi < nphi; ifphi++)
0933       {
0934         int phi_low = ifphi / phi_spacing;
0935         for (int ifz = 0; ifz < nz; ifz++)
0936         {
0937           int z_low = ifz / z_spacing;
0938           q_lowres->Add(r_low, phi_low, z_low, q->GetChargeInBin(ifr, ifphi, ifz));
0939         }
0940       }
0941     }
0942   }
0943   return;
0944 }
0945 
0946 void AnnularFieldSim::loadEfield(const std::string &filename, const std::string &treename, int zsign)
0947 {
0948   // prep variables so that loadField can just iterate over the tree entries and fill our selected tree agnostically
0949   // assumes file stores fields as V/cm.
0950   printf("AnnularFieldSim::loadEfield:  loading E field from file %s, tree %s (sign=%d)\n", filename.c_str(), treename.c_str(),zsign);
0951   TFile fieldFile(filename.c_str(), "READ");
0952   TTree *fTree;
0953   fieldFile.GetObject(treename.c_str(), fTree);
0954   Efieldname = "E:" + filename + ":" + treename;
0955   float r, z;          // coordinates
0956   float fr, fz, fphi;  // field components at that coordinate
0957   fTree->SetBranchAddress("r", &r);
0958   fTree->SetBranchAddress("er", &fr);
0959   fTree->SetBranchAddress("z", &z);
0960   fTree->SetBranchAddress("ez", &fz);
0961   // phi would go here if we had it.
0962   fphi = 0;  // no phi components yet.
0963   // float phi = 0.;
0964   // phi += 1;
0965   // phi = 0;  //satisfy picky racf compiler
0966   loadField(&Eexternal, fTree, &r, nullptr, &z, &fr, &fphi, &fz, V / cm, zsign);
0967   printf("AnnularFieldSim::loadEfield:  finished loading E field from file %s, tree %s (sign=%d)\n", filename.c_str(), treename.c_str(), zsign);
0968   fieldFile.Close();
0969   return;
0970 }
0971 void AnnularFieldSim::loadBfield(const std::string &filename, const std::string &treename)
0972 {
0973   // prep variables so that loadField can just iterate over the tree entries and fill our selected tree agnostically
0974   // assumes file stores field as Tesla.
0975   TFile fieldFile(filename.c_str(), "READ");
0976   TTree *fTree;
0977   fieldFile.GetObject(treename.c_str(), fTree);
0978   Bfieldname = "B:" + filename + ":" + treename;
0979   float r, z;          // coordinates
0980   float fr, fz, fphi;  // field components at that coordinate
0981   fTree->SetBranchAddress("r", &r);
0982   fTree->SetBranchAddress("br", &fr);
0983   fTree->SetBranchAddress("z", &z);
0984   fTree->SetBranchAddress("bz", &fz);
0985   // phi would go here if we had it.
0986   fphi = 0;  // no phi components yet.
0987   // float phi = 0.;
0988   // phi += 1;
0989   // phi = 0;  //satisfy picky racf compiler
0990   loadField(&Bfield, fTree, &r, nullptr, &z, &fr, &fphi, &fz, Tesla, 1);
0991   printf("AnnularFieldSim::loadBfield:  finished loading B field from file %s, tree %s\n", filename.c_str(), treename.c_str());
0992   fieldFile.Close();
0993 
0994   return;
0995 }
0996 
0997 void AnnularFieldSim::load3dBfield(const std::string &filename, const std::string &treename, int zsign, float scale, float xshift, float yshift, float zshift)
0998 {
0999   // prep variables so that loadField can just iterate over the tree entries and fill our selected tree agnostically
1000   // assumes file stores field as Tesla.
1001   printf("AnnularFieldSim::load3dBfield:  loading B field from file %s, tree %s (sign=%d)\n", filename.c_str(), treename.c_str(),zsign);
1002 
1003   TFile fieldFile(filename.c_str(), "READ");
1004   TTree *fTree;
1005   fieldFile.GetObject(treename.c_str(), fTree);
1006   Bfieldname = "B(3D):" + filename + ":" + treename + " Scale =" + boost::str(boost::format("%2.2f") % scale);
1007   float r, z, phi;     // coordinates
1008   float fr, fz, fphi;  // field components at that coordinate
1009   fTree->SetBranchAddress("rho", &r);
1010   fTree->SetBranchAddress("brho", &fr);
1011   fTree->SetBranchAddress("z", &z);
1012   fTree->SetBranchAddress("bz", &fz);
1013   fTree->SetBranchAddress("phi", &phi);
1014   fTree->SetBranchAddress("bphi", &fphi);
1015   loadField(&Bfield, fTree, &r, &phi, &z, &fr, &fphi, &fz, Tesla * scale, zsign, xshift, yshift, zshift);
1016   printf("AnnularFieldSim::load3dBfield:  finished loading B field from file %s, tree %s (sign=%d) with scale %f\n", filename.c_str(), treename.c_str(), zsign, scale);
1017   fieldFile.Close();
1018   return;
1019 }
1020 
1021 void AnnularFieldSim::loadField(MultiArray<TVector3> **field, TTree *source, float *rptr, float *phiptr, float *zptr, float *frptr, float *fphiptr, float *fzptr, float fieldunit, int zsign, float xshift, float yshift, float zshift)
1022 {
1023   // we're loading a tree of unknown size and spacing -- and possibly uneven spacing -- into our local data.
1024   // formally, we might want to interpolate or otherwise weight, but for now, carve this into our usual bins, and average, similar to the way we load spacecharge.
1025   // the x,y,z shift moves the detector in the field (hence for a +1 shift in each, the field value at (0,0,0) is recorded at detector coordinate (-1,-1,-1)
1026   TVector3 origin= TVector3(xshift, yshift, zshift);  // the origin of the detector in the field coordinate system.
1027   bool phiSymmetry = (phiptr == nullptr);  // if the phi pointer is zero, assume phi symmetry.
1028 
1029   printf("AnnularFieldSim::loadField:  loading field from %s, detector origin at (%f,%f,%f) in the field map, field unit %f, zsign %d, handling=%s\n", source->GetName(), origin.X(), origin.Y(), origin.Z(), fieldunit, zsign, (phiSymmetry ? "phi-symmetric" : "not phi-symmetric"));
1030 
1031 
1032   int lowres_factor = 3;                  // to fill in gaps, we group together loweres^3 cells into one block and use that average.
1033 
1034   //float rbinsize=(rmax-rmin)/nr;  // the size of the bins in r
1035 //needed, because the interpolation doesn't know that phi should wrap:
1036   //float phibinsize=(2.0*M_PI)/nphi;  // the size of the bins in phi
1037   //float zbinsize=(zmax-zmin)/nz;  // the size of the bins in z
1038 
1039   //since our lowres_factor may not divide evenly into the number of bins, we need to adjust the number of bins in each dimension:
1040   int nlowbins_phi= nphi / lowres_factor + 1;  
1041   int nlowbins_r = nr / lowres_factor + 1; 
1042   int nlowbins_z = nz / lowres_factor + 1;
1043   float phi_lowres_step = M_PI * 2.0 / nlowbins_phi;  // the step size in phi for the low-res histogram
1044   float r_lowres_step = (rmax - rmin) / nlowbins_r;  // the step size in r for the low-res histogram
1045   float z_lowres_step = (zmax - zmin) / nlowbins_z;  // the step size in z for the low-res histogram
1046 
1047 
1048   std::cout << boost::str(boost::format("loading field from %f<z<%f") % zmin % zmax) << std::endl;
1049   TH3F *htEntries = new TH3F("htentries", "num of entries in the field loading", nphi, 0, M_PI * 2.0, nr, rmin, rmax, nz, zmin, zmax);
1050   TH3F *htSum[3];
1051   TH3F *htEntriesLow = new TH3F("htentrieslow", "num of lowres entries in the field loading", nlowbins_phi+2,  -phi_lowres_step, M_PI * 2.0+phi_lowres_step, nlowbins_r+2, rmin-r_lowres_step, rmax+r_lowres_step, nlowbins_z+2, zmin-z_lowres_step, zmax+z_lowres_step);
1052   TH3F *htSumLow[3];
1053   printf("AnnularFieldSim::loadField:  hires has %d phi bins from %f to %f, %d r bins from %f to %f, and %d z bins from %f to %f\n", 
1054   htEntries->GetNbinsX(), htEntries->GetXaxis()->GetXmin(), htEntries->GetXaxis()->GetXmax(), 
1055   htEntries->GetNbinsY(), htEntries->GetYaxis()->GetXmin(), htEntries->GetYaxis()->GetXmax(), 
1056   htEntries->GetNbinsZ(), htEntries->GetZaxis()->GetXmin(), htEntries->GetZaxis()->GetXmax());
1057   printf("AnnularFieldSim::loadField:  lowres has %d phi bins from %f to %f, %d r bins from %f to %f, and %d z bins from %f to %f\n", 
1058   htEntriesLow->GetNbinsX(), htEntriesLow->GetXaxis()->GetXmin(), htEntriesLow->GetXaxis()->GetXmax(),
1059   htEntriesLow->GetNbinsY(), htEntriesLow->GetYaxis()->GetXmin(), htEntriesLow->GetYaxis()->GetXmax(),
1060   htEntriesLow->GetNbinsZ(), htEntriesLow->GetZaxis()->GetXmin(), htEntriesLow->GetZaxis()->GetXmax());
1061   
1062   
1063   
1064   std::string axis[]{"r", "p", "z"};
1065   for (int i = 0; i < 3; i++)
1066   {
1067     //make each htSum inherit its bounds from htEntries:
1068     htSum[i] = new TH3F(std::string("htsum" + std::to_string(i)).c_str(), std::string("sum of " + axis[i] + "-axis entries in the field loading").c_str(), htEntries->GetNbinsX(), htEntries->GetXaxis()->GetXmin(), htEntries->GetXaxis()->GetXmax(), htEntries->GetNbinsY(), htEntries->GetYaxis()->GetXmin(), htEntries->GetYaxis()->GetXmax(), htEntries->GetNbinsZ(), htEntries->GetZaxis()->GetXmin(), htEntries->GetZaxis()->GetXmax());
1069     //make each htSumLow inherit its bounds from htEntriesLow:
1070     htSumLow[i] = new TH3F(std::string("htsumlow" + std::to_string(i)).c_str(), std::string("sum of low " + axis[i] + "-axis entries in the field loading").c_str(), htEntriesLow->GetNbinsX(), htEntriesLow->GetXaxis()->GetXmin(), htEntriesLow->GetXaxis()->GetXmax(), htEntriesLow->GetNbinsY(), htEntriesLow->GetYaxis()->GetXmin(), htEntriesLow->GetYaxis()->GetXmax(), htEntriesLow->GetNbinsZ(), htEntriesLow->GetZaxis()->GetXmin(), htEntriesLow->GetZaxis()->GetXmax());
1071   }
1072   //define the lowres stepsizes for sanity:
1073 
1074   std::vector<float> phiCoords(nphi);  // the phi coordinates of the bins in our internal rep.
1075   int nPhiCoords=1;
1076   if(phiSymmetry){
1077     //  if we do have phi symmetry, enumerate the phi coordinates we will use:
1078    // printf("AnnularFieldSim::loadField:  phi symmetry, using %d phi coordinates:\n", nphi);
1079     for (int j = 0; j < nphi; j++)
1080     {
1081       float phi0=(j+0.5)*step.Phi(); //stand-in for our phi pointer that doesn't exist.
1082       phiCoords[j]=phi0;
1083       printf("%2.2f ", phi0);
1084     }
1085     printf("\n");
1086     nPhiCoords=nphi;
1087   }
1088 
1089 
1090 
1091   //traverse the field tree and fill the histograms.
1092   int nEntries = source->GetEntries();
1093   for (int i = 0; i < nEntries; i++)
1094   {  // could probably do this with an iterator
1095 
1096     //keep track of progress:
1097     int rem=i%(source->GetEntries()/10);
1098     int quo=i/(source->GetEntries()/10);
1099     if(rem==0 && quo>0){
1100       printf("loadField:  %d0%%\n", quo);
1101     }
1102 
1103 
1104     source->GetEntry(i);
1105     float zval = *zptr * zsign;  // since the field map may assume symmetry wrt z, we  need the ability to flip the sign of the z coordinate.
1106     // note that the z sign also needs to affect the field sign in that direction, which is handled outside in the z components of the fills
1107     float rval=*rptr;
1108     float phival;
1109     //we have to also carefully transform the field itself:
1110     float fzval = *fzptr * fieldunit * zsign;  // z component of the field (altered in rotations of the cylinder, but not translations), note that we flip the sign if requested (ie the Efield loader).
1111     float fphival = *fphiptr * fieldunit*zsign;  // phi component of the field 
1112     float frval = *frptr * fieldunit;  // radial component of the field 
1113     TVector3 rphizField(frval, fphival, fzval);  
1114 
1115 
1116     // if we aren't asking for phi symmetry, build just the one phi strip
1117 
1118     if (!phiSymmetry)
1119     {
1120       assert(phiptr);
1121       phiCoords[0]=*phiptr;
1122     }
1123     for (int j = 0; j < nPhiCoords; j++){//this is a loop over n=1 if we have no symmetry, or nphi if we do.
1124       phival=phiCoords[j];//the input phitr if no symmetry, or precompupted values if symmetry.
1125 
1126       //find the vector coordinate of this field position, in the field map coords.
1127       TVector3 fieldMapPos(rval,0,zval);// the position in the field map, in cylindrical coordinates.
1128       fieldMapPos.SetPhi(phival);  // set the phi coordinate in the field map.
1129 
1130       //convert the components of the field into the global cartesian system:
1131       TVector3 globalField=rphizField;  // the field vector in the global coordinate system, in cylindrical coordinates.
1132       globalField.RotateZ(phival);  // the rphiz frame is rotated from the global by the phi coordinate, so to make these match the cartesian system, we must rotate the field to the local rphiz frame
1133 
1134       //if our origin is not zero, apply the translation of field and coords:
1135       TVector3 tpcPos=fieldMapPos-origin;  // the position of this field datapoint in the tpc coordinate system
1136       //I think this could just be a rotation by -tpcPos.Phi()...
1137       TVector3 tpcField=GetLocalFieldComponents(globalField,fieldMapPos,origin); //note that this returns the rphiz components at that point, in the tpc coordinate system.
1138 
1139       //get the cylindrical coordinates of our position in the TPC coordinate system:
1140       rval= tpcPos.Perp();  // the radial coordinate in the tpc coordinate system
1141       phival = FilterPhiPos(tpcPos.Phi());  // the phi coordinate in the tpc coordinate system, wrapped into the expected range.
1142       zval = tpcPos.Z();  // the z coordinate in the tpc coordinate system
1143 
1144       //and the field components in the tpc coordinate system:
1145       frval = tpcField.X();  // the radial component of the field in the tpc coordinate system
1146       fphival = tpcField.Y();  // the azimuthal component of the field in the tpc coordinate system
1147       fzval = tpcField.Z();  // the z component of the field in the tpc coordinate system
1148 
1149       htEntries->Fill(phival,rval, zval);  // for legacy reasons this histogram, like others, goes phi-r-z.
1150       htSum[0]->Fill(phival,rval, zval, frval );
1151       htSum[1]->Fill(phival,rval, zval, fphival );
1152       htSum[2]->Fill(phival,rval, zval, fzval );
1153       htEntriesLow->Fill(phival,rval, zval);  // for legacy reasons this histogram, like others, goes phi-r-z.
1154       htSumLow[0]->Fill(phival,rval, zval, frval );
1155       htSumLow[1]->Fill(phival,rval, zval, fphival );
1156       htSumLow[2]->Fill(phival,rval, zval, fzval );
1157     }
1158   }
1159   // now we just divide and fill our local plots (which should eventually be stored as histograms, probably) with the values from each hist cell:
1160   int nemptybins = 0;
1161   for (int i = 0; i < nphi; i++)
1162   {
1163     for (int j = 0; j < nr; j++)
1164     {
1165       for (int k = 0; k < nz; k++)
1166       {
1167         TVector3 cellcenter = GetCellCenter(j, i, k);
1168         int bin = htEntries->FindBin(FilterPhiPos(cellcenter.Phi()), cellcenter.Perp(), cellcenter.Z());
1169         TVector3 fieldvec(htSum[0]->GetBinContent(bin), htSum[1]->GetBinContent(bin), htSum[2]->GetBinContent(bin));//r, phi, z components in that order
1170         if (htEntries->GetBinContent(bin) < 0.99)
1171         {
1172           // no entries here!
1173           nemptybins++;
1174         } else{
1175           fieldvec = fieldvec * (1.0 / htEntries->GetBinContent(bin));
1176         }
1177         //now we have the rphiz field at this position.  We want to store it, for our own sanity, in cartesian componnets (it makes the intergrals easier in the drift stage)
1178         // so we have to rotate this to the proper angle
1179         // (if it helps, remember that the x component of the fieldvec is the radial direction, and we need that to point from the origin to the cell center in order for it to be in cartesian coordinates)
1180         fieldvec.RotateZ(FilterPhiPos(cellcenter.Phi()));  
1181         (*field)->Set(j, i, k, fieldvec);
1182       }
1183     }
1184   }
1185   if (nemptybins > 0)
1186   {
1187     std::cout << boost::str(boost::format("found %d empty bins when constructing %s.  Filling with lower resolution.") % nemptybins % ((*field == Bfield) ? "Bfield" : "Eexternal")) << std::endl;
1188 
1189     //first, we need to stitch the lowest phibin together with the highest phibin, by setting the lowest phi bin contents and nentries to be the same as the highest-1, and the highest to the lowest+1:
1190     for (int j=0; j<htEntriesLow->GetNbinsY(); j++){
1191       for (int k=0; k<htEntriesLow->GetNbinsZ(); k++){
1192         int lowbin=htEntriesLow->GetBin(1,j,k);
1193         int highbin=htEntriesLow->GetBin(htEntriesLow->GetNbinsX()-1,j,k);
1194         htEntriesLow->SetBinContent(lowbin,
1195           htEntriesLow->GetBinContent(htEntriesLow->GetBin(htEntriesLow->GetNbinsX()-2,j,k)));
1196         //and the same for the triplet of htSumLow:
1197         for (int i=0; i<3; i++){
1198           htSumLow[i]->SetBinContent(lowbin,
1199             htSumLow[i]->GetBinContent(htSumLow[i]->GetBin(htEntriesLow->GetNbinsX()-2,j,k)));
1200           }
1201         //and then we repeat for the highend stitch:
1202         htEntriesLow->SetBinContent(highbin,
1203           htEntriesLow->GetBinContent(htEntriesLow->GetBin(2,j,k)));
1204         for (int i=0; i<3; i++){
1205           htSumLow[i]->SetBinContent(highbin,
1206             htSumLow[i]->GetBinContent(htSumLow[i]->GetBin(2,j,k)));
1207           }
1208         }
1209       }
1210 
1211       //now that it is stitched, we can interpolate correctly.
1212     for (int i = 0; i < nphi; i++)
1213     {
1214       for (int j = 0; j < nr; j++)
1215       {
1216         for (int k = 0; k < nz; k++)
1217         {
1218           TVector3 cellcenter = GetCellCenter(j, i, k);
1219           int bin = htEntries->FindBin(FilterPhiPos(cellcenter.Phi()), cellcenter.Perp(), cellcenter.Z());
1220           if (htEntries->GetBinContent(bin) == 0)
1221           {
1222             if(0){//long debug check.
1223               printf("Filling coordinates p%f,r%f,z%f, (cell p%d r%d z%d) with lowres field\n", FilterPhiPos(cellcenter.Phi()), cellcenter.Perp(), cellcenter.Z(), i,j,k);
1224               printf(" sanity: htEntries->FindBins(p%2.2f,r%2.2f,z%2.2f)=(p%d,r%d,z%d)=%d, content=%f\n", FilterPhiPos(cellcenter.Phi()), cellcenter.Perp(), cellcenter.Z(), 
1225                 htEntries->GetXaxis()->FindBin(FilterPhiPos(cellcenter.Phi())), 
1226                 htEntries->GetYaxis()->FindBin(cellcenter.Perp()), 
1227                 htEntries->GetZaxis()->FindBin(cellcenter.Z()), bin, htEntries->GetBinContent(bin));
1228             
1229               TVector3 globalPos= cellcenter+origin;  // the position of this field datapoint in the input system
1230               //bounds of the bin of this cell in htEntries, so I can understand why this has no entries:
1231               float tr_low=htEntries->GetYaxis()->GetBinLowEdge(htEntries->GetYaxis()->FindBin(cellcenter.Perp()));
1232               float tr_high=htEntries->GetYaxis()->GetBinUpEdge(htEntries->GetYaxis()->FindBin(cellcenter.Perp()));
1233               float tz_low=htEntries->GetZaxis()->GetBinLowEdge(htEntries->GetZaxis()->FindBin(cellcenter.Z()));
1234               float tz_high=htEntries->GetZaxis()->GetBinUpEdge(htEntries->GetZaxis()->FindBin(cellcenter.Z()));  
1235               float tphi_low=htEntries->GetXaxis()->GetBinLowEdge(htEntries->GetXaxis()->FindBin(FilterPhiPos(cellcenter.Phi())));
1236               float tphi_high=htEntries->GetXaxis()->GetBinUpEdge(htEntries->GetXaxis()->FindBin(FilterPhiPos(cellcenter.Phi())));
1237               TVector3 tlow(tr_low, 0, tz_low);  // the low edge of the bin in the input system
1238               tlow.RotateZ(tphi_low);  // rotate to the correct phi
1239               TVector3 thigh(tr_high, 0, tz_high);  // the high edge of the bin in the input system
1240               thigh.RotateZ(tphi_high);  // rotate to the correct phi
1241               tlow=tlow+origin;  // translate to the input coordinate system
1242               thigh=thigh+origin;  // translate to the input coordinate system
1243               if (phiSymmetry){
1244                 printf("this corresponds to (r,phi,z)=(%f,%f,%f) in the input map coordinates, which has bounds roughly (r>%1.1f && r<%1.1f && z> %1.1f && z<%1.1f)\n",globalPos.Perp(),globalPos.Phi(),globalPos.Z(),tlow.Perp(),thigh.Perp(),tlow.Z(),thigh.Z());
1245               }else{
1246 
1247                 printf("this corresponds to (r,phi,z)=(%f,%f,%f) in the input map coordinates, which has bounds roughly (r>%1.1f && r<%1.1f && phi>%1.1f && phi<%1.1f && z> %1.1f && z<%1.1f)\n",globalPos.Perp(),globalPos.Phi(),globalPos.Z(),tlow.Perp(),thigh.Perp(),tlow.Phi(),thigh.Phi(),tlow.Z(),thigh.Z());
1248               }
1249             } 
1250 
1251             TVector3 fieldvec(htSumLow[0]->Interpolate(FilterPhiPos(cellcenter.Phi()), cellcenter.Perp(), cellcenter.Z()),
1252                htSumLow[1]->Interpolate(FilterPhiPos(cellcenter.Phi()), cellcenter.Perp(), cellcenter.Z()),
1253                htSumLow[2]->Interpolate(FilterPhiPos(cellcenter.Phi()), cellcenter.Perp(), cellcenter.Z()));
1254             int lowbin = htEntriesLow->FindBin(FilterPhiPos(cellcenter.Phi()), cellcenter.Perp(), cellcenter.Z());
1255             //TVector3 fieldvec(htSumLow[0]->GetBinContent(lowbin), htSumLow[1]->GetBinContent(lowbin), htSumLow[2]->GetBinContent(lowbin));
1256  
1257             if (htEntriesLow->GetBinContent(lowbin) < 0.99)
1258             {
1259               std::cout << boost::str(boost::format("not enough entries in source to fill fieldmap, even using the lower res fall-back.  Value near r=%2.2f, phi=%2.2f, z=%2.2f is %f, (with range of %2.3f,%2.3f,%2.3f) Pick lower granularity!") % cellcenter.Perp() % FilterPhiPos(cellcenter.Phi()) % cellcenter.Z()% htEntriesLow->GetBinContent(lowbin) % r_lowres_step % phi_lowres_step % z_lowres_step) << std::endl;
1260               printf("Saving fieldmaps to debug.hist.root\n");
1261               TFile *debugfile = new TFile("debug.hist.root", "RECREATE");
1262               htEntries->Write();
1263               htEntriesLow->Write();
1264               for (int ii = 0; ii < 3; ii++)
1265               {
1266                 htSum[ii]->Write();
1267                 htSumLow[ii]->Write();
1268               }
1269               debugfile->Close();
1270               exit(1);
1271             } else {
1272               fieldvec = fieldvec * (1.0 / htEntriesLow->GetBinContent(lowbin));
1273             }
1274             // have to rotate this to the proper direction.
1275             fieldvec.RotateZ(FilterPhiPos(cellcenter.Phi()));  // rcc caution.  Does this rotation shift the sense of 'up'?
1276             (*field)->Set(j, i, k, fieldvec);
1277           }
1278         }
1279       }
1280     }
1281   }
1282   //printf("Field loaded.\n");
1283   return;
1284 }
1285 
1286 void AnnularFieldSim::load_spacecharge(const std::string &filename, const std::string &histname, float zoffset, float chargescale, float cmscale, bool isChargeDensity)
1287 {
1288   TFile *f = TFile::Open(filename.c_str());
1289   TH3 *scmap;
1290   f->GetObject(histname.c_str(), scmap);
1291   std::cout << "Loading spacecharge from '" << filename
1292             << "'.  Seeking histname '" << histname << "'" << std::endl;
1293   chargesourcename = filename + ":" + histname;
1294   load_spacecharge(scmap, zoffset, chargescale, cmscale, isChargeDensity, chargesourcename.c_str());
1295   f->Close();
1296   return;
1297 }
1298 
1299 void AnnularFieldSim::load_and_resample_spacecharge(int new_nphi, int new_nr, int new_nz, const std::string &filename, const std::string &histname, float zoffset, float chargescale, float cmscale, bool isChargeDensity)
1300 {
1301   TFile *f = TFile::Open(filename.c_str());
1302   TH3 *scmap;
1303   f->GetObject(histname.c_str(), scmap);
1304   std::cout << "Resampling spacecharge from '" << filename
1305             << "'.  Seeking histname '" << histname << "'" << std::endl;
1306   chargesourcename = filename + ":" + histname;
1307   load_and_resample_spacecharge(new_nphi, new_nr, new_nz, scmap, zoffset, chargescale, cmscale, isChargeDensity);
1308   f->Close();
1309   return;
1310 }
1311 
1312 void AnnularFieldSim::load_and_resample_spacecharge(int new_nphi, int new_nr, int new_nz, TH3 *hist, float zoffset, float chargescale, float cmscale, bool isChargeDensity)
1313 {
1314   // load spacecharge densities from a histogram, where scalefactor translates into local units of C/cm^3
1315   // and cmscale translate (hist coord) --> (hist position in cm)
1316   // noting that the histogram limits may differ from the simulation size, and have different granularity
1317   // hist is assumed/required to be x=phi, y=r, z=z
1318   // z offset 'drifts' the charge by that distance toward z=0.
1319 
1320   // Get dimensions of input
1321   float hrmin = hist->GetYaxis()->GetXmin();
1322   float hrmax = hist->GetYaxis()->GetXmax();
1323   float hphimin = hist->GetXaxis()->GetXmin();
1324   float hphimax = hist->GetXaxis()->GetXmax();
1325   float hzmin = hist->GetZaxis()->GetXmin();
1326   float hzmax = hist->GetZaxis()->GetXmax();
1327 
1328   // Get number of bins in each dimension
1329   int hrn = hist->GetNbinsY();
1330   int hphin = hist->GetNbinsX();
1331   int hzn = hist->GetNbinsZ();
1332   std::cout << boost::str(boost::format("From histogram, native r dimensions: %f<r<%f, hrn (Y axis)=%d") % hrmin % hrmax % hrn) << std::endl;
1333   std::cout << boost::str(boost::format("From histogram, native phi dimensions: %f<phi<%f, hrn (X axis)=%d") % hphimin % hphimax % hphin) << std::endl;
1334   std::cout << boost::str(boost::format("From histogram, native z dimensions: %f<z<%f, hrn (Z axis)=%d") % hzmin % hzmax % hzn) << std::endl;
1335 
1336   // do some computation of steps:
1337   float hrstep = (hrmax - hrmin) / hrn;
1338   float hphistep = (hphimax - hphimin) / hphin;
1339   float hzstep = (hzmax - hzmin) / hzn;
1340   float halfrstep = hrstep * 0.5;
1341   float halfphistep = hphistep * 0.5;
1342   float halfzstep = hzstep * 0.5;
1343 
1344   // All we have to do here is resample it so it fits into our expected grid, then pass it to the loader.
1345 
1346   // 1) convert the existing histogram to density if it isn't already:
1347   if (!isChargeDensity)
1348   {
1349     for (int i = 0; i < hphin; i++)
1350     {
1351       float phi = hphimin + hphistep * i;
1352       for (int j = 0; j < hrn; j++)
1353       {
1354         float r = hrmin + hrstep * j;
1355         for (int k = 0; k < hzn; k++)
1356         {
1357           float z = hzmin + hzstep * k;
1358           int bin = hist->FindBin(phi + halfphistep, r + halfrstep, z + halfzstep);
1359           double vol = hzstep * hphistep * (r + halfrstep) * hrstep;
1360           hist->SetBinContent(bin, hist->GetBinContent(bin) / vol);
1361         }
1362       }
1363     }
1364   }
1365   // I ought to consider a subtler approach that better preserves the overall integral, rather than just skipping the interpolation of the outer edges:
1366   TH3F *resampled = new TH3F("resampled", "resampled charge", new_nphi, hphimin, hphimax, new_nr, hrmin, hrmax, new_nz, hzmin, hzmax);
1367   float new_phistep = (hphimax - hphimin) / new_nphi;
1368   float new_rstep = (hrmax - hrmin) / new_nr;
1369   float new_zstep = (hzmax - hzmin) / new_nz;
1370 
1371   // 2 resample with interpolation on the interior, and with nearest-bin for the edge bins
1372   for (int i = 0; i < new_nphi; i++)
1373   {
1374     float phi = hphimin + new_phistep * (i + 0.5);                  // bin center of resampled
1375     float hphi = (phi - hphimin) / hphistep;                        // coord  in source hist
1376     bool phisafe = ((hphi - 0.75) > 0) && ((hphi + 0.75) < hphin);  // we're past the halfway point of the lowest bin, and short of the halfway point of the highest bin.
1377     for (int j = 0; j < new_nr; j++)
1378     {
1379       float r = hrmin + new_rstep * (j + 0.5);              // bin center of resampled
1380       float hr = (r - hrmin) / hrstep;                      // coord in source hist
1381       bool rsafe = ((hr - 0.5) > 0) && ((hr + 0.5) < hrn);  // we're past the halfway point of the lowest bin, and short of the halfway point of the highest bin.
1382       for (int k = 0; k < new_nz; k++)
1383       {
1384         float z = hzmin + new_zstep * (k + 0.5);              // bin center of resampled
1385         float hz = (z - hzmin) / hzstep;                      // coord in source hist
1386         bool zsafe = ((hz - 0.5) > 0) && ((hz + 0.5) < hzn);  // we're past the halfway point of the lowest bin, and short of the halfway point of the highest bin.
1387         // check if we need to do a bin lookup:
1388         if (phisafe && rsafe && zsafe)
1389         {
1390           // print_need_cout("resampling (%d,%d,%d) from (%2.2f/%d,%2.2f/%d,%2.2f/%d) p:(%1.1f<%1.1f<%1.1f) r:(%1.1f<%1.1f<%1.1f) z:(%1.1f<%1.1f<%1.1f)\n",i,j,k,hphi,hphin,hr,hrn,hz,hzn,hphimin,phi,hphimax,hrmin,r,hrmax,hzmin,z,hzmax);
1391           resampled->Fill(phi, r, z, hist->Interpolate(phi, r, z));  // leave as a density
1392         }
1393         else
1394         {
1395           int bin = hist->FindBin(phi, r, z);
1396           resampled->Fill(phi, r, z, hist->GetBinContent(bin));
1397         }
1398       }  // k (z loop)
1399     }    // j (r loop)
1400   }      // i (phi loop)
1401   std::cout << "here in load_and_resample_spacecharge, I am going to call load_spacecharge." << std::endl;
1402   load_spacecharge(resampled, zoffset, chargescale, cmscale, true);
1403 }
1404 
1405 void AnnularFieldSim::load_spacecharge(TH3 *hist, float zoffset, float chargescale, float cmscale, bool isChargeDensity, const std::string &inputchargestring)
1406 {
1407   // new plan:  use ChargeMapReader:
1408   if (std::abs(zoffset) > 0.001)
1409   {
1410     std::cout << boost::str(boost::format("nonzero zoffset given (%E) but new spacecharge loader can't deal with that.  Failing.") % zoffset) << std::endl;
1411     assert(false);
1412   }
1413   if (isChargeDensity)
1414   {
1415     std::cout << "Input dataset is flagged as recording density not total charge, but new loader can't deal with that  Failing.." << std::endl;
1416     assert(false);
1417   }
1418   q->ReadSourceCharge(hist, cmscale, chargescale);
1419 
1420   chargestring = "SC loaded externally: " + inputchargestring + ".";
1421   return;
1422 }
1423 
1424 void AnnularFieldSim::load_digital_current(TH3 *hist, TH2 *gainHist, float chargescale, float cmscale, const std::string &inputchargestring)
1425 {
1426   q->ReadSourceAdc(hist, gainHist, cmscale, chargescale);
1427 
1428   chargestring = "SC loaded externally: " + inputchargestring + ".";
1429   return;
1430 }
1431 
1432 void AnnularFieldSim::save_spacecharge(const std::string &filename)
1433 {
1434   // save a histogram giving the spacecharge in our local coordinates, as we'll be using them..
1435   TH3F *hsc = new TH3F("hInternalSpaceCharge", "Internal Charge Histogram;phi(rad);r(cm);z(cm)", nphi, 0, phispan, nr, rmin, rmax, nz, zmin, zmax);
1436   for (int i = 0; i < nphi; i++)
1437   {
1438     float phi = 0 + step.Phi() * (i + 0.5);
1439     for (int j = 0; j < nr; j++)
1440     {
1441       float r = rmin + step.Perp() * (j + 0.5);
1442       for (int k = 0; k < nz; k++)
1443       {
1444         float z = zmin + step.Z() * (k + 0.5);
1445         hsc->Fill(phi, r, z, q->GetChargeAtPosition(r, phi, z));
1446         // old version: hsc->Fill(phi,r,z,q->Get(j,i,k));
1447       }
1448     }
1449   }
1450   hsc->SaveAs(filename.c_str());
1451   return;
1452 }
1453 
1454 void AnnularFieldSim::add_testcharge(float r, float phi, float z, float coulombs)
1455 {
1456   q->AddChargeAtPosition(r, phi, z, coulombs * C);
1457   return;
1458   /*
1459   int rcell, phicell, zcell;
1460 
1461   //translate to which cell we're in:
1462   BoundsCase rb = GetRindexAndCheckBounds(r, &rcell);
1463   BoundsCase pb = GetPhiIndexAndCheckBounds(phi, &phicell);
1464   BoundsCase zb = GetZindexAndCheckBounds(z, &zcell);
1465   //really, we would like to confirm that the cell we find is in the charge map region, not the field map region.
1466   if (rb == BoundsCase::OutOfBounds || pb == BoundsCase::OutOfBounds || zb == BoundsCase::OutOfBounds)
1467   {
1468     print_need_cout("placing %f Coulombs at (r,p,z)=(%f,%f,%f).  Caution that that is out of the roi.\n", coulombs, r, phi, z);
1469   }
1470   if (rcell < 0 || phicell < 0 || zcell < 0)
1471   {
1472     print_need_cout("Tried placing %f Coulombs at (r,p,z)=(%f,%f,%f).  That is outside of the charge map region.\n", coulombs, r, phi, z);
1473     return;
1474   }
1475 
1476   q->Add(rcell, phicell, zcell, coulombs * C);
1477   if (lookupCase == HybridRes)
1478   {
1479     print_need_cout("write the code you didn't write earlier about reloading the lowres map.\n");
1480     exit(1);
1481   }
1482 
1483   return;
1484   */
1485 }
1486 
1487 /*
1488 void AnnularFieldSim::populate_analytic_fieldmap(){
1489   //sum the E field at every point in the region of interest
1490   // remember that Efield uses relative indices
1491   print_need_cout("in pop_analytic_fieldmap, n=(%d,%d,%d)\n",nr,nphi,nz);
1492 
1493   TVector3 localF;//holder for the summed field at the current position.
1494   for (int ir=rmin_roi;ir<rmax_roi;ir++){
1495     for (int iphi=phimin_roi;iphi<phimax_roi;iphi++){
1496       for (int iz=zmin_roi;iz<zmax_roi;iz++){
1497         localF=aliceModel->E(GetCellCenter(ir, iphi, iz))+Eexternal->Get(ir-rmin_roi,iphi-phimin_roi,iz-zmin_roi);
1498         Efield->Set(ir-rmin_roi,iphi-phimin_roi,iz-zmin_roi,localF);
1499         //if (localF.Mag()>1e-9)
1500         if(debugFlag()) print_need_cout("%d: AnnularFieldSim::populate_analytic_fieldmap fieldmap@ (%d,%d,%d) mag=%f\n",__LINE__,ir,iphi,iz,localF.Mag());
1501       }
1502     }
1503   }
1504   return;
1505 }
1506 */
1507 
1508 void AnnularFieldSim::populate_fieldmap()
1509 {
1510   // sum the E field at every point in the region of interest
1511   //  remember that Efield uses relative indices
1512   std::cout << boost::str(boost::format("in pop_fieldmap, n=(%d,%d,%d)") % nr % nphi % nz) << std::endl;
1513 
1514   std::cout << boost::str(boost::format("populating fieldmap for (%dx%dx%d) grid with (%dx%dx%d) source ") % nr_roi % nphi_roi % nz_roi % nr % nphi % nz) << std::endl;
1515   if (truncation_length > 0)
1516   {
1517     std::cout << boost::str(boost::format(" ==> truncating anything more than %d cells away") % truncation_length) << std::endl;
1518   }
1519   unsigned long long totalelements = nr_roi;
1520   totalelements *= nphi_roi;
1521   totalelements *= nz_roi;  // breaking up this multiplication prevents a 32bit math overflow
1522   unsigned long long percent = totalelements / 100 * debug_npercent;
1523   std::cout << boost::str(boost::format("total elements = %llu") % (totalelements * nr * nphi * nz)) << std::endl;
1524 
1525   int el = 0;
1526 
1527   TVector3 localF;  // holder for the summed field at the current position.
1528   for (int ir = rmin_roi; ir < rmax_roi; ir++)
1529   {
1530     for (int iphi = phimin_roi; iphi < phimax_roi; iphi++)
1531     {
1532       for (int iz = zmin_roi; iz < zmax_roi; iz++)
1533       {
1534         localF = sum_field_at(ir, iphi, iz);  // asks in global coordinates
1535         if (!(el % percent))
1536         {
1537           std::cout << boost::str(boost::format("populate_fieldmap %llu%%:  ") % ((uint64_t) (debug_npercent) *el / percent));
1538           std::cout << boost::str(boost::format("sum_field_at (ir=%d,iphi=%d,iz=%d) gives (%E,%E,%E)") % ir % iphi % iz % localF.X() % localF.Y() % localF.Z()) << std::endl;
1539         }
1540         el++;
1541 
1542         Efield->Set(ir - rmin_roi, iphi - phimin_roi, iz - zmin_roi, localF);  // sets in roi coordinates.
1543                                                                                // if (localF.Mag()>1e-9)
1544                                                                                // if(debugFlag()) print_need_cout("%d: AnnularFieldSim::populate_fieldmap fieldmap@ (%d,%d,%d) mag=%f\n",__LINE__,ir,iphi,iz,localF.Mag());
1545       }
1546     }
1547   }
1548   return;
1549 }
1550 
1551 void AnnularFieldSim::populate_lookup()
1552 {
1553   // with 'f' being the position the field is being measured at, and 'o' being the position of the charge generating the field.
1554   // remember the 'f' part of Epartial uses relative indices.
1555   //   TVector3 (*f)[fx][fy][fz][ox][oy][oz]=field_;
1556   // print_need_cout("populating lookup for (%dx%dx%d)x(%dx%dx%d) grid\n",fx,fy,fz,ox,oy,oz);
1557 
1558   if (lookupCase == Full3D)
1559   {
1560     std::cout << "lookupCase==Full3D" << std::endl;
1561 
1562     populate_full3d_lookup();
1563   }
1564   else if (lookupCase == HybridRes)
1565   {
1566     std::cout << "lookupCase==HybridRes" << std::endl;
1567     populate_highres_lookup();
1568     populate_lowres_lookup();
1569   }
1570   else if (lookupCase == PhiSlice)
1571   {
1572     std::cout << "Populating lookup:  lookupCase==PhiSlice" << std::endl;
1573     populate_phislice_lookup();
1574   }
1575   else if (lookupCase == Analytic)
1576   {
1577     std::cout << "Populating lookup:  lookupCase==Analytic ===> skipping!" << std::endl;
1578   }
1579   else if (lookupCase == NoLookup)
1580   {
1581     std::cout << "Populating lookup:  lookupCase==NoLookup ===> skipping!" << std::endl;
1582   }
1583   else
1584   {
1585     exit(1);
1586   }
1587   return;
1588 }
1589 
1590 void AnnularFieldSim::populate_full3d_lookup()
1591 {
1592   // with 'f' being the position the field is being measured at, and 'o' being the position of the charge generating the field.
1593   // remember the 'f' part of Epartial uses relative indices.
1594   //   TVector3 (*f)[fx][fy][fz][ox][oy][oz]=field_;
1595   std::cout << boost::str(boost::format("populating full lookup table for (%dx%dx%d)x(%dx%dx%d) grid") % (rmax_roi - rmin_roi) % (phimax_roi - phimin_roi) % (zmax_roi - zmin_roi) % nr % nphi % nz) << std::endl;
1596   unsigned long long totalelements = (rmax_roi - rmin_roi);
1597   totalelements *= (phimax_roi - phimin_roi);
1598   totalelements *= (zmax_roi - zmin_roi);
1599   totalelements *= nr;
1600   totalelements *= nphi;
1601   totalelements *= nz;  // breaking up this multiplication prevents a 32bit math overflow
1602   unsigned long long percent = totalelements / 100 * debug_npercent;
1603   std::cout << boost::str(boost::format("total elements = %llu") % totalelements) << std::endl;
1604   TVector3 at(1, 0, 0);
1605   TVector3 from(1, 0, 0);
1606   TVector3 zero(0, 0, 0);
1607 
1608   int el = 0;
1609   for (int ifr = rmin_roi; ifr < rmax_roi; ifr++)
1610   {
1611     for (int ifphi = phimin_roi; ifphi < phimax_roi; ifphi++)
1612     {
1613       for (int ifz = zmin_roi; ifz < zmax_roi; ifz++)
1614       {
1615         at = GetCellCenter(ifr, ifphi, ifz);
1616         for (int ior = 0; ior < nr; ior++)
1617         {
1618           for (int iophi = 0; iophi < nphi; iophi++)
1619           {
1620             for (int ioz = 0; ioz < nz; ioz++)
1621             {
1622               el++;
1623               if (!(el % percent))
1624               {
1625                 std::cout << boost::str(boost::format("populate_full3d_lookup %d%%") % ((uint64_t) (debug_npercent) *el / percent)) << std::endl;
1626               }
1627               from = GetCellCenter(ior, iophi, ioz);
1628 
1629               //*f[ifx][ify][ifz][iox][ioy][ioz]=cacl_unit_field(at,from);
1630               // print_need_cout("calc_unit_field...\n");
1631               if (ifr == ior && ifphi == iophi && ifz == ioz)
1632               {
1633                 Epartial->Set(ifr - rmin_roi, ifphi - phimin_roi, ifz - zmin_roi, ior, iophi, ioz, zero);
1634               }
1635               else
1636               {
1637                 Epartial->Set(ifr - rmin_roi, ifphi - phimin_roi, ifz - zmin_roi, ior, iophi, ioz, calc_unit_field(at, from));
1638               }
1639             }
1640           }
1641         }
1642       }
1643     }
1644   }
1645   return;
1646 }
1647 
1648 void AnnularFieldSim::populate_highres_lookup()
1649 {
1650   TVector3 at(1, 0, 0);
1651   TVector3 from(1, 0, 0);
1652   TVector3 zero(0, 0, 0);
1653 
1654   // populate_highres_lookup();
1655   int r_highres_dist = (nr_high - 1) / 2;
1656   int phi_highres_dist = (nphi_high - 1) / 2;
1657   int z_highres_dist = (nz_high - 1) / 2;
1658 
1659   // number of fbins contained in the 26 weirdly-shaped edge regions (and one center region which we won't use)
1660   static int nfbinsin[3][3][3];
1661   for (auto &i : nfbinsin)
1662   {
1663     for (auto &j : i)
1664     {
1665       for (int &k : j)
1666       {
1667         k = 0;  // we could count total volume, but without knowing the charge prior, it's not clear that'd be /better/
1668       }
1669     }
1670   }
1671 
1672   // todo: if this runs too slowly, I can do geometry instead of looping over all the cells that are possibly in range
1673 
1674   // loop over all the f-bins in the roi:
1675   TVector3 currentf, newf;  // the averaged field vector without, and then with the new contribution from the f-bin being considered.
1676   for (int ifr = rmin_roi; ifr < rmax_roi; ifr++)
1677   {
1678     int r_parentlow = std::floor((ifr - r_highres_dist) / (r_spacing * 1.0));       // l-bin partly enclosed in our high-res region
1679     int r_parenthigh = std::floor((ifr + r_highres_dist) / (r_spacing * 1.0)) + 1;  // definitely not enclosed in our high-res region
1680     int r_startpoint = r_parentlow * r_spacing;                                // the first f-bin of the lowest-r f-bin that our h-region touches.  COuld be less than zero.
1681     int r_endpoint = r_parenthigh * r_spacing;                                 // the first f-bin of the lowest-r l-bin after that that our h-region does not touch.  could be larger than max.
1682 
1683     for (int ifphi = phimin_roi; ifphi < phimax_roi; ifphi++)
1684     {
1685       int phi_parentlow = std::floor(FilterPhiIndex(ifphi - phi_highres_dist) / (phi_spacing * 1.0));  // note this may have wrapped around
1686       bool phi_parentlow_wrapped = (ifphi - phi_highres_dist < 0);
1687       int phi_startpoint = phi_parentlow * phi_spacing;  // the first f-bin of the lowest-z f-bin that our h-region touches.
1688       if (phi_parentlow_wrapped)
1689       {
1690         phi_startpoint -= nphi;  // if we wrapped, re-wrap us so we're negative again
1691       }
1692 
1693       int phi_parenthigh = std::floor(FilterPhiIndex(ifphi + phi_highres_dist) / (phi_spacing * 1.0)) + 1;  // note that this may have wrapped around
1694       bool phi_parenthigh_wrapped = (ifphi + phi_highres_dist >= nphi);
1695       int phi_endpoint = phi_parenthigh * phi_spacing;
1696       if (phi_parenthigh_wrapped)
1697       {
1698         phi_endpoint += nphi;  // if we wrapped, re-wrap us so we're larger than nphi again.  We use these relative coords to determine the position relative to the center of our h-region.
1699       }
1700 
1701       for (int ifz = zmin_roi; ifz < zmax_roi; ifz++)
1702       {
1703         // if(debugFlag()) print_need_cout("%d: AnnularFieldSim::populate_highres_lookup icell=(%d,%d,%d)\n",__LINE__,ifr,ifphi,ifz);
1704 
1705         int z_parentlow = std::floor((ifz - z_highres_dist) / (z_spacing * 1.0));
1706         int z_parenthigh = std::floor((ifz + z_highres_dist) / (z_spacing * 1.0)) + 1;
1707         int z_startpoint = z_parentlow * z_spacing;  // the first f-bin of the lowest-z f-bin that our h-region touches.
1708         int z_endpoint = z_parenthigh * z_spacing;   // the first f-bin of the lowest-z l-bin after that that our h-region does not touch.
1709 
1710         // our 'at' position, in global coords:
1711         at = GetCellCenter(ifr, ifphi, ifz);
1712         // define the farthest-away parent l-bin cells we're dealing with here:
1713         // note we're still in absolute coordinates
1714 
1715         // for every f-bin in the l-bins we're dealing with, figure out which relative highres bin it's in, and average the field vector into that bin's vector
1716         // note most of these relative bins have exactly one f-bin in them.  It's only the edges that can get more.
1717         // note this is a running average:  Anew=(Aold*Nold+V)/(Nold+1) and so on.
1718         // note also that we automatically skip f-bins that would've been out of the valid overall volume.
1719         for (int ir = r_startpoint; ir < r_endpoint; ir++)
1720         {
1721           // skip parts that are out of range:
1722           // could speed this up by moving this into the definition of start and endpoint.
1723           if (ir < 0)
1724           {
1725             ir = 0;
1726           }
1727           if (ir >= nr)
1728           {
1729             break;
1730           }
1731 
1732           int rbin = (ir - ifr) + r_highres_dist;  // zeroth bin when we're at max distance below, etc.
1733           int rcell = 1;
1734           if (rbin <= 0)
1735           {
1736             rbin = 0;
1737             rcell = 0;
1738           }
1739           if (rbin >= nr_high)
1740           {
1741             rbin = nr_high - 1;
1742             rcell = 2;
1743           }
1744 
1745           for (int iphi = phi_startpoint; iphi < phi_endpoint; iphi++)
1746           {
1747             // no phi out-of-range checks since it's circular, but we provide ourselves a filtered version:
1748             int phiFilt = FilterPhiIndex(iphi);
1749             int phibin = (iphi - ifphi) + phi_highres_dist;
1750             int phicell = 1;
1751             if (phibin <= 0)
1752             {
1753               phibin = 0;
1754               phicell = 0;
1755             }
1756             if (phibin >= nphi_high)
1757             {
1758               phibin = nphi_high - 1;
1759               phicell = 2;
1760             }
1761             for (int iz = z_startpoint; iz < z_endpoint; iz++)
1762             {
1763               if (iz < 0)
1764               {
1765                 iz = 0;
1766               }
1767               if (iz >= nz)
1768               {
1769                 break;
1770               }
1771               int zbin = (iz - ifz) + z_highres_dist;
1772               int zcell = 1;
1773               if (zbin <= 0)
1774               {
1775                 zbin = 0;
1776                 zcell = 0;
1777               }
1778               if (zbin >= nz_high)
1779               {
1780                 zbin = nz_high - 1;
1781                 zcell = 2;
1782               }
1783               //'from' is in absolute coordinates
1784               from = GetCellCenter(ir, phiFilt, iz);
1785 
1786               nfbinsin[rcell][phicell][zcell]++;
1787               int nf = nfbinsin[rcell][phicell][zcell];
1788               // coordinates relative to the region of interest:
1789               int ir_rel = ifr - rmin_roi;
1790               int iphi_rel = ifphi - phimin_roi;
1791               int iz_rel = ifz - zmin_roi;
1792               if (zcell != 1 || rcell != 1 || phicell != 1)
1793               {
1794                 // we're not in the center, so deal with our weird shapes by averaging:
1795                 // but Epartial is in coordinates relative to the roi
1796                 if (iphi_rel < 0)
1797                 {
1798                   std::cout << boost::str(boost::format("%d: Getting with phi=%d") % __LINE__ % iphi_rel) << std::endl;
1799                 }
1800                 currentf = Epartial_highres->Get(ir_rel, iphi_rel, iz_rel, rbin, phibin, zbin);
1801                 // to keep this as the average, we multiply what's there back to its initial summed-but-not-divided value
1802                 // then add our new value, and the divide the new sum by the total number of cells
1803                 newf = (currentf * (nf - 1) + calc_unit_field(at, from)) * (1 / (nf * 1.0));
1804                 Epartial_highres->Set(ir_rel, iphi_rel, iz_rel, rbin, phibin, zbin, newf);
1805               }
1806               else
1807               {
1808                 // we're in the center cell, which means any f-bin that's not on the outer edge of our region:
1809                 // calc_unit_field will return zero when at=from, so the center will be automatically zero here.
1810                 if (ifr == rbin && ifphi == phibin && ifz == zbin)
1811                 {
1812                   Epartial_highres->Set(ir_rel, iphi_rel, iz_rel, rbin, phibin, zbin, zero);
1813                 }
1814                 else
1815                 {  // for extra carefulness, only calc the field if it's not self-to-self.
1816                   newf = calc_unit_field(at, from);
1817                   Epartial_highres->Set(ir_rel, iphi_rel, iz_rel, rbin, phibin, zbin, newf);
1818                 }
1819               }
1820             }
1821           }
1822         }
1823       }
1824     }
1825   }
1826   return;
1827 }
1828 
1829 void AnnularFieldSim::populate_lowres_lookup()
1830 {
1831   TVector3 at(1, 0, 0);
1832   TVector3 from(1, 0, 0);
1833   TVector3 zero(0, 0, 0);
1834   int fphi_low, fphi_high, fz_low, fz_high;             // edges of the outer l-bin
1835   int r_low, r_high, phi_low, phi_high, z_low, z_high;  // edges of the inner l-bin
1836 
1837   // todo:  add in handling if roi_low is wrap-around in phi
1838   for (int ifr = rmin_roi_low; ifr < rmax_roi_low; ifr++)
1839   {
1840     int fr_low = ifr * r_spacing;
1841     int fr_high = fr_low + r_spacing - 1;
1842     if (fr_high >= nr)
1843     {
1844       fr_high = nr - 1;
1845     }
1846     for (int ifphi = phimin_roi_low; ifphi < phimax_roi_low; ifphi++)
1847     {
1848       fphi_low = ifphi * phi_spacing;
1849       fphi_high = fphi_low + phi_spacing - 1;
1850       if (fphi_high >= nphi)
1851       {
1852         fphi_high = nphi - 1;  // if our phi l-bins aren't evenly spaced, we need to catch that here.
1853       }
1854       for (int ifz = zmin_roi_low; ifz < zmax_roi_low; ifz++)
1855       {
1856         fz_low = ifz * z_spacing;
1857         fz_high = fz_low + z_spacing - 1;
1858         if (fz_high >= nz)
1859         {
1860           fz_high = nz - 1;
1861         }
1862         at = GetGroupCellCenter(fr_low, fr_high, fphi_low, fphi_high, fz_low, fz_high);
1863         // print_need_cout("ifr=%d, rlow=%d,rhigh=%d,r_spacing=%d\n",ifr,r_low,r_high,r_spacing);
1864         // if(debugFlag())    print_need_cout("%d: AnnularFieldSim::populate_lowres_lookup icell=(%d,%d,%d)\n",__LINE__,ifr,ifphi,ifz);
1865 
1866         for (int ior = 0; ior < nr_low; ior++)
1867         {
1868           r_low = ior * r_spacing;
1869           r_high = r_low + r_spacing - 1;
1870           int ir_rel = ifr - rmin_roi_low;
1871 
1872           if (r_high >= nr)
1873           {
1874             r_high = nr - 1;
1875           }
1876           for (int iophi = 0; iophi < nphi_low; iophi++)
1877           {
1878             phi_low = iophi * phi_spacing;
1879             phi_high = phi_low + phi_spacing - 1;
1880             if (phi_high >= nphi)
1881             {
1882               phi_high = nphi - 1;
1883             }
1884             int iphi_rel = ifphi - phimin_roi_low;
1885             for (int ioz = 0; ioz < nz_low; ioz++)
1886             {
1887               z_low = ioz * z_spacing;
1888               z_high = z_low + z_spacing - 1;
1889               if (z_high >= nz)
1890               {
1891                 z_high = nz - 1;
1892               }
1893               int iz_rel = ifz - zmin_roi_low;
1894               from = GetGroupCellCenter(r_low, r_high, phi_low, phi_high, z_low, z_high);
1895 
1896               if (ifr == ior && ifphi == iophi && ifz == ioz)
1897               {
1898                 Epartial_lowres->Set(ir_rel, iphi_rel, iz_rel, ior, iophi, ioz, zero);
1899               }
1900               else
1901               {  // for extra carefulness, only calc the field if it's not self-to-self.
1902                 Epartial_lowres->Set(ir_rel, iphi_rel, iz_rel, ior, iophi, ioz, calc_unit_field(at, from));
1903               }
1904 
1905               //*f[ifx][ify][ifz][iox][ioy][ioz]=cacl_unit_field(at,from);
1906               // print_need_cout("calc_unit_field...\n");
1907               // this calc's okay.
1908             }
1909           }
1910         }
1911       }
1912     }
1913   }
1914   return;
1915 }
1916 
1917 void AnnularFieldSim::populate_phislice_lookup()
1918 {
1919   // with 'f' being the position the field is being measured at, and 'o' being the position of the charge generating the field.
1920   // remember the 'f' part of Epartial uses relative indices.
1921   //   TVector3 (*f)[fx][fy][fz][ox][oy][oz]=field_;
1922   std::cout << boost::str(boost::format("populating phislice  lookup for (%dx%dx%d)x(%dx%dx%d) grid") % nr_roi % 1 % nz_roi % nr % nphi % nz) << std::endl;
1923   unsigned long long totalelements = nr;  // nr*nphi*nz*nr_roi*nz_roi
1924   totalelements *= nphi;
1925   totalelements *= nz;
1926   totalelements *= nr_roi;
1927   totalelements *= nz_roi;  // breaking up this multiplication prevents a 32bit math overflow
1928   unsigned long long percent = totalelements / 100 * debug_npercent;
1929   std::cout << boost::str(boost::format("total elements = %llu") % totalelements) << std::endl;
1930   TVector3 at(1, 0, 0);
1931   TVector3 from(1, 0, 0);
1932   TVector3 zero(0, 0, 0);
1933 
1934   int el = 0;
1935   for (int ifr = rmin_roi; ifr < rmax_roi; ifr++)
1936   {
1937     for (int ifz = zmin_roi; ifz < zmax_roi; ifz++)
1938     {
1939       at = GetCellCenter(ifr, 0, ifz);
1940       for (int ior = 0; ior < nr; ior++)
1941       {
1942         for (int iophi = 0; iophi < nphi; iophi++)
1943         {
1944           for (int ioz = 0; ioz < nz; ioz++)
1945           {
1946             el++;
1947             from = GetCellCenter(ior, iophi, ioz);
1948             //*f[ifx][ify][ifz][iox][ioy][ioz]=cacl_unit_field(at,from);
1949             // print_need_cout("calc_unit_field...\n");
1950             if (ifr == ior && 0 == iophi && ifz == ioz)
1951             {
1952               if (!(el % percent))
1953               {
1954                 std::cout << boost::str(boost::format("populate_phislice_lookup %llu%%:  ") % ((uint64_t) (debug_npercent) *el / percent));
1955                 std::cout << boost::str(boost::format("self-to-self is zero (ir=%d,iphi=%d,iz=%d) to (or=%d,ophi=0,oz=%d) gives (%E,%E,%E)") % ior % iophi % ioz % ifr % ifz % zero.X() % zero.Y() % zero.Z()) << std::endl;
1956               }
1957               Epartial_phislice->Set(ifr - rmin_roi, 0, ifz - zmin_roi, ior, iophi, ioz, zero);
1958             }
1959             else
1960             {
1961               TVector3 unitf = calc_unit_field(at, from);
1962               if (true)
1963               {
1964                 if (!(el % percent))
1965                 {
1966                   std::cout << boost::str(boost::format("populate_phislice_lookup %llu%%:  ") % ((uint64_t) (debug_npercent) *el / percent));
1967                   std::cout << boost::str(boost::format("calc_unit_field (ir=%d,iphi=%d,iz=%d) to (or=%d,ophi=0,oz=%d) gives (%E,%E,%E)") % ior % iophi % ioz % ifr % ifz % unitf.X() % unitf.Y() % unitf.Z()) << std::endl;
1968                 }
1969               }
1970 
1971               Epartial_phislice->Set(ifr - rmin_roi, 0, ifz - zmin_roi, ior, iophi, ioz, unitf);  // the origin phi is relative to zero anyway.
1972             }
1973           }
1974         }
1975       }
1976     }
1977   }
1978   return;
1979 }
1980 
1981 void AnnularFieldSim::load_phislice_lookup(const std::string &sourcefile)
1982 {
1983   std::cout << boost::str(boost::format("loading phislice  lookup for (%dx%dx%d)x(%dx%dx%d) grid from %s") % nr_roi % 1 % nz_roi % nr % nphi % nz % sourcefile) << std::endl;
1984   unsigned long long totalelements = nr;  // nr*nphi*nz*nr_roi*nz_roi
1985   totalelements *= nphi;
1986   totalelements *= nz;
1987   totalelements *= nr_roi;
1988   totalelements *= nz_roi;  // breaking up this multiplication prevents a 32bit math overflow
1989   unsigned long long percent = totalelements / 100 * debug_npercent;
1990   std::cout << boost::str(boost::format("total elements = %llu") % totalelements) << std::endl;
1991 
1992   TFile *input = TFile::Open(sourcefile.c_str(), "READ");
1993   TTree *tInfo;
1994   input->GetObject("info", tInfo);
1995   assert(tInfo);
1996 
1997   float file_rmin, file_rmax, file_zmin, file_zmax;
1998   int file_rmin_roi, file_rmax_roi, file_zmin_roi, file_zmax_roi;
1999   int file_nr, file_np, file_nz;
2000   tInfo->SetBranchAddress("rmin", &file_rmin);
2001   tInfo->SetBranchAddress("rmax", &file_rmax);
2002   tInfo->SetBranchAddress("zmin", &file_zmin);
2003   tInfo->SetBranchAddress("zmax", &file_zmax);
2004   tInfo->SetBranchAddress("rmin_roi_index", &file_rmin_roi);
2005   tInfo->SetBranchAddress("rmax_roi_index", &file_rmax_roi);
2006   tInfo->SetBranchAddress("zmin_roi_index", &file_zmin_roi);
2007   tInfo->SetBranchAddress("zmax_roi_index", &file_zmax_roi);
2008   tInfo->SetBranchAddress("nr", &file_nr);
2009   tInfo->SetBranchAddress("nphi", &file_np);
2010   tInfo->SetBranchAddress("nz", &file_nz);
2011   tInfo->GetEntry(0);
2012 
2013   std::cout << "param\tobj\tfile" << std::endl;
2014   ;
2015   std::cout << boost::str(boost::format("nr\t%d\t%d") % nr % file_nr) << std::endl;
2016   std::cout << boost::str(boost::format("np\t%d\t%d") % nphi % file_np) << std::endl;
2017   std::cout << boost::str(boost::format("nz\t%d\t%d") % nz % file_nz) << std::endl;
2018   std::cout << boost::str(boost::format("rmin\t%2.2f\t%2.2f") % rmin % file_rmin) << std::endl;
2019   std::cout << boost::str(boost::format("rmax\t%2.2f\t%2.2f") % rmax % file_rmax) << std::endl;
2020   std::cout << boost::str(boost::format("zmin\t%2.2f\t%2.2f") % zmin % file_zmin) << std::endl;
2021   std::cout << boost::str(boost::format("zmax\t%2.2f\t%2.2f") % zmax % file_zmax) << std::endl;
2022   std::cout << boost::str(boost::format("rmin_roi\t%d\t%d") % rmin_roi % file_rmin_roi) << std::endl;
2023   std::cout << boost::str(boost::format("rmax_roi\t%d\t%d") % rmax_roi % file_rmax_roi) << std::endl;
2024   std::cout << boost::str(boost::format("zmin_roi\t%d\t%d") % zmin_roi % file_zmin_roi) << std::endl;
2025   std::cout << boost::str(boost::format("zmax_roi\t%d\t%d") % zmax_roi % file_zmax_roi) << std::endl;
2026 
2027   if (file_rmin != rmin || file_rmax != rmax ||
2028       file_zmin != zmin || file_zmax != zmax ||
2029       file_rmin_roi != rmin_roi || file_rmax_roi != rmax_roi ||
2030       file_zmin_roi != zmin_roi || file_zmax_roi != zmax_roi ||
2031       file_nr != nr || file_np != nphi || file_nz != nz)
2032   {
2033     std::cout << "file parameters do not match fieldsim parameters:" << std::endl;
2034     ;
2035 
2036     exit(1);
2037   }
2038 
2039   TTree *tLookup;
2040   input->GetObject("phislice", tLookup);
2041   assert(tLookup);
2042   int ior, ifr, iophi, ioz, ifz;
2043   TVector3 *unitf = nullptr;
2044   tLookup->SetBranchAddress("ir_source", &ior);
2045   tLookup->SetBranchAddress("ir_target", &ifr);
2046   tLookup->SetBranchAddress("ip_source", &iophi);
2047   // always zero: tLookup->SetBranchAddress("ip_target",&ifphi);
2048   tLookup->SetBranchAddress("iz_source", &ioz);
2049   tLookup->SetBranchAddress("iz_target", &ifz);
2050   tLookup->SetBranchAddress("Evec", &unitf);  // assume field has units V/(C*cm)
2051 
2052   int el = 0;
2053   std::cout << boost::str(boost::format("%s has %lld entries") % sourcefile % tLookup->GetEntries());
2054   for (int i = 0; i < (int) totalelements; i++)
2055   {
2056     el++;
2057     tLookup->GetEntry(i);
2058     // print_need_cout("loading i=%d\n",i);
2059     Epartial_phislice->Set(ifr - rmin_roi, 0, ifz - zmin_roi, ior, iophi, ioz, (*unitf) * (-1.0) * (V / (cm * C)));  // load assuming field has units V/(C*cm), which is how we save it.
2060     // note that we save the gradient terms, not the field, hence we need to multiply by (-1.0)
2061     if (!(el % percent))
2062     {
2063       std::cout << boost::str(boost::format("load_phislice_lookup %llu%%:  ") % ((uint64_t) (debug_npercent) *el / percent));
2064       std::cout << boost::str(boost::format("field from (ir=%d,iphi=%d,iz=%d) to (or=%d,ophi=0,oz=%d) is (%E,%E,%E)") % ior % iophi % ioz % ifr % ifz % unitf->X() % unitf->Y() % unitf->Z()) << std::endl;
2065     }
2066   }
2067 
2068   input->Close();
2069   return;
2070 }
2071 
2072 void AnnularFieldSim::save_phislice_lookup(const std::string &destfile)
2073 {
2074   std::cout << boost::str(boost::format("saving phislice  lookup for (%dx%dx%d)x(%dx%dx%d) grid to %s") % nr_roi % 1 % nz_roi % nr % nphi % nz % destfile) << std::endl;
2075   unsigned long long totalelements = nr;  // nr*nphi*nz*nr_roi*nz_roi
2076   totalelements *= nphi;
2077   totalelements *= nz;
2078   totalelements *= nr_roi;
2079   totalelements *= nz_roi;  // breaking up this multiplication prevents a 32bit math overflow
2080   unsigned long long percent = totalelements / 100 * debug_npercent;
2081   std::cout << boost::str(boost::format("total elements = %llu") % totalelements) << std::endl;
2082 
2083   TFile *output = TFile::Open(destfile.c_str(), "RECREATE");
2084   output->cd();
2085 
2086   TTree *tInfo = new TTree("info", "Information about Lookup Table");
2087   tInfo->Branch("rmin", &rmin);
2088   tInfo->Branch("rmax", &rmax);
2089   tInfo->Branch("zmin", &zmin);
2090   tInfo->Branch("zmax", &zmax);
2091   tInfo->Branch("rmin_roi_index", &rmin_roi);
2092   tInfo->Branch("rmax_roi_index", &rmax_roi);
2093   tInfo->Branch("zmin_roi_index", &zmin_roi);
2094   tInfo->Branch("zmax_roi_index", &zmax_roi);
2095   tInfo->Branch("nr", &nr);
2096   tInfo->Branch("nphi", &nphi);
2097   tInfo->Branch("nz", &nz);
2098   tInfo->Fill();
2099   std::cout << "info tree built." << std::endl;
2100   ;
2101 
2102   TTree *tLookup = new TTree("phislice", "Phislice Lookup Table");
2103   int ior, ifr, iophi, ioz, ifz;
2104   TVector3 unitf;
2105   tLookup->Branch("ir_source", &ior);
2106   tLookup->Branch("ir_target", &ifr);
2107   tLookup->Branch("ip_source", &iophi);
2108   // always zero: tLookup->Branch("ip_target",&ifphi);
2109   tLookup->Branch("iz_source", &ioz);
2110   tLookup->Branch("iz_target", &ifz);
2111   tLookup->Branch("Evec", &unitf);
2112   std::cout << "lookup tree built." << std::endl;
2113   ;
2114 
2115   int el = 0;
2116   for (ifr = rmin_roi; ifr < rmax_roi; ifr++)
2117   {
2118     for (ifz = zmin_roi; ifz < zmax_roi; ifz++)
2119     {
2120       for (ior = 0; ior < nr; ior++)
2121       {
2122         for (iophi = 0; iophi < nphi; iophi++)
2123         {
2124           for (ioz = 0; ioz < nz; ioz++)
2125           {
2126             el++;
2127             unitf = Epartial_phislice->Get(ifr - rmin_roi, 0, ifz - zmin_roi, ior, iophi, ioz) * (-1 / (V / (C * cm)));  // save in units of V/(C*cm) note that we introduce a -1 here for legcy reasons.
2128             if (true)
2129             {
2130               if (!(el % percent))
2131               {
2132                 std::cout << boost::str(boost::format("save_phislice_lookup %llu%%:  ") % ((uint64_t) (debug_npercent) *el / percent));
2133                 std::cout << boost::str(boost::format("field from (ir=%d,iphi=%d,iz=%d) to (or=%d,ophi=0,oz=%d) is (%E,%E,%E)") % ior % iophi % ioz % ifr % ifz % unitf.X() % unitf.Y() % unitf.Z()) << std::endl;
2134               }
2135             }
2136 
2137             tLookup->Fill();
2138           }
2139         }
2140       }
2141     }
2142   }
2143   output->cd();
2144   tInfo->Write();
2145   tLookup->Write();
2146   // output->Write();
2147   output->Close();
2148   return;
2149 }
2150 
2151 void AnnularFieldSim::setFlatFields(float B, float E)
2152 {
2153   // these only cover the roi, but since we address them flat, we don't need to know that here.
2154   std::cout << boost::str(boost::format("AnnularFieldSim::setFlatFields(B=%f Tesla,E=%f V/cm)") % B % E) << std::endl;
2155   std::cout << boost::str(boost::format("lengths:  Eext=%d, Bfie=%d") % Eexternal->Length() % Bfield->Length()) << std::endl;
2156   Efieldname = "E:Flat:" + std::to_string(E);
2157   Bfieldname = "B:Flat:" + std::to_string(B);
2158 
2159   Enominal = E * (V / cm);
2160   Bnominal = B * Tesla;
2161   for (int i = 0; i < Eexternal->Length(); i++)
2162   {
2163     Eexternal->GetFlat(i)->SetXYZ(0, 0, Enominal);
2164   }
2165   for (int i = 0; i < Bfield->Length(); i++)
2166   {
2167     Bfield->GetFlat(i)->SetXYZ(0, 0, Bnominal);
2168   }
2169   UpdateOmegaTau();
2170   return;
2171 }
2172 
2173 TVector3 AnnularFieldSim::sum_field_at(int r, int phi, int z)
2174 {
2175   // sum the E field over all nr by ny by nz cells of sources, at the global coordinate position r,phi,z.
2176   // note the specific position in Epartial is in relative coordinates.
2177   //  if(debugFlag()) print_need_cout("%d: AnnularFieldSim::sum_field_at(r=%d,phi=%d, z=%d)\n",__LINE__,r,phi,z);
2178 
2179   /*
2180     for the near future:
2181   TVector3 sum=(sum_local_field_at(r, phi, z)
2182                 + sum_nonlocal_field_at(r,phi,z)
2183                 + Escale*Eexternal->Get(r-rmin_roi,phi-phimin_roi,z-zmin_roi));
2184   return sum;
2185   */
2186 
2187   TVector3 sum(0, 0, 0);
2188   if (lookupCase == Full3D)
2189   {
2190     sum += sum_full3d_field_at(r, phi, z);
2191   }
2192   else if (lookupCase == HybridRes)
2193   {
2194     sum += sum_local_field_at(r, phi, z);
2195     sum += sum_nonlocal_field_at(r, phi, z);
2196   }
2197   else if (lookupCase == PhiSlice)
2198   {
2199     sum += sum_phislice_field_at(r, phi, z);
2200   }
2201   else if (lookupCase == Analytic)
2202   {
2203     sum += aliceModel->E(GetCellCenter(r, phi, z));
2204   }
2205   else if (lookupCase == NoLookup)
2206   {
2207     // do nothing.  We are forcibly assuming E from spacecharge is zero everywhere.
2208   }
2209   sum += Eexternal->Get(r - rmin_roi, phi - phimin_roi, z - zmin_roi);
2210   if (debugFlag())
2211   {
2212     std::cout << boost::str(boost::format("summed field at (%d,%d,%d)=(%f,%f,%f)") % r % phi % z % sum.X() % sum.Y() % sum.Z()) << std::endl;
2213   }
2214 
2215   return sum;
2216 }
2217 
2218 TVector3 AnnularFieldSim::sum_full3d_field_at(int r, int phi, int z)
2219 {
2220   // sum the E field over all nr by ny by nz cells of sources, at the specific position r,phi,z.
2221   // note the specific position in Epartial is in relative coordinates.
2222   // print_need_cout("AnnularFieldSim::sum_field_at(r=%d,phi=%d, z=%d)\n",r,phi,z);
2223   TVector3 sum(0, 0, 0);
2224   float rdist, phidist, zdist, remdist;
2225   for (int ir = 0; ir < nr; ir++)
2226   {
2227     if (truncation_length > 0)
2228     {
2229       rdist = std::abs(ir - r);
2230       remdist = sqrt(truncation_length * truncation_length - rdist * rdist);
2231       if (remdist < 0)
2232       {
2233         continue;  // skip if we're too far away
2234       }
2235     }
2236     for (int iphi = 0; iphi < nphi; iphi++)
2237     {
2238       if (truncation_length > 0)
2239       {
2240         phidist = fmin(std::abs(iphi - phi), std::abs(std::abs(iphi - phi) - nphi));  // think about this in phi... rcc food for thought.
2241         remdist = sqrt(truncation_length * truncation_length - phidist * phidist);
2242         if (remdist < 0)
2243         {
2244           continue;  // skip if we're too far away
2245         }
2246       }
2247       for (int iz = 0; iz < nz; iz++)
2248       {
2249         if (truncation_length > 0)
2250         {
2251           zdist = std::abs(iz - z);
2252           remdist = sqrt(truncation_length * truncation_length - zdist * zdist);
2253           if (remdist < 0)
2254           {
2255             continue;  // skip if we're too far away
2256           }
2257         }
2258         // sum+=*partial[x][phi][z][ix][iphi][iz] * *q[ix][iphi][iz];
2259         if (r == ir && phi == iphi && z == iz)
2260         {
2261           continue;  // dont' compute self-to-self field.
2262         }
2263         sum += Epartial->Get(r - rmin_roi, phi - phimin_roi, z - zmin_roi, ir, iphi, iz) * q->GetChargeInBin(ir, iphi, iz);
2264       }
2265     }
2266   }
2267   // print_need_cout("summed field at (%d,%d,%d)=(%f,%f,%f)\n",x,y,z,sum.X(),sum.Y(),sum.Z());
2268   return sum;
2269 }
2270 
2271 TVector3 AnnularFieldSim::sum_local_field_at(int r, int phi, int z)
2272 {
2273   // do the summation of the inner high-resolution region charges:
2274   //
2275   //  bin 0  1 2 ...  n-2 n-1
2276   //   . . .|.|.|.|.|.|.|. . .
2277   //        | | | | | | |
2278   //   . . .|.|.|.|.|.|.|. . .
2279   //   -----+-+-+-+-+-+-+-----
2280   //   . . .|.|.|.|.|.|.|. . .
2281   //   -----+-+-+-+-+-+-+-----
2282   //   . . .|.|.|.|.|.|.|. . .
2283   //   -----+-+-+-+-+-+-+-----
2284   //   . . .|.|.|.|.|.|.|. . .
2285   //   -----+-+-+-+-+-+-+-----
2286   //   . . .|.|.|.|.|.|.|. . .
2287   //        | | | | | | |
2288   //   . . .|.|.|.|.|.|.|. . .
2289   //
2290   //
2291 
2292   int r_highres_dist = (nr_high - 1) / 2;
2293   int phi_highres_dist = (nphi_high - 1) / 2;
2294   int z_highres_dist = (nz_high - 1) / 2;
2295 
2296   int r_parentlow = std::floor((r - r_highres_dist) / (r_spacing * 1.0));       // partly enclosed in our high-res region
2297   int r_parenthigh = std::floor((r + r_highres_dist) / (r_spacing * 1.0)) + 1;  // definitely not enclosed in our high-res region
2298   int phi_parentlow = std::floor((phi - phi_highres_dist) / (phi_spacing * 1.0));
2299   int phi_parenthigh = std::floor((phi + phi_highres_dist) / (phi_spacing * 1.0)) + 1;  // note that this can be bigger than nphi!  We keep track of that.
2300   int z_parentlow = std::floor((z - z_highres_dist) / (z_spacing * 1.0));
2301   int z_parenthigh = std::floor((z + z_highres_dist) / (z_spacing * 1.0)) + 1;
2302   std::cout << boost::str(boost::format("AnnularFieldSim::sum_local_field_at parents: rlow=%d,philow=%d,zlow=%d,rhigh=%d,phihigh=%d,zhigh=%d") % r_parentlow % phi_parentlow % z_parentlow % r_parenthigh % phi_parenthigh % z_parenthigh) << std::endl;
2303 
2304   // zero our current qlocal holder:
2305   for (int i = 0; i < q_local->Length(); i++)
2306   {
2307     *(q_local->GetFlat(i)) = 0;
2308   }
2309 
2310   // get the charge involved in the local highres block:
2311   for (int ir = r_parentlow * r_spacing; ir < r_parenthigh * r_spacing; ir++)
2312   {
2313     if (ir < 0)
2314     {
2315       ir = 0;
2316     }
2317     if (ir >= nr)
2318     {
2319       break;
2320     }
2321     int rbin = (ir - r) + r_highres_dist;  // index in our highres locale.  zeroth bin when we're at max distance below, etc.
2322     if (rbin < 0)
2323     {
2324       rbin = 0;
2325     }
2326     if (rbin >= nr_high)
2327     {
2328       rbin = nr_high - 1;
2329     }
2330     for (int iphi = phi_parentlow * phi_spacing; iphi < phi_parenthigh * phi_spacing; iphi++)
2331     {
2332       // no phi range checks since it's circular.
2333       int phiFilt = FilterPhiIndex(iphi);
2334       int phibin = (iphi - phi) + phi_highres_dist;
2335       if (phibin < 0)
2336       {
2337         phibin = 0;
2338       }
2339       if (phibin >= nphi_high)
2340       {
2341         phibin = nphi_high - 1;
2342       }
2343       for (int iz = z_parentlow * z_spacing; iz < z_parenthigh * z_spacing; iz++)
2344       {
2345         if (iz < 0)
2346         {
2347           iz = 0;
2348         }
2349         if (iz >= nz)
2350         {
2351           break;
2352         }
2353         // if(debugFlag()) print_need_cout("%d: AnnularFieldSim::sum_field_at, reading q in f-bin at(r=%d,phi=%d, z=%d)\n",__LINE__,ir,iphi,iz);
2354 
2355         int zbin = (iz - z) + z_highres_dist;
2356         if (zbin < 0)
2357         {
2358           zbin = 0;
2359         }
2360         if (zbin >= nz_high)
2361         {
2362           zbin = nz_high - 1;
2363         }
2364         // print_need_cout("filtering in local highres block\n");
2365         q_local->Add(rbin, phibin, zbin, q->GetChargeInBin(ir, phiFilt, iz));
2366         // print_need_cout("done filtering in local highres block\n");
2367       }
2368     }
2369   }
2370 
2371   // now q_highres is up to date for our current cell of interest.
2372   // start building our full sum by scaling the local lookup table by q.
2373   // note that the lookup table needs to have already accounted for cell centers.
2374 
2375   TVector3 sum(0, 0, 0);
2376 
2377   // note that Epartial_highres returns zero if we're outside of the global region.  q_local will also be zero there.
2378   // these are loops over the position in the epartial highres grid, so relative to the point in question:
2379   // reminder: variables r, phi, and z are global f-bin indices.
2380 
2381   // assuming highres correctly gives a zero when asked for the middle element in each of the last three indices,
2382   // and assuming q_local has no contribution from regions outside the global bounds, this is correct:
2383   for (int ir = 0; ir < nr_high; ir++)
2384   {
2385     for (int iphi = 0; iphi < nphi_high; iphi++)
2386     {
2387       for (int iz = 0; iz < nz_high; iz++)
2388       {
2389         // first three are relative to the roi, last three are relative to the point in the first three.  ooph.
2390         if (phi - phimin_roi < 0)
2391         {
2392           std::cout << boost::str(boost::format("%d: Getting with phi=%d") % __LINE__ % (phi - phimin_roi)) << std::endl;
2393         }
2394         sum += Epartial_highres->Get(r - rmin_roi, phi - phimin_roi, z - zmin_roi, ir, iphi, iz) * q_local->Get(ir, iphi, iz);
2395       }
2396     }
2397   }
2398 
2399   return sum;
2400 }
2401 
2402 TVector3 AnnularFieldSim::sum_nonlocal_field_at(int r, int phi, int z)
2403 {
2404   // now we look for our low_res contribution, which will be the interpolated summed field from the eight blocks closest to our f-bin of interest:
2405 
2406   // find our interpolated position between the eight nearby lowres cells:
2407   // this is similar to the interpolated integral stuff, except we're at integer steps inside integer blocks
2408   // and we have z as well, now.
2409   bool skip[] = {false, false, false, false, false, false, false, false};
2410 
2411   float r0 = r / (1.0 * r_spacing) - 0.5 - rmin_roi_low;  // the position in r, in units of r_spacing, starting from the center of the 0th l-bin in the roi.
2412   int r0i = std::floor(r0);                                    // the integer portion of the position. -- what center is below our position?
2413   float r0d = r0 - r0i;                                   // the decimal portion of the position. -- how far past center are we?
2414   // instead of listing all eight, I'll address these as i%2, (i/2)%2 and (i/4)%2 to avoid typos
2415   int ri[2];  // the r position of the eight cell centers to consider.
2416   ri[0] = r0i;
2417   ri[1] = r0i + 1;
2418   float rw[2];      // the weight of that cell, linear in distance from the center of it
2419   rw[0] = 1 - r0d;  // 1 when we're on it, 0 when we're at the other one.
2420   rw[1] = r0d;      // 1 when we're on it, 0 when we're at the other one
2421 
2422   // zero out if the requested l-bin is out of the roi (happens if we're close to the outer than the inner edge of the outermost l-bin)
2423   if (ri[0] < 0)
2424   {
2425     for (int i = 0; i < 8; i++)
2426     {
2427       if ((i / 4) % 2 == 0)
2428       {
2429         skip[i] = true;  // don't handle contributions from ri[0].
2430       }
2431     }
2432     rw[1] = 1;  // and weight like we're dead-center on the outer cells.
2433   }
2434   if (ri[1] >= nr_roi_low)
2435   {
2436     for (int i = 0; i < 8; i++)
2437     {
2438       if ((i / 4) % 2 == 1)
2439       {
2440         skip[i] = true;  // don't bother handling ri[1].
2441       }
2442     }
2443     rw[0] = 1;  // and weight like we're dead-center on the inner cells.
2444   }
2445 
2446   // now repeat that structure for phi:
2447 
2448   float p0 = phi / (1.0 * phi_spacing) - 0.5 - phimin_roi_low;  // the position 'phi' in  units of phi_spacing, starting from the center of the 0th bin.
2449   // print_need_cout("prepping to filter low, p0=%f, phi=%d, phi_spacing=%d, phimin_roi_low=%d\n",p0,phi,phi_spacing,phimin_roi_low);
2450 
2451   int p0i = std::floor(p0);   // the integer portion of the position. -- what center is below our position?
2452   float p0d = p0 - p0i;  // the decimal portion of the position. -- how far past center are we?
2453   int pi[4];             // the local phi position of the four l-bin centers to consider
2454   // print_need_cout("filtering low, p0i=%d, nphi_low=%d\n",p0i,nphi_low);
2455   // Q: why do I need to filter here?
2456   // A: Worst case scenario, this produces -1<p0<0.  If that wraps around and is still in the roi, I should include it
2457   // A: Likewise, if I get p0>nphi_low, then that means it ought to wrap around and be considered against the other limit.
2458   pi[0] = FilterPhiIndex(p0i, nphi_low);
2459   pi[1] = FilterPhiIndex(p0i + 1, nphi_low);
2460   // having filtered, we will always have a positive number.
2461 
2462   // print_need_cout("done filtering low\n");
2463   float pw[2];      // the weight of that cell, linear in distance from the center of it
2464   pw[0] = 1 - p0d;  // 1 when we're on it, 0 when we're at the other one.
2465   pw[1] = p0d;      // 1 when we're on it, 0 when we're at the other one
2466 
2467   // note that since phi wraps around itself, we have the possibility of being outside by being above/below the opposite end of where we thought we were
2468   // remember these are coordinates wrt the beginning of the roi, and are positive because we filtered.  If that rotation didn't put them back in the roi, then they weren't before, either.
2469   if (pi[0] >= nphi_roi_low)
2470   {
2471     for (int i = 0; i < 8; i++)
2472     {
2473       if ((i / 2) % 2 == 0)
2474       {
2475         skip[i] = true;  // don't bother handling pi[0].
2476       }
2477     }
2478     pw[1] = 1;  // and weight like we're dead-center on the high cells.
2479   }
2480   if (pi[1] >= nphi_roi_low)
2481   {
2482     for (int i = 0; i < 8; i++)
2483     {
2484       if ((i / 2) % 2 == 1)
2485       {
2486         skip[i] = true;  // don't bother handling pi[1].
2487       }
2488     }
2489     pw[0] = 1;  // and weight like we're dead-center on the high cells.
2490   }
2491 
2492   // and once more for z.  ooph.
2493 
2494   float z0 = z / (1.0 * z_spacing) - 0.5 - zmin_roi_low;  // the position in r, in units of r_spacing, starting from the center of the 0th bin.
2495   int z0i = std::floor(z0);                                    // the integer portion of the position. -- what center is below our position?
2496   float z0d = z0 - z0i;                                   // the decimal portion of the position. -- how far past center are we?
2497   // instead of listing all eight, I'll address these as i%2, (i/2)%2 and (i/4)%2 to avoid typos
2498   int zi[2];  // the r position of the eight cell centers to consider.
2499   zi[0] = z0i;
2500   zi[1] = z0i + 1;
2501   float zw[2];      // the weight of that cell, linear in distance from the center of it
2502   zw[0] = 1 - z0d;  // 1 when we're on it, 0 when we're at the other one.
2503   zw[1] = z0d;      // 1 when we're on it, 0 when we're at the other one
2504 
2505   if (zi[0] < 0)
2506   {
2507     for (int i = 0; i < 8; i++)
2508     {
2509       if ((i) % 2 == 0)
2510       {
2511         skip[i] = true;  // don't bother handling zi[0].
2512       }
2513     }
2514     zw[1] = 1;  // and weight like we're dead-center on the higher cells.
2515   }
2516   if (zi[1] >= nz_roi_low)
2517   {
2518     for (int i = 0; i < 8; i++)
2519     {
2520       if ((i) % 2 == 1)
2521       {
2522         skip[i] = true;  // don't bother handling zi[1].
2523       }
2524     }
2525     zw[0] = 1;  // and weight like we're dead-center on the lower cells.
2526   }
2527 
2528   TVector3 sum(0, 0, 0);
2529   // at this point, we should be skipping all destination l-bins that are out-of-bounds.
2530   // note that if any out-of-bounds ones survive somehow, the call to Epartial_lowres will fail loudly.
2531   int lBinEdge[2];              // lower and upper (included) edges of the low-res bin, measured in f-bins, reused per-dimension
2532   int hRegionEdge[2];           // lower and upper edge of the high-res region, measured in f-bins, reused per-dimension.
2533   bool overlapsPhi, overlapsZ;  // whether we overlap in R, phi, and z.
2534 
2535   int r_highres_dist = (nr_high - 1) / 2;
2536   int phi_highres_dist = (nphi_high - 1) / 2;
2537   int z_highres_dist = (nz_high - 1) / 2;
2538 
2539   for (int ir = 0; ir < nr_low; ir++)
2540   {
2541     lBinEdge[0] = ir * r_spacing;
2542     lBinEdge[1] = (ir + 1) * r_spacing - 1;
2543     hRegionEdge[0] = r - r_highres_dist;
2544     hRegionEdge[1] = r + r_highres_dist;
2545     bool overlapsR = (lBinEdge[0] <= hRegionEdge[1] && hRegionEdge[0] <= lBinEdge[1]);
2546     for (int iphi = 0; iphi < nphi_low; iphi++)
2547     {
2548       lBinEdge[0] = iphi * phi_spacing;
2549       lBinEdge[1] = (iphi + 1) * phi_spacing - 1;
2550       hRegionEdge[0] = phi - phi_highres_dist;
2551       hRegionEdge[1] = phi + phi_highres_dist;
2552       overlapsPhi = (lBinEdge[0] <= hRegionEdge[1] && hRegionEdge[0] <= lBinEdge[1]);
2553       for (int iz = 0; iz < nz_low; iz++)
2554       {
2555         lBinEdge[0] = iz * z_spacing;
2556         lBinEdge[1] = (iz + 1) * z_spacing - 1;
2557         hRegionEdge[0] = z - z_highres_dist;
2558         hRegionEdge[1] = z + z_highres_dist;
2559         overlapsZ = (lBinEdge[0] <= hRegionEdge[1] && hRegionEdge[0] <= lBinEdge[1]);
2560         // conceptually: see if the l-bin overlaps with the high-res region:
2561         // the high-res region includes all indices from r-(nr_high-1)/2 to r+(nr_high-1)/2.
2562         // each low-res region includes all indices from ir*r_spacing to (ir+1)*r_spacing-1.
2563         if (overlapsR && overlapsPhi && overlapsZ)
2564         {
2565           // if their bounds are interleaved in all dimensions, there is overlap, and we've already summed this region.
2566           continue;
2567         }
2568         else
2569         {
2570           // if(debugFlag()) print_need_cout("%d: AnnularFieldSim::sum_field_at, considering l-bin at(r=%d,phi=%d, z=%d)\n",__LINE__,ir,iphi,iz);
2571 
2572           for (int i = 0; i < 8; i++)
2573           {
2574             if (skip[i])
2575             {
2576               continue;
2577             }
2578             if (ri[(i / 4) % 2] + rmin_roi_low == ir && pi[(i / 2) % 2] + phimin_roi_low == iphi && zi[(i) % 2] + zmin_roi_low == iz)
2579             {
2580               std::cout << boost::str(boost::format("considering an l-bins effect on itself, r=%d,phi=%d,z=%d (matches i=%d, not skipped), means we're not interpolating fairly") % ir % iphi % iz % i) << std::endl;
2581               exit(1);
2582             }
2583             // the ri, pi, and zi elements are relative to the roi, as needed for Epartial.
2584             // the ir, iphi, and iz are all absolute, as needed for q_lowres
2585             if (pi[(i / 2) % 2] < 0)
2586             {
2587               std::cout << boost::str(boost::format("%d: Getting with phi=%d") % __LINE__ % (pi[(i / 2) % 2])) << std::endl;
2588             }
2589             sum += (Epartial_lowres->Get(ri[(i / 4) % 2], pi[(i / 2) % 2], zi[(i) % 2], ir, iphi, iz) * q_lowres->Get(ir, iphi, iz)) * zw[(i) % 2] * pw[(i / 2) % 2] * rw[(i / 4) % 2];
2590           }
2591         }
2592       }
2593     }
2594   }
2595 
2596   return sum;
2597 }
2598 
2599 TVector3 AnnularFieldSim::sum_phislice_field_at(int r, int phi, int z)
2600 {
2601   // sum the E field over all nr by ny by nz cells of sources, at the specific position r,phi,z.
2602   // note the specific position in Epartial is in relative coordinates.
2603   // print_need_cout("AnnularFieldSim::sum_field_at(r=%d,phi=%d, z=%d)\n",r,phi,z);
2604   TVector3 pos = GetRoiCellCenter(r - rmin_roi, phi - phimin_roi, z - zmin_roi);
2605   TVector3 slicepos = GetRoiCellCenter(r - rmin_roi, 0, z - zmin_roi);
2606   float rotphi = pos.Phi() - slicepos.Phi();  // probably this is phi*step.Phi();
2607 
2608   // caution:  if you print progress of each of these, it will print a great deal of data, since this is called per-bin
2609   // unsigned long long totalelements=nr*nphi*nz;
2610   //  unsigned long long percent=totalelements/100;
2611 
2612   // unsigned long long el=0;
2613 
2614   TVector3 sum(0, 0, 0);
2615   TVector3 unrotatedField(0, 0, 0);
2616   TVector3 unitField(0, 0, 0);
2617   int phirel;
2618   for (int ir = 0; ir < nr; ir++)
2619   {
2620     for (int iphi = 0; iphi < nphi; iphi++)
2621     {
2622       for (int iz = 0; iz < nz; iz++)
2623       {
2624         // sum+=*partial[x][phi][z][ix][iphi][iz] * *q[ix][iphi][iz];
2625         if (r == ir && phi == iphi && z == iz)
2626         {
2627           continue;  // dont' compute self-to-self field.
2628         }
2629         phirel = FilterPhiIndex(iphi - phi);
2630         unitField = Epartial_phislice->Get(r - rmin_roi, 0, z - zmin_roi, ir, phirel, iz);
2631         unitField.RotateZ(rotphi);  // previously was rotate by the step.Phi()*phi.    //annoying that I can't rename this to 'rotated field' here without unnecessary overhead.
2632 
2633         sum += unitField * q->GetChargeInBin(ir, iphi, iz);
2634         ;
2635 
2636         /*
2637         if(!(el%percent)) {print_need_cout("summing phislices %d%%:  ",(int)(el/percent));
2638           print_need_cout("unit field at (r=%d,p=%d,z=%d) from  (ir=%d,ip=%d,iz=%d) is (%E,%E,%E) (xyz), q=%E\n",
2639                  r,phi,z,ir,iphi,iz,unitField.X(),unitField.Y(),unitField.Z(),q->GetChargeInBin(ir,iphi,iz));
2640         }
2641         el++;
2642         */
2643       }
2644     }
2645   }
2646   // print_need_cout("summed field at (%d,%d,%d)=(%f,%f,%f)\n",x,y,z,sum.X(),sum.Y(),sum.Z());
2647   return sum;
2648 }
2649 
2650 TVector3 AnnularFieldSim::swimToInAnalyticSteps(float zdest, TVector3 start, int steps = 1, int *goodToStep = nullptr)
2651 {
2652   // assume coordinates are given in native units (cm=1 unless that changed!).
2653   double zdist = (zdest - start.Z()) * cm;
2654   double zstep = zdist / steps;
2655   start = start * cm;  // scale us to cm.
2656 
2657   TVector3 ret = start;
2658   TVector3 accumulated_distortion(0, 0, 0);
2659   TVector3 accumulated_drift(0, 0, 0);
2660   TVector3 drift_step(0, 0, zstep);
2661 
2662   int rt, pt, zt;  // just placeholders for the bounds-checking.
2663   BoundsCase zBound;
2664   for (int i = 0; i < steps; i++)
2665   {
2666     zBound = GetZindexAndCheckBounds(ret.Z(), &zt);
2667     if (zBound == OnLowEdge)
2668     {
2669       // print_need_cout("AnnularFieldSIm::swimToInAnalyticSteps requests z-nudge from z=%f to %f\n", ret.Z(), ret.Z()+ALMOST_ZERO);//nudge it in z:
2670       ret.SetZ(ret.Z() + ALMOST_ZERO);
2671     }
2672     if (GetRindexAndCheckBounds(ret.Perp(), &rt) != InBounds || GetPhiIndexAndCheckBounds(FilterPhiPos(ret.Phi()), &pt) != InBounds || (zBound == OutOfBounds))
2673     {
2674       std::cout << boost::str(boost::format(
2675                                   "AnnularFieldSim::swimToInAnalyticSteps at step %d asked to swim particle from (%f,%f,%f)(cm) (rphiz)=(%fcm,%frad,%fcm)which is outside the ROI.") %
2676                               i % (ret.X() / cm) % (ret.Y() / cm) % (ret.Z() / cm) % (ret.Perp() / cm) % ret.Phi() % (ret.Z() / cm))
2677                 << std::endl;
2678       std::cout << boost::str(boost::format(" -- %f <= r < %f \t%f <= phi < %f \t%f <= z < %f ") % (rmin_roi * step.Perp() + rmin) % (rmax_roi * step.Perp() + rmin) % (phimin_roi * step.Phi()) % (phimax_roi * step.Phi()) % (zmin_roi * step.Z()) % (zmax_roi * step.Z())) << std::endl;
2679       std::cout << "Returning last valid position." << std::endl;
2680       ;
2681       if (!(goodToStep == nullptr))
2682       {
2683         *goodToStep = i - 1;
2684       }
2685       return ret * (1.0 / cm);
2686     }
2687     // print_need_cout("AnnularFieldSim::swimToInAnalyticSteps at step %d, asked to swim particle from (%f,%f,%f) (rphiz)=(%f,%f,%f).\n",i,ret.X(),ret.Y(),ret.Z(),ret.Perp(),ret.Phi(),ret.Z());
2688     // rcc note: once I put the z distoriton back in, I need to check that ret.Z+zstep is still in bounds:
2689     accumulated_distortion += GetStepDistortion(ret.Z() + zstep, ret, true, true);
2690 
2691     accumulated_drift += drift_step;
2692 
2693     // this seems redundant, but if the distortions are small they may lose precision and stop actually changing the position when step size is small.  This allows them to accumulate separately so they can grow properly:
2694     ret = start + accumulated_distortion + accumulated_drift;
2695   }
2696 
2697   return ret * (1.0 / cm);
2698 }
2699 
2700 TVector3 AnnularFieldSim::swimToInSteps(float zdest, const TVector3 &start, int steps = 1, bool interpolate = false, int *goodToStep = nullptr)
2701 {
2702   int *success = nullptr;
2703   TVector3 straightline(start.X(), start.Y(), zdest);
2704   TVector3 distortion = GetTotalDistortion(zdest, start, steps, interpolate, goodToStep, success);
2705   return straightline + distortion;
2706 }
2707 
2708 // NOLINTNEXTLINE(misc-no-recursion)
2709 TVector3 AnnularFieldSim::GetTotalDistortion(float zdest, const TVector3 &start, int steps, bool interpolate, int *goodToStep, int *success)
2710 {
2711   // work in native units is automatic.
2712   // double zdist=(zdest-start.Z())*cm;
2713   // start=start*cm;
2714 
2715   // check the z bounds:
2716   int rt, pt, zt;  // just placeholders for the bounds-checking.
2717   BoundsCase zBound;
2718   zBound = GetZindexAndCheckBounds(zdest, &zt);
2719   bool rdrswitch = RdeltaRswitch;
2720   if (success)
2721   {
2722     *success = 0;
2723   }
2724   if (zBound == OutOfBounds)
2725   {
2726     if (hasTwin)
2727     {  // check if this destination is valid for our twin, if we have one:
2728       zBound = GetZindexAndCheckBounds(-zdest, &zt);
2729       if (zBound == InBounds || zBound == OnLowEdge)
2730       {
2731         // destination is in the twin
2732         return twin->GetTotalDistortion(zdest, start, steps, interpolate, goodToStep, success);
2733       }
2734     }
2735     // otherwise, we're not in the twin, and default to our usual gripe:
2736     std::cout << boost::str(boost::format("AnnularFieldSim::GetTotalDistortion starting at (%f,%f,%f)=(r%f,p%f,z%f) asked to drift to z=%f, which is outside the ROI.  hasTwin= %d.  Returning zero_vector.") % start.X() % start.Y() % start.Z() % start.Perp() % FilterPhiPos(start.Phi()) % start.Z() % zdest % (int) hasTwin) << std::endl;
2737     std::cout << boost::str(boost::format(" -- %f <= r < %f \t%f <= phi < %f \t%f <= z < %f ") % (rmin_roi * step.Perp() + rmin) % (rmax_roi * step.Perp() + rmin) % (phimin_roi * step.Phi()) % (phimax_roi * step.Phi()) % (zmin_roi * step.Z()) % (zmax_roi * step.Z())) << std::endl;
2738     *goodToStep = 0;
2739     *success = 0;
2740     return zero_vector;  // rcchere
2741   }
2742   else if (zBound == OnLowEdge)
2743   {
2744     // nudge it in z:
2745     zdest += ALMOST_ZERO;
2746   }
2747   zBound = GetZindexAndCheckBounds(start.Z(), &zt);
2748   if (zBound == OutOfBounds)
2749   {
2750     std::cout << boost::str(boost::format("AnnularFieldSim::GetTotalDistortion starting at (%f,%f,%f)=(r%f,p%f,z%f) asked to drift from z=%f, which is outside the ROI.  Returning zero_vector.") % start.X() % start.Y() % start.Z() % start.Perp() % FilterPhiPos(start.Phi()) % start.Z() % start.Z()) << std::endl;
2751     std::cout << boost::str(boost::format(" -- %f <= r < %f \t%f <= phi < %f \t%f <= z < %f ") % (rmin_roi * step.Perp() + rmin) % (rmax_roi * step.Perp() + rmin) % (phimin_roi * step.Phi()) % (phimax_roi * step.Phi()) % (zmin_roi * step.Z()) % (zmax_roi * step.Z())) << std::endl;
2752     *goodToStep = 0;
2753     *success = 0;
2754     return zero_vector;
2755   }
2756   else if (zBound == OnLowEdge)
2757   {
2758     // nudge it in z:
2759     zdest += ALMOST_ZERO;
2760   }
2761 
2762   // now we are guaranteed the z limits are in range, and don't need to check them again.
2763 
2764   double zstep = (zmax - zmin) / steps;  // always a positive number
2765   // print_need_cout("Lets do some handy math here, the value of the first zstep: %f \n",zstep);
2766   // print_need_cout("Lets do some handy math here, the value of zmax is: %f \n",zmax);
2767   // print_need_cout("Lets do some handy math here, the value of zmin is: %f \n",zmin);
2768   // print_need_cout("Lets do some handy math here, the value of steps is: %d \n",steps);
2769   if ((zdest - start.Z()) < 0)
2770   {
2771     zstep = -zstep;  // It goes two directions, so there will be positive and negative value
2772   }
2773   int integernumbersteps = (zdest - start.Z()) / zstep;
2774   // print_need_cout("Lets do some handy math here, the value of integernumbersteps is: %d \n",integernumbersteps);
2775   // print_need_cout("Lets do some handy math here, the value of zdest is: %f \n",zdest);
2776   // print_need_cout("Lets do some handy math here, the value of start.Z() is: %f \n",start.Z());
2777   // print_need_cout("Lets do some handy math here, the value of zstep is: %f \n",zstep);
2778 
2779   // print_need_cout("Hello, this is the zmax-zmin length: %f\n", (zmax-zmin));
2780   // print_need_cout("Hello, this is the zdest-start.Z(): %f\n", (zdest-start.Z()));
2781   // print_need_cout("Hello, this is the zstep number: %f\n", zstep);
2782   // print_need_cout("Hello, this is the integernumbersteps number: %d\n", integernumbersteps);
2783 
2784   TVector3 position = start;
2785   TVector3 accumulated_distortion(0, 0, 0);
2786   TVector3 accumulated_drift(0, 0, 0);
2787   TVector3 drift_step(0, 0, zstep);
2788 
2789   // the conceptual approach here is to get the vector distortion in each z step, and use the transverse component of that to update the position of the particle for the next step, while accumulating the total distortion separately from the position.  This allows a small residual to accumulate, rather than being lost.  We do not correct the z position, so that the stepping does not 'skip' parts of the trajectory.
2790 
2791   // describe what you're doing here:
2792   // print_need_cout("Hello again, this is the zdest: %f\n", (zdest));
2793   // print_need_cout("Hello again, this is the start.Z(): %f\n", (start.Z()));
2794 
2795   accumulated_distortion += GetStepDistortion(zdest - (integernumbersteps * zstep), position, true, false);
2796 
2797   // short-circuit if there's no travel length:
2798 
2799   position.SetX(start.X() + accumulated_distortion.X());
2800   position.SetY(start.Y() + accumulated_distortion.Y());
2801   position.SetZ(zdest - (integernumbersteps * zstep));
2802 
2803   float StartR, FinalR;
2804   float DeltaR;
2805   for (int i = 0; i < integernumbersteps; i++)
2806   {
2807     if (GetRindexAndCheckBounds(position.Perp(), &rt) != InBounds || GetPhiIndexAndCheckBounds(FilterPhiPos(position.Phi()), &pt) != InBounds || (zBound == OutOfBounds))
2808     {
2809       // print_need_cout("AnnularFieldSim::GetTotalDistortion starting at (%f,%f,%f)=(r%f,p%f,z%f) with drift_step=%f, at step %d, asked to swim particle from (%f,%f,%f) (rphiz)=(%f,%f,%f)which is outside the ROI.\n", start.X(), start.Y(), start.Z(), start.Perp(), FilterPhiPos(start.Phi()), start.Z(), zstep, i, position.X(), position.Y(), position.Z(), position.Perp(), position.Phi(), position.Z());
2810       // print_need_cout(" -- %f <= r < %f \t%f <= phi < %f \t%f <= z < %f \n", rmin_roi * step.Perp() + rmin, rmax_roi * step.Perp() + rmin, phimin_roi * step.Phi(), phimax_roi * step.Phi(), zmin_roi * step.Z(), zmax_roi * step.Z());
2811       // print_need_cout("Returning last good position.\n");
2812       if (!(goodToStep == nullptr))
2813       {
2814         *goodToStep = i - 1, *success = 0;
2815       }
2816       // assert (1==2);
2817       return (accumulated_distortion);
2818     }
2819 
2820     StartR = position.Perp();
2821     // as we accumulate distortions, add these to the x and y positions, but do not change the z position, otherwise we will 'skip' parts of the drift, which is not the intended behavior.
2822     accumulated_distortion += GetStepDistortion(position.Z() + zstep, position, true, false);
2823     position.SetX(start.X() + accumulated_distortion.X());
2824     position.SetY(start.Y() + accumulated_distortion.Y());
2825     FinalR = position.Perp();
2826     // PositionXAfter=start.X() + accumulated_distortion.X();
2827     // PositionYAfter=start.Y() + accumulated_distortion.Y();//
2828 
2829     // StartR=sqrt(PositionXBefore*PositionXBefore+PositionYBefore*PositionYBefore);
2830     DeltaR = FinalR - StartR;  // sqrt(PositionXAfter*PositionXAfter+PositionYAfter*PositionYAfter)-StartR;
2831     if (rdrswitch)
2832     {
2833       hRdeltaRComponent->Fill(StartR, DeltaR);
2834     }
2835     position.SetZ(position.Z() + zstep);
2836   }
2837   if (GetRindexAndCheckBounds(position.Perp(), &rt) != InBounds || GetPhiIndexAndCheckBounds(FilterPhiPos(position.Phi()), &pt) != InBounds || (zBound == OutOfBounds))
2838   {
2839     *goodToStep = integernumbersteps - 1;
2840     *success = 0;
2841     return (accumulated_distortion);
2842   }
2843   *goodToStep = integernumbersteps;
2844   *success = 1;
2845   return accumulated_distortion;
2846 }
2847 
2848 void AnnularFieldSim::PlotFieldSlices(const std::string &filebase, const TVector3 &pos, char which)
2849 {
2850   bool mapEfield = true;
2851   if (which == 'B')
2852   {
2853     mapEfield = false;
2854   }
2855   which = mapEfield ? 'E' : 'B';
2856   std::string units;
2857   if (mapEfield)
2858   {
2859     units = "V/cm";
2860   }
2861   else
2862   {
2863     units = "T";
2864   }
2865 
2866   std::cout << boost::str(boost::format("plotting field slices for %c field, slicing at (%1.2F,%1.2f,%1.2f)...") % which % pos.Perp() % FilterPhiPos(pos.Phi()) % pos.Z()) << std::endl;
2867   std::cout << "file=" << filebase << std::endl;
2868   ;
2869   std::string plotfilename = filebase + "." + which + "field_slices.pdf";
2870   TVector3 inner = GetInnerEdge();
2871   TVector3 outer = GetOuterEdge();
2872   TVector3 steplocal = GetFieldStep();
2873 
2874   TCanvas *c;
2875 
2876   TH2F *hEfield[3][3];
2877   TH2F *hCharge[3];
2878   TH1F *hEfieldComp[3][3];
2879   std::string axis[] = {"r", "p", "z", "r", "p", "z"};
2880   float axisval[] = {(float) pos.Perp(), (float) FilterPhiPos(pos.Phi()), (float) pos.Z(), (float) pos.Perp(), (float) FilterPhiPos(pos.Phi()), (float) pos.Z()};
2881   int axn[] = {nr_roi, nphi_roi, nz_roi, nr_roi, nphi_roi, nz_roi};
2882   float axtop[] = {(float) outer.Perp(), 2 * M_PI, (float) outer.Z(), (float) outer.Perp(), 2 * M_PI, (float) outer.Z()};
2883   float axbot[] = {(float) inner.Perp(), 0, (float) inner.Z(), (float) inner.Perp(), 0, (float) inner.Z()};
2884 
2885   // if we are in charge of a twin, extend our axes:
2886   if (hasTwin)
2887   {
2888     // axtop[2]=axtop[5]=(float)(twin->GetOuterEdge().Z());
2889     axbot[2] = axbot[5] = (float) (twin->GetInnerEdge().Z());
2890   }
2891   std::cout << boost::str(boost::format("rpz bounds are %f<r%f\t %f<phi%f\t %f<z%f") % axbot[0] % axtop[0] % axbot[1] % axtop[1] % axbot[2] % axtop[2]) << std::endl;
2892   float axstep[6];
2893   for (int i = 0; i < 6; i++)
2894   {
2895     axstep[i] = (axtop[i] - axbot[i]) / (1.0 * axn[i]);
2896   }
2897   TVector3 field;
2898   TVector3 lpos;
2899 
2900   // it's a bit meta, but this loop over axes is a compact way to generate all the histogram titles.
2901   for (int ax = 0; ax < 3; ax++)
2902   {
2903     // loop over which axis slice to take
2904     hCharge[ax] = new TH2F(std::string("hCharge" + axis[ax]).c_str(),
2905                            boost::str(boost::format("Spacecharge Distribution in the %s%s plane at %s=%2.3f (C/cm^3);%s;%s") % axis[ax + 1] % axis[ax + 2] % axis[ax] % axisval[ax] % axis[ax + 1] % axis[ax + 2]).c_str(),
2906                            axn[ax + 1], axbot[ax + 1], axtop[ax + 1],
2907                            axn[ax + 2], axbot[ax + 2], axtop[ax + 2]);
2908     for (int i = 0; i < 3; i++)
2909     {
2910       // loop over which axis of the field to read
2911       hEfield[ax][i] = new TH2F(boost::str(boost::format("h%sfield%s_%s%s") % which % axis[i] % axis[ax + 1] % axis[ax + 2]).c_str(),
2912                                 boost::str(boost::format("%s component of %s Field in the %s%s plane at %s=%2.3f (%s);%s;%s") % axis[i] % which % axis[ax + 1] % axis[ax + 2] % axis[ax] % axisval[ax] % units.c_str() % axis[ax + 1] % axis[ax + 2]).c_str(),
2913                                 axn[ax + 1], axbot[ax + 1], axtop[ax + 1],
2914                                 axn[ax + 2], axbot[ax + 2], axtop[ax + 2]);
2915       hEfieldComp[ax][i] = new TH1F(boost::str(boost::format("h%sfieldComp%s_%s%s") % which % axis[i] % axis[ax + 1] % axis[ax + 2]).c_str(),
2916                                     boost::str(boost::format("Log Magnitude of %s component of %s Field in the %s%s plane at %s=%2.3f;log10(mag)") % axis[i] % which % axis[ax + 1] % axis[ax + 2] % axis[ax] % axisval[ax]).c_str(),
2917                                     200, -5, 5);
2918     }
2919   }
2920 
2921   float rpz_coord[3];
2922   for (int ax = 0; ax < 3; ax++)
2923   {  // we have three sets of 'slices'.  the R slice is a 2d plot in phi-z, etc.
2924     rpz_coord[ax] = axisval[ax] + axstep[ax] / 2;
2925     for (int i = 0; i < axn[ax + 1]; i++)
2926     {  // for each slice, loop over the bins of the 2d plot:
2927       rpz_coord[(ax + 1) % 3] = axbot[ax + 1] + (i + 0.5) * axstep[ax + 1];
2928       for (int j = 0; j < axn[ax + 2]; j++)
2929       {
2930         rpz_coord[(ax + 2) % 3] = axbot[ax + 2] + (j + 0.5) * axstep[ax + 2];
2931         lpos.SetXYZ(rpz_coord[0], 0, rpz_coord[2]);
2932         lpos.SetPhi(rpz_coord[1]);
2933         if (false && ax == 0)
2934         {
2935           std::cout << boost::str(boost::format("sampling rpz=(%f,%f,%f)=(%f,%f,%f) after conversion to xyz=(%f,%f,%f)") % rpz_coord[0] % rpz_coord[1] % rpz_coord[2] % lpos.Perp() % (FilterPhiPos(lpos.Phi())) % lpos.Z() % lpos.X() % lpos.Y() % lpos.Z()) << std::endl;
2936         }
2937         if (mapEfield)
2938         {
2939           // GetFieldAt automatically asks the twin if we are out of bounds here.
2940           field = GetFieldAt(lpos) * (1.0 * cm / V);  // get units so we're drawing in V/cm when we draw.
2941         }
2942         else
2943         {
2944           field = GetBFieldAt(lpos) * (1.0 / Tesla);  // get units so we're drawing in Tesla when we draw.
2945           // if (hasTwin && lpos.Z()<0) field=twin->GetBFieldAt(lpos)*1.0/Tesla;
2946         }
2947         field.RotateZ(-rpz_coord[1]);  // rotate us so we can read the y component as the phi component
2948         // if (field.Mag()>0) continue; //nothing has mag zero because of the drift field.
2949         hEfield[ax][0]->Fill(rpz_coord[(ax + 1) % 3], rpz_coord[(ax + 2) % 3], field.X());
2950         hEfield[ax][1]->Fill(rpz_coord[(ax + 1) % 3], rpz_coord[(ax + 2) % 3], field.Y());
2951         hEfield[ax][2]->Fill(rpz_coord[(ax + 1) % 3], rpz_coord[(ax + 2) % 3], field.Z());
2952         hCharge[ax]->Fill(rpz_coord[(ax + 1) % 3], rpz_coord[(ax + 2) % 3], GetChargeAt(lpos));
2953         hEfieldComp[ax][0]->Fill((std::abs(field.X())));
2954         hEfieldComp[ax][1]->Fill((std::abs(field.Y())));
2955         hEfieldComp[ax][2]->Fill((std::abs(field.Z())));
2956       }
2957     }
2958   }
2959 
2960   c = new TCanvas("cfieldslices", "electric field", 1200, 800);
2961   c->Divide(4, 3);
2962   gStyle->SetOptStat();
2963   for (int ax = 0; ax < 3; ax++)
2964   {
2965     for (int i = 0; i < 3; i++)
2966     {
2967       c->cd(ax * 4 + i + 1);
2968       gPad->SetRightMargin(0.15);
2969       hEfield[ax][i]->SetStats(false);
2970       hEfield[ax][i]->Draw("colz");
2971       // hEfield[ax][i]->GetListOfFunctions()->Print();
2972       gPad->Modified();
2973       // hEfieldComp[ax][i]->Draw();//"colz");
2974     }
2975     c->cd(ax * 4 + 4);
2976     gPad->SetRightMargin(0.15);
2977     hCharge[ax]->SetStats(false);
2978     hCharge[ax]->Draw("colz");
2979     // pal=dynamic_cast<TPaletteAxis*>(hCharge[ax]->GetListOfFunctions()->FindObject("palette"));
2980     if (false)
2981     {
2982       // pal->SetX1NDC(0.86);
2983       // pal->SetX2NDC(0.91);
2984       gPad->Modified();
2985     }
2986   }
2987   c->SaveAs(plotfilename.c_str());
2988   std::cout << "after plotting field slices..." << std::endl;
2989   std::cout << "file=" << filebase << std::endl;
2990 
2991   return;
2992 }
2993 
2994 void AnnularFieldSim::GenerateSeparateDistortionMaps(const std::string &filebase, int nSteps, int r_subsamples, int p_subsamples, int z_subsamples, int /*z_substeps*/, bool andCartesian)
2995 {
2996   // generates the distortion map for one or both sides of the detector, separating them so
2997   // they do not try to interpolate across the CM.
2998 
2999   // 1) pick a map spacing ('s')
3000   TVector3 steplocal(step.Perp() / r_subsamples, 0, step.Z() / z_subsamples);
3001   steplocal.SetPhi(step.Phi() / p_subsamples);
3002   float deltar = steplocal.Perp();  //(rf-ri)/nr;
3003   float deltap = steplocal.Phi();   //(pf-pi)/np;
3004   float deltaz = steplocal.Z();     //(zf-zi)/nz;
3005   bool rdrswitch = RdeltaRswitch;
3006   TVector3 stepzvec(0, 0, deltaz);
3007 
3008   //  int nSteps = 500;  //how many steps to take in the particle path.  hardcoded for now.  Think about this later.
3009 
3010   int nSides = 1;
3011   if (hasTwin)
3012   {
3013     nSides = 2;
3014   }
3015   // idea for a faster way to build a map:
3016 
3017   // 2) generate the distortions steplocal.Z() away from the readout
3018   // 3) generate the distortion from (i)*steplocal.Z() away to (i-1) away, then add the interpolated value from the (i-1) layer
3019 
3020   // for interpolation, Henry needs one extra buffer bin on each side.
3021 
3022   // so we define the histogram bounds (the 'h' suffix) to be the full range
3023   // plus an additional step in each direction so interpolation can work at the edges
3024   TVector3 lowerEdge = GetRoiCellCenter(rmin_roi, phimin_roi, zmin_roi);
3025   TVector3 upperEdge = GetRoiCellCenter(rmax_roi - 1, phimax_roi - 1, zmax_roi - 1);
3026   int nph = nphi * p_subsamples + 2;  // number of phibins in the histogram
3027   int nrh = nr * r_subsamples + 2;    // number of r bins in the histogram
3028   int nzh = nz * z_subsamples + 2;    // number of z you get the idea.
3029 
3030   float rih = lowerEdge.Perp() - 0.5 * step.Perp() - steplocal.Perp();             // lower bound of roi, minus one
3031   float rfh = upperEdge.Perp() + 0.5 * step.Perp() + steplocal.Perp();             // upper bound of roi, plus one
3032   float pih = FilterPhiPos(lowerEdge.Phi()) - 0.5 * step.Phi() - steplocal.Phi();  // can't automate this or we'll run afoul of phi looping.
3033   float pfh = FilterPhiPos(upperEdge.Phi()) + 0.5 * step.Phi() + steplocal.Phi();  // can't automate this or we'll run afoul of phi looping.
3034   float zih = lowerEdge.Z() - 0.5 * step.Z() - steplocal.Z();                      // lower bound of roi, minus one
3035   float zfh = upperEdge.Z() + 0.5 * step.Z() + steplocal.Z();                      // upper bound of roi, plus one
3036   float z_readout = upperEdge.Z() + 0.5 * step.Z();                                // readout plane.  Note we assume this is positive.
3037 
3038   std::cout << "generating distortion map..." << std::endl;
3039   std::cout << "file=" << filebase << std::endl;
3040   std::cout << boost::str(boost::format("Phi:  %d steps from %f to %f (field has %d steps)") % nph % pih % pfh % GetFieldStepsPhi()) << std::endl;
3041   std::cout << boost::str(boost::format("R:  %d steps from %f to %f (field has %d steps)") % nrh % rih % rfh % GetFieldStepsR()) << std::endl;
3042   std::cout << boost::str(boost::format("Z:  %d steps from %f to %f (field has %d steps)") % nzh % zih % zfh % GetFieldStepsZ()) << std::endl;
3043   std::string distortionFilename = filebase + ".distortion_map.hist.root";
3044   std::string summaryFilename = filebase + "distortion_summary.pdf";
3045   std::string diffSummaryFilename = filebase + "differential_summary.pdf";
3046 
3047   TFile *outf = TFile::Open(distortionFilename.c_str(), "RECREATE");
3048   outf->cd();
3049 
3050   // actual output maps:
3051 
3052   TH3F *hDistortionR = new TH3F("hDistortionR", "Per-z-bin Distortion in the R direction as a function of (phi,r,z) (centered in r,phi, z);phi;r;z", nph, pih, pfh, nrh, rih, rfh, nzh, zih, zfh);
3053   TH3F *hDistortionP = new TH3F("hDistortionP", "Per-z-bin Distortion in the RPhi direction as a function of (phi,r,z)  (centered in r,phi, z);phi;r;z", nph, pih, pfh, nrh, rih, rfh, nzh, zih, zfh);
3054   TH3F *hDistortionZ = new TH3F("hDistortionZ", "Per-z-bin Distortion in the Z direction as a function of (phi,r,z)  (centered in r,phi, z);phi;r;z", nph, pih, pfh, nrh, rih, rfh, nzh, zih, zfh);
3055   TH3F *hIntDistortionR = new TH3F("hIntDistortionR", "Integrated R Distortion from (phi,r,z) to z=0 (centered in r,phi, and z);phi;r;z", nph, pih, pfh, nrh, rih, rfh, nzh, zih, zfh);
3056   TH3F *hIntDistortionP = new TH3F("hIntDistortionP", "Integrated R Distortion from (phi,r,z) to z=0 (centered in r,phi, and z);phi;r;z", nph, pih, pfh, nrh, rih, rfh, nzh, zih, zfh);
3057   TH3F *hIntDistortionZ = new TH3F("hIntDistortionZ", "Integrated R Distortion from (phi,r,z) to z=0  (centered in r,phi, and z);phi;r;z", nph, pih, pfh, nrh, rih, rfh, nzh, zih, zfh);
3058 
3059   TH3F *hIntDistortionX = new TH3F("hIntDistortionX", "Integrated X Distortion from (phi,r,z) to z=0 (centered in r,phi, and z);phi;r;z", nph, pih, pfh, nrh, rih, rfh, nzh, zih, zfh);
3060   TH3F *hIntDistortionY = new TH3F("hIntDistortionY", "Integrated Y Distortion from (phi,r,z) to z=0 (centered in r,phi, and z);phi;r;z", nph, pih, pfh, nrh, rih, rfh, nzh, zih, zfh);
3061 
3062   const int nMapComponents = 6;
3063   TH3F *hSeparatedMapComponent[2][6];  // side, then xyzrp
3064   TH3F *hValidToStepComponent[2];
3065   TH3F *hReachesReadout[2];
3066   std::string side[2];
3067   side[0] = "soloz";
3068   if (hasTwin)
3069   {
3070     side[0] = "posz";
3071     side[1] = "negz";
3072   }
3073   std::string sepAxis[] = {"X", "Y", "Z", "R", "P", "RPhi"};
3074   float zlower, zupper;
3075   for (int i = 0; i < nSides; i++)
3076   {
3077     if (i == 0)
3078     {  // doing the positive side
3079       zlower = fmin(zih, zfh);
3080       zupper = fmax(zih, zfh);
3081     }
3082     else
3083     {
3084       zlower = -1 * fmax(zih, zfh);
3085       zupper = -1 * fmin(zih, zfh);
3086     }
3087     for (int j = 0; j < nMapComponents; j++)
3088     {
3089       hSeparatedMapComponent[i][j] = new TH3F(std::string("hIntDistortion" + sepAxis[j] + "_" + side[i]).c_str(),
3090                                               std::string("Integrated" + sepAxis[j] + " Deflection drifting from (phi,r,z) to z=endcap;phi;r;z (" + side[i] + " side)").c_str(),
3091                                               nph, pih, pfh, nrh, rih, rfh, nzh, zlower, zupper);
3092     }
3093     hValidToStepComponent[i] = new TH3F(std::string("hValidToStep_" + side[i]).c_str(),
3094                                         std::string("Step before out of bounds drifting from (phi,r,z) to z=endcap;phi;r;z (" + side[i] + " side)").c_str(),
3095                                         nph, pih, pfh, nrh, rih, rfh, nzh, zlower, zupper);
3096     hReachesReadout[i] = new TH3F(std::string("hReachesReadout_" + side[i]).c_str(),
3097                                   std::string("Bool value for checking if successfuly drifting from (phi,r,z) to z=endcap and reaches the readout;phi;r;z (" + side[i] + " side)").c_str(),
3098                                   nph, pih, pfh, nrh, rih, rfh, nzh, zlower, zupper);
3099   }
3100   if (rdrswitch)
3101   {
3102     hRdeltaRComponent = new TH2F("hRdeltaR", "Bool value of input for a 2D R_versus_deltaR plot from Zstart to Zend;R;deltaR", nrh, rih, rfh, 10 * nrh, rih - 70, rfh + 170);
3103     // hRdeltaRComponent = new TH2F("hRdeltaR","Bool value of input for a 2D R_versus_deltaR plot from Zstart to Zend;R;deltaR", nrh, rih, rfh, 10*nrh, rih-20, rfh-77);
3104     twin->hRdeltaRComponent = hRdeltaRComponent;
3105   }
3106   // monitor plots, and the position that that plot monitors at:
3107 
3108   // TVector3 pos((nrh/2+0.5)*s.Perp()+rih,0,(nzh/2+0.5)*s.Z()+zih);
3109   TVector3 pos(((int) (nrh / 2) + 0.5) * steplocal.Perp() + rih, 0, zmin + ((int) (nz / 2) + 0.5) * step.Z());
3110   float posphi = ((int) (nph / 2) + 0.5) * steplocal.Phi() + pih;
3111   pos.SetPhi(posphi);
3112   // int xi[3]={nrh/2,nph/2,nzh/2};
3113   int xi[3] = {(int) std::floor((pos.Perp() - rih) / steplocal.Perp()), (int) std::floor((posphi - pih) / steplocal.Phi()), (int) std::floor((pos.Z() - zih) / steplocal.Z())};
3114   if (!hasTwin)
3115   {
3116     std::cout << boost::str(boost::format("rpz slice indices= (%d,%d,%d) (no twin)") % xi[0] % xi[1] % xi[2]) << std::endl;
3117   }
3118   int twinz = (-pos.Z() - zih) / steplocal.Z();
3119   if (hasTwin)
3120   {
3121     std::cout << boost::str(boost::format("rpz slice indices= (%d,%d,%d) twinz=%d") % xi[0] % xi[1] % xi[2] % twinz) << std::endl;
3122   }
3123 
3124   const std::string axname[] = {"r", "p", "z", "r", "p", "z"};
3125   int axn[] = {nrh, nph, nzh, nrh, nph, nzh};
3126   float axval[] = {(float) pos.Perp(), (float) pos.Phi(), (float) pos.Z(), (float) pos.Perp(), (float) pos.Phi(), (float) pos.Z()};
3127   float axbot[] = {rih, pih, zih, rih, pih, zih};
3128   float axtop[] = {rfh, pfh, zfh, rfh, pfh, zfh};
3129   TH2F *hIntDist[3][3];
3130   TH1F *hRDist[2][3];  // now with a paired friend for the negative side
3131   TH2F *hDiffDist[3][3];
3132   TH1F *hRDiffDist[2][3];
3133   for (int i = 0; i < 3; i++)
3134   {
3135     // loop over which axis of the distortion to read
3136     for (int ax = 0; ax < 3; ax++)
3137     {
3138       // loop over which plane to work in
3139       hDiffDist[ax][i] = new TH2F(std::string("hDiffDist" + axname[i] + "_" + axname[ax + 1] + axname[ax + 2]).c_str(),
3140                                   boost::str(boost::format("%s component of diff. distortion in  %s%s plane at %s=%2.3f;%s;%s") % axname[i] % axname[ax + 1] % axname[ax + 2] % axname[ax] % axval[ax] % axname[ax + 1] % axname[ax + 2]).c_str(),
3141                                   axn[ax + 1], axbot[ax + 1], axtop[ax + 1],
3142                                   axn[ax + 2], axbot[ax + 2], axtop[ax + 2]);
3143       hIntDist[ax][i] = new TH2F(std::string("hIntDist" + axname[i] + "_" + axname[ax + 1] + axname[ax + 2]).c_str(),
3144                                  boost::str(boost::format("%s component of int. distortion in  %s%s plane at %s=%2.3f;%s;%s") % axname[i] % axname[ax + 1] % axname[ax + 2] % axname[ax] % axval[ax] % axname[ax + 1] % axname[ax + 2]).c_str(),
3145                                  axn[ax + 1], axbot[ax + 1], axtop[ax + 1],
3146                                  axn[ax + 2], axbot[ax + 2], axtop[ax + 2]);
3147     }
3148     hRDist[0][i] = new TH1F(std::string("hRDist" + axname[i]).c_str(),
3149                             boost::str(boost::format("%s component of int. distortion vs r with %s=%2.3f and %s=%2.3f;r(cm);#delta (cm)") % axname[i] % axname[1] % axval[1] % axname[2] % axval[2]).c_str(),
3150                             axn[0], axbot[0], axtop[0]);
3151     hRDist[1][i] = new TH1F(std::string("hRDist" + axname[i] + "Neg").c_str(),
3152                             boost::str(boost::format("%s component of int. distortion vs r with %s=%2.3f and %s=-%2.3f;r(cm);#delta (cm)") % axname[i] % axname[1] % axval[1] % axname[2] % axval[2]).c_str(),
3153                             axn[0], axbot[0], axtop[0]);
3154     hRDiffDist[0][i] = new TH1F(std::string("hRDiffDist" + axname[i]).c_str(),
3155                                 boost::str(boost::format("%s component of diff. distortion vs r with %s=%2.3f and %s=%2.3f;r(cm);#delta (cm)") % axname[i] % axname[1] % axval[1] % axname[2] % axval[2]).c_str(),
3156                                 axn[0], axbot[0], axtop[0]);
3157     hRDiffDist[1][i] = new TH1F(std::string("hRDiffDist" + axname[i] + "Neg").c_str(),
3158                                 boost::str(boost::format("%s component of diff. distortion vs r with %s=%2.3f and %s=-%2.3f;r(cm);#delta (cm)") % axname[i] % axname[1] % axval[1] % axname[2] % axval[2]).c_str(),
3159                                 axn[0], axbot[0], axtop[0]);
3160   }
3161 
3162   TVector3 inpart, outpart;
3163   TVector3 diffdistort, distort;
3164   int validToStep;
3165   int successCheck;
3166 
3167   // TTree version:
3168   float partR, partP, partZ;
3169   int ir, ip, iz;
3170   float distortR, distortP, distortZ;
3171   float distortX, distortY;
3172   float diffdistR, diffdistP, diffdistZ;
3173   TTree *dTree = new TTree("dTree", "Distortion per step z");
3174   dTree->Branch("r", &partR);
3175   dTree->Branch("p", &partP);
3176   dTree->Branch("z", &partZ);
3177   dTree->Branch("ir", &ir);
3178   dTree->Branch("ip", &ip);
3179   dTree->Branch("iz", &iz);
3180   dTree->Branch("dr", &distortR);
3181   dTree->Branch("drp", &distortP);
3182   dTree->Branch("dz", &distortZ);
3183 
3184   std::cout << boost::str(boost::format("generating separated distortion map with (%dx%dx%d) grid ") % nrh % nph % nzh) << std::endl;
3185   unsigned long long totalelements = nrh;
3186   totalelements *= nph;
3187   totalelements *= nzh;  // breaking up this multiplication prevents a 32bit math overflow
3188   if (hasTwin)
3189   {
3190     totalelements *= 2;  // if we have a twin, we have twice as many z bins as we thought.
3191   }
3192 
3193   unsigned long long percent = totalelements / 100;
3194   unsigned long long waypoint = percent * debug_npercent;
3195   std::cout << boost::str(boost::format("total elements = %llu") % totalelements) << std::endl;
3196 
3197   int el = 0;
3198 
3199   // we want to loop over the entire region to be mapped, but we also need to include
3200   // one additional bin at each edge, to allow the mc drift code to interpolate properly.
3201   // hence we count from -1 to n+1, and manually adjust the position in those edge cases
3202   // to avoid sampling nonphysical regions in r and z.  the phi case is free to wrap as
3203   //  normal.
3204 
3205   // note that we apply the adjustment to the particle position (inpart) and not the plotted position (partR etc)
3206   inpart.SetXYZ(1, 0, 0);
3207   for (ir = 0; ir < nrh; ir++)
3208   {
3209     partR = (ir + 0.5) * deltar + rih;
3210     if (ir == 0)
3211     {
3212       inpart.SetPerp(partR + deltar);
3213     }
3214     else if (ir == nrh - 1)
3215     {
3216       inpart.SetPerp(partR - deltar);
3217     }
3218     else
3219     {
3220       inpart.SetPerp(partR);
3221     }
3222     for (ip = 0; ip < nph; ip++)
3223     {
3224       partP = (ip + 0.5) * deltap + pih;
3225       inpart.SetPhi(partP);
3226       // since phi loops, there's no need to adjust phis that are out of bounds.
3227       for (iz = 0; iz < nzh; iz++)
3228       {
3229         partZ = (iz) *deltaz + zih;  // start us at the EDGE of the bin,
3230         if (iz == 0)
3231         {
3232           inpart.SetZ(partZ + deltaz);
3233         }
3234         else if (iz == nzh - 1)
3235         {
3236           inpart.SetZ(partZ - deltaz);
3237         }
3238         else
3239         {
3240           inpart.SetZ(partZ);
3241         }
3242         partZ += 0.5 * deltaz;  // move to center of histogram bin.
3243         for (int localside = 0; localside < nSides; localside++)
3244         {
3245           if (localside == 0)
3246           {
3247             diffdistort = zero_vector;  // GetTotalDistortion(inpart.Z() + deltaz, inpart, nSteps, true, &validToStep, &successCheck);
3248             distort = GetTotalDistortion(z_readout, inpart, nSteps, true, &validToStep, &successCheck);
3249           }
3250           else
3251           {
3252             // if we have more than one side,
3253             // flip z coords and do the twin instead:
3254             partZ *= -1;                   // position to place in histogram
3255             inpart.SetZ(-1 * inpart.Z());  // position to seek in sim
3256             diffdistort = zero_vector;     // twin->GetTotalDistortion(inpart.Z() - deltaz, inpart, nSteps, true, &validToStep, &successCheck);
3257             distort = twin->GetTotalDistortion(-z_readout, inpart, nSteps, true, &validToStep, &successCheck);
3258           }
3259 
3260           diffdistort.RotateZ(-inpart.Phi());  // rotate so that distortion components are wrt the x axis
3261           diffdistP = diffdistort.Y();         // the phi component is now the y component.
3262           diffdistR = diffdistort.X();         // and the r component is the x component
3263           diffdistZ = diffdistort.Z();
3264 
3265           distortX = distort.X();
3266           distortY = distort.Y();
3267           distort.RotateZ(-inpart.Phi());  // rotate so that distortion components are wrt the x axis
3268           distortP = distort.Y();          // the phi component is now the y component.
3269           distortR = distort.X();          // and the r component is the x component
3270           distortZ = distort.Z();
3271 
3272           float distComp[nMapComponents];  // by components
3273           distComp[0] = distortX;
3274           distComp[1] = distortY;
3275           distComp[2] = distortZ;
3276           distComp[3] = distortR;
3277           distComp[4] = distortP / partR;  // 'P' now refers to phi in radians, rather than unitful
3278           distComp[5] = distortP;          // 'RPhi' is the one that correponds to the []meter unitful phi-hat value
3279 
3280           for (int c = 0; c < nMapComponents; c++)
3281           {
3282             hSeparatedMapComponent[localside][c]->Fill(partP, partR, partZ, distComp[c]);
3283           }
3284 
3285           hValidToStepComponent[localside]->Fill(partP, partR, partZ, validToStep);
3286           hReachesReadout[localside]->Fill(partP, partR, partZ, successCheck);
3287 
3288           // recursive integral distortion:
3289           // get others working first!
3290 
3291           // print_need_cout("part=(rpz)(%f,%f,%f),distortP=%f\n",partP,partR,partZ,distortP);
3292           hIntDistortionR->Fill(partP, partR, partZ, distortR);
3293           hIntDistortionP->Fill(partP, partR, partZ, distortP);
3294           hIntDistortionZ->Fill(partP, partR, partZ, distortZ);
3295 
3296           if (andCartesian)
3297           {
3298             hIntDistortionX->Fill(partP, partR, partZ, distortX);
3299             hIntDistortionY->Fill(partP, partR, partZ, distortY);
3300           }
3301 
3302           // now we fill particular slices for integral visualizations:
3303           if (ir == xi[0] && localside == 0)
3304           {  // r slice
3305             // print_need_cout("ir=%d, r=%f (pz)=(%d,%d), distortR=%2.2f, distortP=%2.2f\n",ir,partR,ip,iz,distortR,distortP);
3306             hIntDist[0][0]->Fill(partP, partZ, distortR);
3307             hIntDist[0][1]->Fill(partP, partZ, distortP);
3308             hIntDist[0][2]->Fill(partP, partZ, distortZ);
3309             hDiffDist[0][0]->Fill(partP, partZ, diffdistR);
3310             hDiffDist[0][1]->Fill(partP, partZ, diffdistP);
3311             hDiffDist[0][2]->Fill(partP, partZ, diffdistZ);
3312           }
3313           if (ip == xi[1] && localside == 0)
3314           {  // phi slice
3315             // print_need_cout("ip=%d, p=%f (rz)=(%d,%d), distortR=%2.2f, distortP=%2.2f\n",ip,partP,ir,iz,distortR,distortP);
3316             hIntDist[1][0]->Fill(partZ, partR, distortR);
3317             hIntDist[1][1]->Fill(partZ, partR, distortP);
3318             hIntDist[1][2]->Fill(partZ, partR, distortZ);
3319             hDiffDist[1][0]->Fill(partZ, partR, diffdistR);
3320             hDiffDist[1][1]->Fill(partZ, partR, diffdistP);
3321             hDiffDist[1][2]->Fill(partZ, partR, diffdistZ);
3322 
3323             if (iz == xi[2] && localside == 0)
3324             {  // z slices of phi slices= r line at mid phi, mid z:
3325               hRDist[0][0]->Fill(partR, distortR);
3326               hRDist[0][1]->Fill(partR, distortP);
3327               hRDist[0][2]->Fill(partR, distortZ);
3328               hRDiffDist[0][0]->Fill(partR, diffdistR);
3329               hRDiffDist[0][1]->Fill(partR, diffdistP);
3330               hRDiffDist[0][2]->Fill(partR, diffdistZ);
3331             }
3332             if (hasTwin && iz == twinz && localside == 1)
3333             {  // z slices of phi slices= r line at mid phi, mid z:
3334               hRDist[1][0]->Fill(partR, distortR);
3335               hRDist[1][1]->Fill(partR, distortP);
3336               hRDist[1][2]->Fill(partR, distortZ);
3337               hRDiffDist[1][0]->Fill(partR, diffdistR);
3338               hRDiffDist[1][1]->Fill(partR, diffdistP);
3339               hRDiffDist[1][2]->Fill(partR, diffdistZ);
3340             }
3341           }
3342           if (iz == xi[2] && localside == 0)
3343           {  // z slice
3344             // print_need_cout("iz=%d, z=%f (rp)=(%d,%d), distortR=%2.2f, distortP=%2.2f\n",iz,partZ,ir,ip,distortR,distortP);
3345 
3346             hIntDist[2][0]->Fill(partR, partP, distortR);
3347             hIntDist[2][1]->Fill(partR, partP, distortP);
3348             hIntDist[2][2]->Fill(partR, partP, distortZ);
3349             hDiffDist[2][0]->Fill(partR, partP, diffdistR);
3350             hDiffDist[2][1]->Fill(partR, partP, diffdistP);
3351             hDiffDist[2][2]->Fill(partR, partP, diffdistZ);
3352           }
3353 
3354           if (!(el % waypoint))
3355           {
3356             std::cout << boost::str(boost::format("generating distortions %d%%:  ") % ((int) (el / percent)));
3357             std::cout << boost::str(boost::format("distortion at (ir=%d,ip=%d,iz=%d) is (%E,%E,%E)") % ir % ip % iz % distortR % distortP % distortZ) << std::endl;
3358           }
3359           el++;
3360         }
3361       }
3362     }
3363   }
3364   std::cout << "Completed distortion generation.  Saving outputs..." << std::endl;
3365 
3366   TCanvas *canvas = new TCanvas("cdistort", "distortion integrals", 1200, 800);
3367   // take 10 of the bottom of this for data?
3368   std::cout << "was able to make a tcanvas" << std::endl;
3369   canvas->cd();
3370   TPad *c = new TPad("cplots", "distortion integral plots", 0, 0.2, 1, 1);
3371   canvas->cd();
3372   TPad *textpad = new TPad("ctext", "distortion integral plots", 0, 0.0, 1, 0.2);
3373   std::cout << "was able to make some tpads" << std::endl;
3374 
3375   c->Divide(4, 3);
3376   gStyle->SetOptStat();
3377   std::cout << "was able to interact with gStyle" << std::endl;
3378 
3379   for (int i = 0; i < 3; i++)
3380   {
3381     // component
3382     for (int ax = 0; ax < 3; ax++)
3383     {
3384       std::cout << boost::str(boost::format("looping over components i=%d ax=%d") % i % ax) << std::endl;
3385 
3386       // plane
3387       c->cd(i * 4 + ax + 1);
3388       gPad->SetRightMargin(0.15);
3389       hIntDist[ax][i]->SetStats(false);
3390       hIntDist[ax][i]->Draw("colz");
3391     }
3392 
3393     std::cout << "drawing R profile " << i << std::endl;
3394 
3395     c->cd(i * 4 + 4);
3396     hRDist[0][i]->SetStats(false);
3397     hRDist[0][i]->SetFillColor(kRed);
3398     hRDist[0][i]->Draw("hist");
3399     if (hasTwin)
3400     {
3401       std::cout << "drawing R profile twin " << i << std::endl;
3402       hRDist[1][i]->SetStats(false);
3403       hRDist[1][i]->SetLineColor(kBlue);
3404       hRDist[1][i]->Draw("hist,same");
3405     }
3406   }
3407   std::cout << "switching to textpad" << std::endl;
3408 
3409   textpad->cd();
3410   float texpos = 0.9;
3411   float texshift = 0.12;
3412   TLatex *tex = new TLatex(0.0, texpos, "Fill Me In");
3413   std::cout << "built TLatex" << std::endl;
3414 
3415   tex->SetTextSize(texshift * 0.8);
3416   tex->DrawLatex(0.05, texpos, GetFieldString().c_str());
3417   texpos -= texshift;
3418   tex->DrawLatex(0.05, texpos, GetChargeString().c_str());
3419   texpos -= texshift;
3420   // tex->DrawLatex(0.05,texpos,Form("Drift Field = %2.2f V/cm",GetNominalE()));texpos-=texshift;
3421   tex->DrawLatex(0.05, texpos, boost::str(boost::format("Drifting grid of (rp)=(%d x %d) electrons with %d steps") % nrh % nph % nSteps).c_str());
3422   texpos -= texshift;
3423   tex->DrawLatex(0.05, texpos, GetLookupString().c_str());
3424   texpos -= texshift;
3425   tex->DrawLatex(0.05, texpos, GetGasString().c_str());
3426   texpos -= texshift;
3427   if (debug_distortionScale.Mag() > 0)
3428   {
3429     tex->DrawLatex(0.05, texpos, boost::str(boost::format("Distortion scaled by (r,p,z)=(%2.2f,%2.2f,%2.2f)") % debug_distortionScale.X() % debug_distortionScale.Y() % debug_distortionScale.Z()).c_str());
3430     texpos -= texshift;
3431   }
3432   texpos = 0.9;
3433   std::cout << "cd'ing to canvas:" << std::endl;
3434 
3435   canvas->cd();
3436   std::cout << "draw1" << std::endl;
3437 
3438   c->Draw();
3439   canvas->cd();
3440   std::cout << "draw2" << std::endl;
3441   textpad->Draw();
3442   std::cout << "was able to complete drawing on both pads" << std::endl;
3443 
3444   canvas->SaveAs(summaryFilename.c_str());
3445 
3446   // canvas->cd();
3447   // already done TPad *c=new TPad("cplots","distortion differential plots",0,0.2,1,1);
3448   // canvas->cd();
3449   // already done TPad *textpad=new TPad("ctext","distortion differential plots",0,0.0,1,0.2);
3450   // already done c->Divide(4,3);
3451   // gStyle->SetOptStat();
3452   for (int i = 0; i < 3; i++)
3453   {
3454     // component
3455     for (int ax = 0; ax < 3; ax++)
3456     {
3457       // plane
3458       c->cd(i * 4 + ax + 1);
3459       gPad->SetRightMargin(0.15);
3460       hDiffDist[ax][i]->SetStats(false);
3461       hDiffDist[ax][i]->Draw("colz");
3462     }
3463     c->cd(i * 4 + 4);
3464     hRDiffDist[0][i]->SetStats(false);
3465     hRDiffDist[0][i]->SetFillColor(kRed);
3466     hRDiffDist[0][i]->Draw("hist");
3467     if (hasTwin)
3468     {
3469       hRDiffDist[1][i]->SetStats(false);
3470       hRDiffDist[1][i]->SetLineColor(kBlue);
3471       hRDiffDist[1][i]->Draw("hist same");
3472     }
3473   }
3474   textpad->cd();
3475   texpos = 0.9;
3476   texshift = 0.12;
3477   tex->SetTextSize(texshift * 0.8);
3478   tex->DrawLatex(0.05, texpos, GetFieldString().c_str());
3479   texpos -= texshift;
3480   tex->DrawLatex(0.05, texpos, GetChargeString().c_str());
3481   texpos -= texshift;
3482   // tex->DrawLatex(0.05,texpos,Form("Drift Field = %2.2f V/cm",GetNominalE()));texpos-=texshift;
3483   tex->DrawLatex(0.05, texpos, boost::str(boost::format("Drifting grid of (rp)=(%d x %d) electrons with %d steps") % nrh % nph % nSteps).c_str());
3484   texpos -= texshift;
3485   tex->DrawLatex(0.05, texpos, GetLookupString().c_str());
3486   texpos -= texshift;
3487   tex->DrawLatex(0.05, texpos, GetGasString().c_str());
3488   texpos -= texshift;
3489   tex->DrawLatex(0.05, texpos, "Differential Plots");
3490   texpos -= texshift;
3491   if (debug_distortionScale.Mag() > 0)
3492   {
3493     tex->DrawLatex(0.05, texpos, boost::str(boost::format("Distortion scaled by (r,p,z)=(%2.2f,%2.2f,%2.2f)") % debug_distortionScale.X() % debug_distortionScale.Y() % debug_distortionScale.Z()).c_str());
3494     texpos -= texshift;
3495   }
3496   texpos = 0.9;
3497 
3498   canvas->cd();
3499   c->Draw();
3500   canvas->cd();
3501   textpad->Draw();
3502   canvas->SaveAs(diffSummaryFilename.c_str());
3503 
3504   std::cout << "saving map histograms to:" << distortionFilename << std::endl;
3505 
3506   outf->cd();
3507   for (int i = 0; i < nSides; i++)
3508   {
3509     std::cout << "Saving side " << side[i] << std::endl;
3510     for (int j = 0; j < nMapComponents; j++)
3511     {
3512       hSeparatedMapComponent[i][j]->GetSumw2()->Set(0);
3513       hSeparatedMapComponent[i][j]->Write();
3514     }
3515     hValidToStepComponent[i]->GetSumw2()->Set(0);
3516     hValidToStepComponent[i]->Write();
3517 
3518     hReachesReadout[i]->GetSumw2()->Set(0);
3519     hReachesReadout[i]->Write();
3520   }
3521   if (rdrswitch)
3522   {
3523     hRdeltaRComponent->GetSumw2()->Set(0);
3524     hRdeltaRComponent->Write();
3525   }
3526 
3527   hDistortionR->GetSumw2()->Set(0);
3528   hDistortionP->GetSumw2()->Set(0);
3529   hDistortionZ->GetSumw2()->Set(0);
3530   hIntDistortionR->GetSumw2()->Set(0);
3531   hIntDistortionP->GetSumw2()->Set(0);
3532   hIntDistortionZ->GetSumw2()->Set(0);
3533   if (andCartesian)
3534   {
3535     hIntDistortionX->GetSumw2()->Set(0);
3536     hIntDistortionY->GetSumw2()->Set(0);
3537   }
3538   hDistortionR->Write();
3539   hDistortionP->Write();
3540   hDistortionZ->Write();
3541   hIntDistortionR->Write();
3542   hIntDistortionP->Write();
3543   hIntDistortionZ->Write();
3544   if (false && andCartesian)
3545   {
3546     hIntDistortionX->Write();
3547     hIntDistortionY->Write();
3548   }
3549   std::cout << "finished writing histograms" << std::endl;
3550   // dTree->Write();
3551   //  print_need_cout("wrote dTree\n");
3552   outf->Close();
3553   // print_need_cout("map:%s.closed\n",distortionFilename.Data());
3554 
3555   std::cout << "wrote separated map and summary to " << filebase << std::endl;
3556   std::cout << "map: " << distortionFilename << std::endl;
3557   std::cout << "summary: " << summaryFilename << std::endl;
3558   // print_need_cout("wrote map and summary to %s and %s.\n",distortionFilename.Data(),summaryFilename.Data());
3559   return;
3560 }
3561 
3562 void AnnularFieldSim::GenerateDistortionMaps(const std::string &filebase, int r_subsamples, int p_subsamples, int z_subsamples, int /*z_substeps*/, bool andCartesian)
3563 {
3564   // generates the distortion map for the full detector instead of one map per side.
3565   // This produces wrong behavior when interpolating across the CM and should not be used
3566   // unless you're doing some debugging/backward compatibility checking.  Eventually this should be removed  (said in early 2022)
3567   std::cout << "WARNING:  You called the version of the distortion generator that generates a unified map.  Are you sure you meant to do this?" << std::endl;
3568 
3569   // 1) pick a map spacing ('s')
3570   bool makeUnifiedMap = true;  // if true, generate a single map across the whole TPC.  if false, save two maps, one for each side.
3571 
3572   TVector3 steplocal(step.Perp() / r_subsamples, 0, step.Z() / z_subsamples);
3573   steplocal.SetPhi(step.Phi() / p_subsamples);
3574   float deltar = steplocal.Perp();  //(rf-ri)/nr;
3575   float deltap = steplocal.Phi();   //(pf-pi)/np;
3576   float deltaz = steplocal.Z();     //(zf-zi)/nz;
3577   TVector3 stepzvec(0, 0, deltaz);
3578   int nSteps = 500;  // how many steps to take in the particle path.  hardcoded for now.  Think about this later.
3579 
3580   // idea for a faster way to build a map:
3581 
3582   // 2) generate the distortions steplocal.Z() away from the readout
3583   // 3) generate the distortion from (i)*steplocal.Z() away to (i-1) away, then add the interpolated value from the (i-1) layer
3584 
3585   // for interpolation, Henry needs one extra buffer bin on each side.
3586 
3587   // so we define the histogram bounds (the 'h' suffix) to be the full range
3588   // plus an additional step in each direction so interpolation can work at the edges
3589   TVector3 lowerEdge = GetRoiCellCenter(rmin_roi, phimin_roi, zmin_roi);
3590   TVector3 upperEdge = GetRoiCellCenter(rmax_roi - 1, phimax_roi - 1, zmax_roi - 1);
3591   int nph = nphi * p_subsamples + 2;  // nuber of phibins in the histogram
3592   int nrh = nr * r_subsamples + 2;    // number of r bins in the histogram
3593   int nzh = nz * z_subsamples + 2;    // number of z you get the idea.
3594   int successCheck;
3595 
3596   if (hasTwin && makeUnifiedMap)
3597   {  // double the z range if we have a twin.  r and phi are the same, unless we had a phi roi...
3598     lowerEdge.SetZ(-1 * upperEdge.Z());
3599     nzh += nz * z_subsamples;
3600   }
3601 
3602   float rih = lowerEdge.Perp() - 0.5 * step.Perp() - steplocal.Perp();             // lower bound of roi, minus one
3603   float rfh = upperEdge.Perp() + 0.5 * step.Perp() + steplocal.Perp();             // upper bound of roi, plus one
3604   float pih = FilterPhiPos(lowerEdge.Phi()) - 0.5 * step.Phi() - steplocal.Phi();  // can't automate this or we'll run afoul of phi looping.
3605   float pfh = FilterPhiPos(upperEdge.Phi()) + 0.5 * step.Phi() + steplocal.Phi();  // can't automate this or we'll run afoul of phi looping.
3606   float zih = lowerEdge.Z() - 0.5 * step.Z() - steplocal.Z();                      // lower bound of roi, minus one
3607   float zfh = upperEdge.Z() + 0.5 * step.Z() + steplocal.Z();                      // upper bound of roi, plus one
3608   float z_readout = upperEdge.Z() + 0.5 * step.Z();                                // readout plane.  Note we assume this is positive.
3609 
3610   std::cout << "generating distortion map..." << std::endl;
3611   std::cout << "file=" << filebase << std::endl;
3612   std::cout << boost::str(boost::format("Phi:  %d steps from %f to %f (field has %d steps)") % nph % pih % pfh % GetFieldStepsPhi()) << std::endl;
3613   std::cout << boost::str(boost::format("R:  %d steps from %f to %f (field has %d steps)") % nrh % rih % rfh % GetFieldStepsR()) << std::endl;
3614   std::cout << boost::str(boost::format("Z:  %d steps from %f to %f (field has %d steps)") % nzh % zih % zfh % GetFieldStepsZ()) << std::endl;
3615   std::string distortionFilename = filebase + ".distortion_summary.pdf";
3616   std::string summaryFilename = filebase + ".distortion_summary.pdf";
3617   std::string diffSummaryFilename = filebase + ".differential_summary.pdf";
3618 
3619   TFile *outf = TFile::Open(distortionFilename.c_str(), "RECREATE");
3620   outf->cd();
3621 
3622   // actual output maps:
3623 
3624   TH3F *hDistortionR = new TH3F("hDistortionR", "Per-z-bin Distortion in the R direction as a function of (phi,r,z) (centered in r,phi, z);phi;r;z", nph, pih, pfh, nrh, rih, rfh, nzh, zih, zfh);
3625   TH3F *hDistortionP = new TH3F("hDistortionP", "Per-z-bin Distortion in the RPhi direction as a function of (phi,r,z)  (centered in r,phi, z);phi;r;z", nph, pih, pfh, nrh, rih, rfh, nzh, zih, zfh);
3626   TH3F *hDistortionZ = new TH3F("hDistortionZ", "Per-z-bin Distortion in the Z direction as a function of (phi,r,z)  (centered in r,phi, z);phi;r;z", nph, pih, pfh, nrh, rih, rfh, nzh, zih, zfh);
3627   TH3F *hIntDistortionR = new TH3F("hIntDistortionR", "Integrated R Distortion from (phi,r,z) to z=0 (centered in r,phi, and z);phi;r;z", nph, pih, pfh, nrh, rih, rfh, nzh, zih, zfh);
3628   TH3F *hIntDistortionP = new TH3F("hIntDistortionP", "Integrated R Distortion from (phi,r,z) to z=0 (centered in r,phi, and z);phi;r;z", nph, pih, pfh, nrh, rih, rfh, nzh, zih, zfh);
3629   TH3F *hIntDistortionZ = new TH3F("hIntDistortionZ", "Integrated R Distortion from (phi,r,z) to z=0  (centered in r,phi, and z);phi;r;z", nph, pih, pfh, nrh, rih, rfh, nzh, zih, zfh);
3630 
3631   TH3F *hIntDistortionX = new TH3F("hIntDistortionX", "Integrated X Distortion from (phi,r,z) to z=0 (centered in r,phi, and z);phi;r;z", nph, pih, pfh, nrh, rih, rfh, nzh, zih, zfh);
3632   TH3F *hIntDistortionY = new TH3F("hIntDistortionY", "Integrated Y Distortion from (phi,r,z) to z=0 (centered in r,phi, and z);phi;r;z", nph, pih, pfh, nrh, rih, rfh, nzh, zih, zfh);
3633 
3634   /*
3635   TH3F* hNewIntDistortionR=new TH3F("hNewIntDistortionR","Recursively Integrated R Distortion from (r,phi,z) to z=0 (centered in r,phi, and z);phi;r;z",nph,pih,pfh,nrh,rih,rfh,nzh,zih,zfh);
3636   TH3F* hNewIntDistortionP=new TH3F("hNewIntDistortionP","Recursively Integrated R Distortion from (r,phi,z) to z=0 (centered in r,phi, and z);phi;r;z",nph,pih,pfh,nrh,rih,rfh,nzh,zih,zfh);
3637   TH3F* hNewIntDistortionZ=new TH3F("hNewIntDistortionZ","Recursively Integrated R Distortion from (r,phi,z) to z=0  (centered in r,phi, and z);phi;r;z",nph,pih,pfh,nrh,rih,rfh,nzh,zih,zfh);
3638 //for the interchanging distortion maps.
3639     TH2F* hNewIntDistortionR=new TH3F("hNewIntDistortionR","Recursively Integrated R Distortion from (r,phi,z) to z=0 (centered in r,phi, and z);phi;r;z",nph,pih,pfh,nrh,rih,rfh,nzh,zih,zfh);
3640   TH3F* hNewIntDistortionP=new TH3F("hNewIntDistortionP","Recursively Integrated R Distortion from (r,phi,z) to z=0 (centered in r,phi, and z);phi;r;z",nph,pih,pfh,nrh,rih,rfh,nzh,zih,zfh);
3641   TH3F* hNewIntDistortionZ=new TH3F("hNewIntDistortionZ","Recursively Integrated R Distortion from (r,phi,z) to z=0  (centered in r,phi, and z);phi;r;z",nph,pih,pfh,nrh,rih,rfh,nzh,zih,zfh);
3642   */
3643 
3644   // monitor plots, and the position that plot monitors at:
3645 
3646   // TVector3 pos((nrh/2+0.5)*steplocal.Perp()+rih,0,(nzh/2+0.5)*steplocal.Z()+zih);
3647   TVector3 pos(((int) (nrh / 2) + 0.5) * steplocal.Perp() + rih, 0, zmin + ((int) (nz / 2) + 0.5) * step.Z());
3648   float posphi = ((int) (nph / 2) + 0.5) * steplocal.Phi() + pih;
3649   pos.SetPhi(posphi);
3650   // int xi[3]={nrh/2,nph/2,nzh/2};
3651   int xi[3] = {(int) std::floor((pos.Perp() - rih) / steplocal.Perp()), (int) std::floor((posphi - pih) / steplocal.Phi()), (int) std::floor((pos.Z() - zih) / steplocal.Z())};
3652   if (!hasTwin)
3653   {
3654     std::cout << boost::str(boost::format("rpz slice indices= (%d,%d,%d) (no twin)") % xi[0] % xi[1] % xi[2]) << std::endl;
3655   }
3656   int twinz = 0;  // this is meant to be the matching position to xi[2] in the twin, hence generally -1*pos.  Better to just ask the twin rather than trying to calculate it ourselves...
3657   if (hasTwin)
3658   {
3659     twinz = twin->GetZindex(-1 * pos.Z());
3660     std::cout << boost::str(boost::format("rpz slice indices= (%d,%d,%d) twinz=%d") % xi[0] % xi[1] % xi[2] % twinz) << std::endl;
3661   }
3662 
3663   const std::string axname[] = {"r", "p", "z", "r", "p", "z"};
3664   int axn[] = {nrh, nph, nzh, nrh, nph, nzh};
3665   float axval[] = {(float) pos.Perp(), (float) pos.Phi(), (float) pos.Z(), (float) pos.Perp(), (float) pos.Phi(), (float) pos.Z()};
3666   float axbot[] = {rih, pih, zih, rih, pih, zih};
3667   float axtop[] = {rfh, pfh, zfh, rfh, pfh, zfh};
3668   TH2F *hIntDist[3][3];
3669   TH1F *hRDist[2][3];  // now with a paired friend for the negative side
3670   TH2F *hDiffDist[3][3];
3671   TH1F *hRDiffDist[2][3];
3672   for (int i = 0; i < 3; i++)
3673   {
3674     // loop over which axis of the distortion to read
3675     for (int ax = 0; ax < 3; ax++)
3676     {
3677       // loop over which plane to work in
3678       hDiffDist[ax][i] = new TH2F(std::string("hDiffDist" + axname[i] + "_" + axname[ax + 1] + axname[ax + 2]).c_str(),
3679                                   boost::str(boost::format("%s component of diff. distortion in  %s%s plane at %s=%2.3f;%s;%s") % axname[i] % axname[ax + 1] % axname[ax + 2] % axname[ax] % axval[ax] % axname[ax + 1] % axname[ax + 2]).c_str(),
3680                                   axn[ax + 1], axbot[ax + 1], axtop[ax + 1],
3681                                   axn[ax + 2], axbot[ax + 2], axtop[ax + 2]);
3682       hIntDist[ax][i] = new TH2F(std::string("hDIntDist" + axname[i] + "_" + axname[ax + 1] + axname[ax + 2]).c_str(),
3683                                  boost::str(boost::format("%s component of int. distortion in  %s%s plane at %s=%2.3f;%s;%s") % axname[i] % axname[ax + 1] % axname[ax + 2] % axname[ax] % axval[ax] % axname[ax + 1] % axname[ax + 2]).c_str(),
3684                                  axn[ax + 1], axbot[ax + 1], axtop[ax + 1],
3685                                  axn[ax + 2], axbot[ax + 2], axtop[ax + 2]);
3686     }
3687     hRDist[0][i] = new TH1F(std::string("hRDist" + axname[i]).c_str(),
3688                             boost::str(boost::format("%s component of int. distortion vs r with %s=%2.3f and %s=%2.3f;r(cm);#delta (cm)") % axname[i] % axname[1] % axval[1] % axname[2] % axval[2]).c_str(),
3689                             axn[0], axbot[0], axtop[0]);
3690     hRDist[1][i] = new TH1F(std::string("hRDist" + axname[i] + "Neg").c_str(),
3691                             boost::str(boost::format("%s component of int. distortion vs r with %s=%2.3f and %s=-%2.3f;r(cm);#delta (cm)") % axname[i] % axname[1] % axval[1] % axname[2] % axval[2]).c_str(),
3692                             axn[0], axbot[0], axtop[0]);
3693     hRDiffDist[0][i] = new TH1F(std::string("hRDist" + axname[i]).c_str(),
3694                                 boost::str(boost::format("%s component of diff. distortion vs r with %s=%2.3f and %s=%2.3f;r(cm);#delta (cm)") % axname[i] % axname[1] % axval[1] % axname[2] % axval[2]).c_str(),
3695                                 axn[0], axbot[0], axtop[0]);
3696     hRDiffDist[1][i] = new TH1F(std::string("hRDist" + axname[i] + "Neg").c_str(),
3697                                 boost::str(boost::format("%s component of diff. distortion vs r with %s=%2.3f and %s=-%2.3f;r(cm);#delta (cm)") % axname[i] % axname[1] % axval[1] % axname[2] % axval[2]).c_str(),
3698                                 axn[0], axbot[0], axtop[0]);
3699   }
3700 
3701   TVector3 inpart, outpart;
3702   TVector3 distort;
3703   int validToStep;
3704 
3705   // TTree version:
3706   float partR, partP, partZ;
3707   int ir, ip, iz;
3708   float distortR, distortP, distortZ;
3709   float distortX, distortY;
3710   float diffdistR, diffdistP, diffdistZ;
3711   TTree *dTree = new TTree("dTree", "Distortion per step z");
3712   dTree->Branch("r", &partR);
3713   dTree->Branch("p", &partP);
3714   dTree->Branch("z", &partZ);
3715   dTree->Branch("ir", &ir);
3716   dTree->Branch("ip", &ip);
3717   dTree->Branch("iz", &iz);
3718   dTree->Branch("dr", &distortR);
3719   dTree->Branch("drp", &distortP);
3720   dTree->Branch("dz", &distortZ);
3721 
3722   std::cout << "generating distortion map with (" << nrh << "x" << nph << "x" << nzh << " grid" << std::endl;
3723   unsigned long long totalelements = nrh;
3724   totalelements *= nph;
3725   totalelements *= nzh;  // breaking up this multiplication prevents a 32bit math overflow
3726   unsigned long long percent = totalelements / 100 * debug_npercent;
3727   std::cout << boost::str(boost::format("total elements = %llu") % totalelements) << std::endl;
3728 
3729   int el = 0;
3730 
3731   // we want to loop over the entire region to be mapped, but we also need to include
3732   // one additional bin at each edge, to allow the mc drift code to interpolate properly.
3733   // hence we count from -1 to n+1, and manually adjust the position in those edge cases
3734   // to avoid sampling nonphysical regions in r and z.  the phi case is free to wrap as
3735   //  normal.
3736 
3737   // note that we apply the adjustment to the particle position (inpart) and not the plotted position (partR etc)
3738   inpart.SetXYZ(1, 0, 0);
3739   for (ir = 0; ir < nrh; ir++)
3740   {
3741     partR = (ir + 0.5) * deltar + rih;
3742     if (ir == 0)
3743     {
3744       inpart.SetPerp(partR + deltar);
3745     }
3746     else if (ir == nrh - 1)
3747     {
3748       inpart.SetPerp(partR - deltar);
3749     }
3750     else
3751     {
3752       inpart.SetPerp(partR);
3753     }
3754     for (ip = 0; ip < nph; ip++)
3755     {
3756       partP = (ip + 0.5) * deltap + pih;
3757       inpart.SetPhi(partP);
3758       // since phi loops, there's no need to adjust phis that are out of bounds.
3759       for (iz = 0; iz < nzh; iz++)
3760       {
3761         partZ = (iz) *deltaz + zih;  // start us at the EDGE of the bin, maybe has problems at the CM when twinned.
3762         if (iz == 0)
3763         {
3764           inpart.SetZ(partZ + deltaz);
3765         }
3766         else if (iz == nzh - 1)
3767         {
3768           inpart.SetZ(partZ - deltaz);
3769         }
3770         else
3771         {
3772           inpart.SetZ(partZ);
3773         }
3774         partZ += 0.5 * deltaz;  // move to center of histogram bin.
3775 
3776         // print_need_cout("iz=%d, zcoord=%2.2f, bin=%d\n",iz,partZ,  hIntDist[0][0]->GetYaxis()->FindBin(partZ));
3777 
3778         // differential distortion:
3779         // be careful with the math of a distortion.  The R distortion is NOT the perp() component of outpart-inpart -- that's the transverse magnitude of the distortion!
3780         if (hasTwin && inpart.Z() < 0)
3781         {
3782           distort = twin->GetTotalDistortion(inpart.Z(), inpart + stepzvec, nSteps, true, &validToStep, &successCheck);  // step across the cell in the opposite direction, starting at the high side and going to the low side..
3783         }
3784         else
3785         {
3786           distort = GetTotalDistortion(inpart.Z() + deltaz, inpart, nSteps, true, &validToStep, &successCheck);
3787         }
3788         distort.RotateZ(-inpart.Phi());  // rotate so that that is on the x axis
3789         diffdistP = distort.Y();         // the phi component is now the y component.
3790         diffdistR = distort.X();         // and the r component is the x component
3791         diffdistZ = distort.Z();
3792         hDistortionR->Fill(partP, partR, partZ, diffdistR);
3793         hDistortionP->Fill(partP, partR, partZ, diffdistP);
3794         hDistortionZ->Fill(partP, partR, partZ, diffdistZ);
3795         dTree->Fill();
3796 
3797         // integral distortion:
3798         if (hasTwin && makeUnifiedMap && inpart.Z() < 0)
3799         {
3800           distort = twin->GetTotalDistortion(-z_readout, inpart + stepzvec, nSteps, true, &validToStep, &successCheck);
3801         }
3802         else
3803         {
3804           distort = GetTotalDistortion(z_readout, inpart, nSteps, true, &validToStep, &successCheck);
3805         }
3806         distortX = distort.X();
3807         distortY = distort.Y();
3808         distort.RotateZ(-inpart.Phi());  // rotate so that that is on the x axis
3809         distortP = distort.Y();          // the phi component is now the y component.
3810         distortR = distort.X();          // and the r component is the x component
3811         distortZ = distort.Z();
3812 
3813         // recursive integral distortion:
3814         // get others working first!
3815 
3816         // print_need_cout("part=(rpz)(%f,%f,%f),distortP=%f\n",partP,partR,partZ,distortP);
3817         hIntDistortionR->Fill(partP, partR, partZ, distortR);
3818         hIntDistortionP->Fill(partP, partR, partZ, distortP);
3819         hIntDistortionZ->Fill(partP, partR, partZ, distortZ);
3820 
3821         if (andCartesian)
3822         {
3823           hIntDistortionX->Fill(partP, partR, partZ, distortX);
3824           hIntDistortionY->Fill(partP, partR, partZ, distortY);
3825         }
3826 
3827         // now we fill particular slices for integral visualizations:
3828         if (ir == xi[0])
3829         {  // r slice
3830           // print_need_cout("ir=%d, r=%f (pz)=(%d,%d), distortR=%2.2f, distortP=%2.2f\n",ir,partR,ip,iz,distortR,distortP);
3831           hIntDist[0][0]->Fill(partP, partZ, distortR);
3832           hIntDist[0][1]->Fill(partP, partZ, distortP);
3833           hIntDist[0][2]->Fill(partP, partZ, distortZ);
3834           hDiffDist[0][0]->Fill(partP, partZ, diffdistR);
3835           hDiffDist[0][1]->Fill(partP, partZ, diffdistP);
3836           hDiffDist[0][2]->Fill(partP, partZ, diffdistZ);
3837         }
3838         if (ip == xi[1])
3839         {  // phi slice
3840           // print_need_cout("ip=%d, p=%f (rz)=(%d,%d), distortR=%2.2f, distortP=%2.2f\n",ip,partP,ir,iz,distortR,distortP);
3841           hIntDist[1][0]->Fill(partZ, partR, distortR);
3842           hIntDist[1][1]->Fill(partZ, partR, distortP);
3843           hIntDist[1][2]->Fill(partZ, partR, distortZ);
3844           hDiffDist[1][0]->Fill(partZ, partR, diffdistR);
3845           hDiffDist[1][1]->Fill(partZ, partR, diffdistP);
3846           hDiffDist[1][2]->Fill(partZ, partR, diffdistZ);
3847 
3848           if (iz == xi[2])
3849           {  // z slices of phi slices= r line at mid phi, mid z:
3850             hRDist[0][0]->Fill(partR, distortR);
3851             hRDist[0][1]->Fill(partR, distortP);
3852             hRDist[0][2]->Fill(partR, distortZ);
3853             hRDiffDist[0][0]->Fill(partR, diffdistR);
3854             hRDiffDist[0][1]->Fill(partR, diffdistP);
3855             hRDiffDist[0][2]->Fill(partR, diffdistZ);
3856           }
3857           if (hasTwin && iz == twinz)
3858           {  // z slices of phi slices= r line at mid phi, mid z:
3859             hRDist[1][0]->Fill(partR, distortR);
3860             hRDist[1][1]->Fill(partR, distortP);
3861             hRDist[1][2]->Fill(partR, distortZ);
3862             hRDiffDist[1][0]->Fill(partR, diffdistR);
3863             hRDiffDist[1][1]->Fill(partR, diffdistP);
3864             hRDiffDist[1][2]->Fill(partR, diffdistZ);
3865           }
3866         }
3867         if (iz == xi[2])
3868         {  // z slice
3869           // print_need_cout("iz=%d, z=%f (rp)=(%d,%d), distortR=%2.2f, distortP=%2.2f\n",iz,partZ,ir,ip,distortR,distortP);
3870 
3871           hIntDist[2][0]->Fill(partR, partP, distortR);
3872           hIntDist[2][1]->Fill(partR, partP, distortP);
3873           hIntDist[2][2]->Fill(partR, partP, distortZ);
3874           hDiffDist[2][0]->Fill(partR, partP, diffdistR);
3875           hDiffDist[2][1]->Fill(partR, partP, diffdistP);
3876           hDiffDist[2][2]->Fill(partR, partP, diffdistZ);
3877         }
3878 
3879         if (!(el % percent))
3880         {
3881           std::cout << boost::str(boost::format("generating distortions %d%%:  ") % ((int) (debug_npercent * (el / percent))));
3882           std::cout << boost::str(boost::format("distortion at (ir=%d,ip=%d,iz=%d) is (%E,%E,%E)") % ir % ip % iz % distortR % distortP % distortZ) << std::endl;
3883         }
3884         el++;
3885       }
3886     }
3887   }
3888 
3889   TCanvas *canvas = new TCanvas("cdistort", "distortion integrals", 1200, 800);
3890   // take 10 of the bottom of this for data?
3891   canvas->cd();
3892   TPad *c = new TPad("cplots", "distortion integral plots", 0, 0.2, 1, 1);
3893   canvas->cd();
3894   TPad *textpad = new TPad("ctext", "distortion integral plots", 0, 0.0, 1, 0.2);
3895   c->Divide(4, 3);
3896   gStyle->SetOptStat();
3897   for (int i = 0; i < 3; i++)
3898   {
3899     // component
3900     for (int ax = 0; ax < 3; ax++)
3901     {
3902       // plane
3903       c->cd(i * 4 + ax + 1);
3904       gPad->SetRightMargin(0.15);
3905       hIntDist[ax][i]->SetStats(false);
3906       hIntDist[ax][i]->Draw("colz");
3907     }
3908     c->cd(i * 4 + 4);
3909     hRDist[0][i]->SetStats(false);
3910     hRDist[0][i]->SetFillColor(kRed);
3911     hRDist[0][i]->Draw("hist");
3912     if (hasTwin)
3913     {
3914       hRDist[1][i]->SetStats(false);
3915       hRDist[1][i]->SetLineColor(kBlue);
3916       hRDist[1][i]->Draw("hist,same");
3917     }
3918     /*
3919       double Cut = 40;
3920       h->SetFillColor(kRed);
3921       TH1F *hNeg = (TH1F*)hRDist[i]->Clone(Form("hNegRDist%d",i));
3922       hNeg->SetFillColor(kGreen);
3923       for (int n = 1; n <= hNeg->GetNbinsX(); n++) {
3924       hNeg->SetBinContent(n,Cut);
3925       }
3926       h3->Draw(); h.Draw("same");
3927       TH1F *h2 = (TH1F*)h->Clone("h2");
3928       h2->SetFillColor(kGray-4);
3929       h2->SetMaximum(Cut);
3930       h2->Draw("same");
3931     */
3932   }
3933   textpad->cd();
3934   float texpos = 0.9;
3935   float texshift = 0.12;
3936   TLatex *tex = new TLatex(0.0, texpos, "Fill Me In");
3937   tex->SetTextSize(texshift * 0.8);
3938   tex->DrawLatex(0.05, texpos, GetFieldString().c_str());
3939   texpos -= texshift;
3940   tex->DrawLatex(0.05, texpos, GetChargeString().c_str());
3941   texpos -= texshift;
3942   // tex->DrawLatex(0.05,texpos,Form("Drift Field = %2.2f V/cm",GetNominalE()));texpos-=texshift;
3943   tex->DrawLatex(0.05, texpos, boost::str(boost::format("Drifting grid of (rp)=(%d x %d) electrons with %d steps") % nrh % nph % nSteps).c_str());
3944   texpos -= texshift;
3945   tex->DrawLatex(0.05, texpos, GetLookupString().c_str());
3946   texpos -= texshift;
3947   tex->DrawLatex(0.05, texpos, GetGasString().c_str());
3948   texpos -= texshift;
3949   if (debug_distortionScale.Mag() > 0)
3950   {
3951     tex->DrawLatex(0.05, texpos, boost::str(boost::format("Distortion scaled by (r,p,z)=(%2.2f,%2.2f,%2.2f)") % debug_distortionScale.X() % debug_distortionScale.Y() % debug_distortionScale.Z()).c_str());
3952     texpos -= texshift;
3953   }
3954   texpos = 0.9;
3955 
3956   canvas->cd();
3957   c->Draw();
3958   canvas->cd();
3959   textpad->Draw();
3960   canvas->SaveAs(summaryFilename.c_str());
3961 
3962   // canvas->cd();
3963   // already done TPad *c=new TPad("cplots","distortion differential plots",0,0.2,1,1);
3964   // canvas->cd();
3965   // already done TPad *textpad=new TPad("ctext","distortion differential plots",0,0.0,1,0.2);
3966   // already done c->Divide(4,3);
3967   // gStyle->SetOptStat();
3968   for (int i = 0; i < 3; i++)
3969   {
3970     // component
3971     for (int ax = 0; ax < 3; ax++)
3972     {
3973       // plane
3974       c->cd(i * 4 + ax + 1);
3975       gPad->SetRightMargin(0.15);
3976       hDiffDist[ax][i]->SetStats(false);
3977       hDiffDist[ax][i]->Draw("colz");
3978     }
3979     c->cd(i * 4 + 4);
3980     hRDiffDist[0][i]->SetStats(false);
3981     hRDiffDist[0][i]->SetFillColor(kRed);
3982     hRDiffDist[0][i]->Draw("hist");
3983     if (hasTwin)
3984     {
3985       hRDiffDist[1][i]->SetStats(false);
3986       hRDiffDist[1][i]->SetLineColor(kBlue);
3987       hRDiffDist[1][i]->Draw("hist same");
3988     }
3989   }
3990   textpad->cd();
3991   texpos = 0.9;
3992   texshift = 0.12;
3993   tex->SetTextSize(texshift * 0.8);
3994   tex->DrawLatex(0.05, texpos, GetFieldString().c_str());
3995   texpos -= texshift;
3996   tex->DrawLatex(0.05, texpos, GetChargeString().c_str());
3997   texpos -= texshift;
3998   // tex->DrawLatex(0.05,texpos,Form("Drift Field = %2.2f V/cm",GetNominalE()));texpos-=texshift;
3999   tex->DrawLatex(0.05, texpos, boost::str(boost::format("Drifting grid of (rp)=(%d x %d) electrons with %d steps") % nrh % nph % nSteps).c_str());
4000   texpos -= texshift;
4001   tex->DrawLatex(0.05, texpos, GetLookupString().c_str());
4002   texpos -= texshift;
4003   tex->DrawLatex(0.05, texpos, GetGasString().c_str());
4004   texpos -= texshift;
4005   tex->DrawLatex(0.05, texpos, "Differential Plots");
4006   texpos -= texshift;
4007   if (debug_distortionScale.Mag() > 0)
4008   {
4009     tex->DrawLatex(0.05, texpos, boost::str(boost::format("Distortion scaled by (r,p,z)=(%2.2f,%2.2f,%2.2f)") % debug_distortionScale.X() % debug_distortionScale.Y() % debug_distortionScale.Z()).c_str());
4010     texpos -= texshift;
4011   }
4012   texpos = 0.9;
4013 
4014   canvas->cd();
4015   c->Draw();
4016   canvas->cd();
4017   textpad->Draw();
4018   canvas->SaveAs(diffSummaryFilename.c_str());
4019 
4020   //  print_need_cout("map:%s.\n",distortionFilename.c_str());
4021 
4022   outf->cd();
4023 
4024   hDistortionR->Write();
4025   hDistortionP->Write();
4026   hDistortionZ->Write();
4027   hIntDistortionR->Write();
4028   hIntDistortionP->Write();
4029   hIntDistortionZ->Write();
4030   if (andCartesian)
4031   {
4032     hIntDistortionX->Write();
4033     hIntDistortionY->Write();
4034   }
4035   dTree->Write();
4036   outf->Close();
4037   // print_need_cout("map:%s.closed\n",distortionFilename.c_str());
4038 
4039   /*
4040   //all histograms associated with this file should be deleted when we closed the file.
4041   //delete the histograms we made:
4042   TH3F *m;
4043   m = (TH3F*)gROOT­>FindObject("hDistortionR");  if(m) { print_need_cout("found in memory still.\n"); m­>Delete();}
4044   m = (TH3F*)gROOT­>FindObject("hDistortionP");  if(m) m­>Delete();
4045   m = (TH3F*)gROOT­>FindObject("hDistortionZ");  if(m) m­>Delete();
4046   m = (TH3F*)gROOT­>FindObject("hIntDistortionR");  if(m) m­>Delete();
4047   m = (TH3F*)gROOT­>FindObject("hIntDistortionP");  if(m) m­>Delete();
4048   m = (TH3F*)gROOT­>FindObject("hIntDistortionZ");  if(m) m­>Delete();
4049 
4050   print_need_cout("map:%s.deleted via convoluted call.  sigh.\n",distortionFilename.c_str());
4051   for (int i=0;i<3;i++){
4052     //loop over which axis of the distortion to read
4053     for (int ax=0;ax<3;ax++){
4054       //loop over which plane to work in
4055       hIntDist[ax][i]->Delete();
4056     }
4057     hRDist[i]->Delete();
4058   }
4059   */
4060   std::cout << "wrote map and summary to " << filebase << std::endl;
4061   std::cout << "map: " << distortionFilename << std::endl;
4062   std::cout << "summary: " << summaryFilename << std::endl;
4063   // print_need_cout("wrote map and summary to %s and %s.\n",distortionFilename.c_str(),summaryFilename.c_str());
4064   return;
4065 }
4066 
4067 TVector3 AnnularFieldSim::swimTo(float zdest, const TVector3 &start, bool interpolate, bool useAnalytic)
4068 {
4069   int defaultsteps = 100;
4070   int goodtostep = 0;
4071   if (useAnalytic)
4072   {
4073     return swimToInAnalyticSteps(zdest, start, defaultsteps, &goodtostep);
4074   }
4075   return swimToInSteps(zdest, start, defaultsteps, interpolate, &goodtostep);
4076 }
4077 
4078 TVector3 AnnularFieldSim::GetStepDistortion(float zdest, const TVector3 &start, bool interpolate, bool useAnalytic)
4079 {
4080   // getting the distortion instead of the post-step position allows us to accumulate small deviations from the original position that might be lost in the large number
4081 
4082   // using second order langevin expansion from http://skipper.physics.sunysb.edu/~prakhar/tpc/Papers/ALICE-INT-2010-016.pdf
4083   // TVector3 (*field)[nr][ny][nz]=field_;
4084   int rt, pt, zt;  // these are filled by the checkbounds that follow, but are not used.
4085   BoundsCase zBound = GetZindexAndCheckBounds(start.Z(), &zt);
4086   if (GetRindexAndCheckBounds(start.Perp(), &rt) != InBounds || GetPhiIndexAndCheckBounds(FilterPhiPos(start.Phi()), &pt) != InBounds || (zBound != InBounds && zBound != OnHighEdge))
4087   {
4088     // print_need_cout("AnnularFieldSim::swimTo asked to swim particle from (xyz)=(%f,%f,%f) which is outside the ROI:\n", start.X(), start.Y(), start.Z());
4089     // print_need_cout(" -- %f <= r < %f \t%f <= phi < %f \t%f <= z < %f \n", rmin_roi * step.Perp(), rmax_roi * step.Perp(), phimin_roi * step.Phi(), phimax_roi * step.Phi(), zmin_roi * step.Z(), zmax_roi * step.Z());
4090     // print_need_cout("Returning original position.\n");
4091     return start;
4092   }
4093 
4094   // set the direction of the external fields.
4095 
4096   double zdist = zdest - start.Z();
4097 
4098   // short-circuit if there's no travel length:
4099 
4100   if (std::abs(zdist) < ALMOST_ZERO * step.Z())
4101   {
4102     //   std::cout <<  boost::str(boost::format("Asked  particle from (%f,%f,%f) to z=%f, which is a distance of %fcm.  Returning zero.") %start.X() %start.Y() %start.Z() %zdest %zdist) << std::endl;
4103     return zero_vector;
4104   }
4105 
4106   TVector3 fieldInt;   // integral of E field along path
4107   TVector3 fieldIntB;  // integral of B field along path
4108 
4109   // note that using analytic takes priority over interpolating todo: clean this up to use a status rther than a pair of flags
4110   if (useAnalytic)
4111   {
4112     fieldInt = analyticFieldIntegral(zdest, start, Efield);
4113     fieldIntB = analyticFieldIntegral(zdest, start, Bfield);
4114   }
4115   else if (interpolate)
4116   {
4117     fieldInt = interpolatedFieldIntegral(zdest, start, Efield);
4118     fieldIntB = interpolatedFieldIntegral(zdest, start, Bfield);
4119   }
4120   else
4121   {
4122     fieldInt = fieldIntegral(zdest, start, Efield);
4123     fieldIntB = fieldIntegral(zdest, start, Bfield);
4124   }
4125 
4126   if (std::abs(fieldInt.Z() / zdist) < ALMOST_ZERO)
4127   {
4128     std::cout << "GetStepDistortion is attempting to swim with no drift field:" << std::endl;
4129     std::cout << boost::str(boost::format("GetStepDistortion: (%2.4f,%2.4f,%2.4f) to z=%2.4f") % start.X() % start.Y() % start.Z() % zdest) << std::endl;
4130     std::cout << boost::str(boost::format("GetStepDistortion: fieldInt=(%E,%E,%E)") % fieldInt.X() % fieldInt.Y() % fieldInt.Z()) << std::endl;
4131     exit(1);
4132   }
4133   // float fieldz=field_[in3(x,y,0,fx,fy,fz)].Z()+E.Z();// *field[x][y][zi].Z();
4134   double EfieldZ = fieldInt.Z() / zdist;  // average field over the path.
4135   double BfieldZ = fieldIntB.Z() / zdist;
4136   // double fieldz=Enominal; // ideal field over path.
4137 
4138   // these values should be with real, not nominal field?
4139   // double mu=std::abs(vdrift/Enominal);//vdrift in [cm/s], field in [V/cm] hence mu in [cm^2/(V*s)];  should be a positive value.  drift velocity over field magnitude, not field direction.
4140   // double omegatau=-mu*Bnominal;//minus sign is for electron charge.
4141   double omegatau = omegatau_nominal;  // don't compute this every time!
4142   // or:  omegatau=-10*(10*B.Z()/Tesla)*(vdrift/(cm/us))/(fieldz/(V/cm)); //which is the same as my calculation up to a sign.
4143   // std::cout << "omegatau=" << omegatau << std::endl;
4144 
4145   double T1om = langevin_T1 * omegatau;
4146   double T2om2 = langevin_T2 * omegatau * langevin_T2 * omegatau;
4147   double c0 = 1 / (1 + T2om2);           //
4148   double c1 = T1om / (1 + T1om * T1om);  // Carlos gets this term wrong.  It should be linear on top, quadratic on bottom.
4149   double c2 = T2om2 / (1 + T2om2);
4150 
4151   TVector3 EintOverEz = 1 / EfieldZ * fieldInt;   // integral of E/E_z= integral of E / integral of E_z * delta_z
4152   TVector3 BintOverBz = 1 / BfieldZ * fieldIntB;  // should this (and the above?) be BfieldZ or Bnominal?
4153 
4154   // really this should be the integral of the ratio, not the ratio of the integrals.
4155   // and should be integrals over the B field, btu for now that's fixed and constant across the region, so not necessary
4156   // there's no reason to do this as r phi.  This is an equivalent result, since I handle everything in vectors.
4157   double deltaX = c0 * EintOverEz.X() + c1 * EintOverEz.Y() - c1 * BintOverBz.Y() + c2 * BintOverBz.X();
4158   double deltaY = c0 * EintOverEz.Y() - c1 * EintOverEz.X() + c1 * BintOverBz.X() + c2 * BintOverBz.Y();
4159   // strictly, for deltaZ we want to integrate v'(E)*(E-E0)dz and v''(E)*(E-E0)^2 dz, but over a short step the field is constant, and hence this can be a product of the integral and not an integral of the product:
4160 
4161   double vprime = (5000 * cm / s) / (100 * V / cm);  // hard-coded value for 50:50.  Eventually this needs to be part of the constructor, as do most of the repeated math terms here.
4162   double vdoubleprime = 0;                           // neglecting the v'' term for now.  Fair? It's pretty linear at our operating point, and it would require adding an additional term to the field integral.
4163 
4164   // note: as long as my step is very small, I am essentially just reading the field at a point and multiplying by the step size.
4165   // hence integral of P dz, where P is a function of the fields, int|P(E(x,y,z))dz=P(int|E(x,y,z)dz/deltaZ)*deltaZ
4166   // hence: , eg, int|E^2dz=(int|Edz)^2/deltaz
4167   double deltaZ = vprime / vdrift * (fieldInt.Z() - zdist * Enominal) + vdoubleprime / vdrift * (fieldInt.Z() - Enominal * zdist) * (fieldInt.Z() - Enominal * zdist) / (2 * zdist) - 0.5 / zdist * (EintOverEz.X() * EintOverEz.X() + EintOverEz.Y() * EintOverEz.Y()) + c1 / zdist * (EintOverEz.X() * BintOverBz.Y() - EintOverEz.Y() * BintOverBz.X()) + c2 / zdist * (EintOverEz.X() * BintOverBz.X() + EintOverEz.Y() * BintOverBz.Y()) + c2 / zdist * (BintOverBz.X() * BintOverBz.X() + BintOverBz.Y() * BintOverBz.Y());  // missing v'' term.
4168 
4169   if (false)
4170   {
4171     std::cout << boost::str(boost::format("GetStepDistortion:  (c0,c1,c2)=(%E,%E,%E)") % c0 % c1 % c2) << std::endl;
4172     std::cout << boost::str(boost::format("GetStepDistortion:  EintOverEz==(%E,%E,%E)") % EintOverEz.X() % EintOverEz.Y() % EintOverEz.Z()) << std::endl;
4173     std::cout << boost::str(boost::format("GetStepDistortion:  BintOverBz==(%E,%E,%E)") % BintOverBz.X() % BintOverBz.Y() % BintOverBz.Z()) << std::endl;
4174     std::cout << boost::str(boost::format("GetStepDistortion: (%2.4f,%2.4f,%2.4f) to z=%2.4f") % start.X() % start.Y() % start.Z() % zdest) << std::endl;
4175     std::cout << boost::str(boost::format("GetStepDistortion: fieldInt=(%E,%E,%E)") % fieldInt.X() % fieldInt.Y() % fieldInt.Z()) << std::endl;
4176     std::cout << boost::str(boost::format("GetStepDistortion: delta=(%E,%E,%E)") % deltaX % deltaY % deltaZ) << std::endl;
4177   }
4178 
4179   // if (std::abs(deltaZ / zdist) > 0.25)
4180   if (false)
4181   {
4182     std::cout << "GetStepDistortion produced a very large zdistortion!" << std::endl;
4183     std::cout << boost::str(boost::format("GetStepDistortion: zdist=%2.4f, deltaZ=%2.4f, Delta/z=%2.4f") % zdist % deltaZ % (deltaZ / zdist)) << std::endl;
4184     std::cout << boost::str(boost::format("GetStepDistortion: average field Z:  Ez=%2.4fV/cm, Bz=%2.4fT") % (EfieldZ / (V / cm)) % (BfieldZ / Tesla)) << std::endl;
4185     std::cout << boost::str(boost::format("GetStepDistortion:  (c0,c1,c2)=(%E,%E,%E)") % c0 % c1 % c2) << std::endl;
4186     std::cout << boost::str(boost::format("GetStepDistortion:  EintOverEz==(%E,%E,%E)") % EintOverEz.X() % EintOverEz.Y() % EintOverEz.Z()) << std::endl;
4187     std::cout << boost::str(boost::format("GetStepDistortion:  BintOverBz==(%E,%E,%E)") % BintOverBz.X() % BintOverBz.Y() % BintOverBz.Z()) << std::endl;
4188     std::cout << boost::str(boost::format("GetStepDistortion: (%2.4f,%2.4f,%2.4f) to z=%2.4f") % start.X() % start.Y() % start.Z() % zdest) << std::endl;
4189     std::cout << boost::str(boost::format("GetStepDistortion: EfieldInt=(%E,%E,%E)") % fieldInt.X() % fieldInt.Y() % fieldInt.Z()) << std::endl;
4190     std::cout << boost::str(boost::format("GetStepDistortion: BfieldInt=(%E,%E,%E)") % fieldIntB.X() % fieldIntB.Y() % fieldIntB.Z()) << std::endl;
4191     std::cout << boost::str(boost::format("GetStepDistortion: delta=(%E,%E,%E)") % deltaX % deltaY % deltaZ) << std::endl;
4192     // assert(false);
4193   }
4194 
4195   if (std::abs(deltaX) < 1E-20 && !(chargeCase == NoSpacecharge))
4196   {
4197     std::cout << boost::str(boost::format("GetStepDistortion produced a very small deltaX: %E") % deltaX) << std::endl;
4198     std::cout << boost::str(boost::format("GetStepDistortion:  (c0,c1,c2)=(%E,%E,%E)") % c0 % c1 % c2) << std::endl;
4199     std::cout << boost::str(boost::format("GetStepDistortion:  EintOverEz==(%E,%E,%E)") % EintOverEz.X() % EintOverEz.Y() % EintOverEz.Z()) << std::endl;
4200     std::cout << boost::str(boost::format("GetStepDistortion:  BintOverBz==(%E,%E,%E)") % BintOverBz.X() % BintOverBz.Y() % BintOverBz.Z()) << std::endl;
4201     std::cout << boost::str(boost::format("GetStepDistortion: (%2.4f,%2.4f,%2.4f) to z=%2.4f") % start.X() % start.Y() % start.Z() % zdest) << std::endl;
4202     std::cout << boost::str(boost::format("GetStepDistortion: fieldInt=(%E,%E,%E)") % fieldInt.X() % fieldInt.Y() % fieldInt.Z()) << std::endl;
4203     std::cout << boost::str(boost::format("GetStepDistortion: delta=(%E,%E,%E)") % deltaX % deltaY % deltaZ) << std::endl;
4204     // assert(1==2);
4205   }
4206 
4207   // if (!(std::abs(deltaX) < 1E3))
4208   if (false)
4209   {
4210     std::cout << boost::str(boost::format("GetStepDistortion produced a very large deltaX: %E") % deltaX) << std::endl;
4211     std::cout << boost::str(boost::format("GetStepDistortion:  (c0,c1,c2)=(%E,%E,%E)") % c0 % c1 % c2) << std::endl;
4212     std::cout << boost::str(boost::format("GetStepDistortion:  EintOverEz==(%E,%E,%E)") % EintOverEz.X() % EintOverEz.Y() % EintOverEz.Z()) << std::endl;
4213     std::cout << boost::str(boost::format("GetStepDistortion:  BintOverBz==(%E,%E,%E)") % BintOverBz.X() % BintOverBz.Y() % BintOverBz.Z()) << std::endl;
4214     std::cout << boost::str(boost::format("GetStepDistortion: (%2.4f,%2.4f,%2.4f) (rp)=(%2.4f,%2.4f) to z=%2.4f") % start.X() % start.Y() % start.Z() % start.Perp() % start.Phi() % zdest) << std::endl;
4215     std::cout << boost::str(boost::format("GetStepDistortion: fieldInt=(%E,%E,%E)") % fieldInt.X() % fieldInt.Y() % fieldInt.Z()) << std::endl;
4216     std::cout << boost::str(boost::format("GetStepDistortion: delta=(%E,%E,%E)") % deltaX % deltaY % deltaZ) << std::endl;
4217     exit(1);
4218   }
4219 
4220   // deltaZ=0;//temporary removal.
4221 
4222   TVector3 shift(deltaX, deltaY, deltaZ);
4223   if (debug_distortionScale.Mag() > 0)
4224   {  // debug code to scale the resulting distortions
4225     shift.RotateZ(-start.Phi());
4226     // TVector3 localScale=debug_distortionScale;
4227     // localScale.RotateZ(start.Phi());
4228     shift.SetXYZ(shift.X() * debug_distortionScale.X(), shift.Y() * debug_distortionScale.Y(), shift.Z() * debug_distortionScale.Z());
4229     shift.RotateZ(start.Phi());
4230   }
4231 
4232   return shift;
4233 }
4234 
4235 // putting all the getters here out of the way:
4236 const std::string AnnularFieldSim::GetLookupString()
4237 {
4238   if (lookupCase == LookupCase::Full3D)
4239   {
4240     return boost::str(boost::format("Full3D (%d x %d x %d) with (%d x %d x %d) roi") % nr % nphi % nz % nr_roi % nphi_roi % nz_roi);
4241   }
4242 
4243   if (lookupCase == LookupCase::PhiSlice)
4244   {
4245     return boost::str(boost::format("PhiSlice (%d x %d x %d) with (%d x 1 x %d) roi") % nr % nphi % nz % nr_roi % nz_roi);
4246   }
4247 
4248   return "broken";
4249 }
4250 const std::string AnnularFieldSim::GetGasString()
4251 {
4252   return boost::str(boost::format("vdrift=%2.2fcm/us, Enom=%2.2fV/cm, Bnom=%2.2fT, omtau=%2.4E") % (vdrift / (cm / us)) % (Enominal / (V / cm)) % (Bnominal / Tesla) % omegatau_nominal);
4253 }
4254 
4255 const std::string AnnularFieldSim::GetFieldString()
4256 {
4257   return boost::str(boost::format("%s, %s") % Efieldname % Bfieldname);
4258 }
4259 
4260 // NOLINTNEXTLINE(misc-no-recursion)
4261 TVector3 AnnularFieldSim::GetFieldAt(const TVector3 &pos)
4262 {
4263   // assume pos is in native units (see header)
4264 
4265   int r, p, z;
4266 
4267   if (GetRindexAndCheckBounds(pos.Perp(), &r) == BoundsCase::OutOfBounds)
4268   {
4269     return zero_vector;
4270   }
4271   if (GetPhiIndexAndCheckBounds(FilterPhiPos(pos.Phi()), &p) == BoundsCase::OutOfBounds)
4272   {
4273     return zero_vector;
4274   }
4275   if (GetZindexAndCheckBounds(pos.Z(), &z) == BoundsCase::OutOfBounds)
4276   {
4277     if (hasTwin)
4278     {
4279       return twin->GetFieldAt(pos);
4280     }
4281     return zero_vector;
4282   }
4283   return Efield->Get(r, p, z);
4284 }
4285 
4286 // NOLINTNEXTLINE(misc-no-recursion)
4287 TVector3 AnnularFieldSim::GetBFieldAt(const TVector3 &pos)
4288 {
4289   // assume pos is in native units (see header)
4290 
4291   int r, p, z;
4292 
4293   if (GetRindexAndCheckBounds(pos.Perp(), &r) == BoundsCase::OutOfBounds)
4294   {
4295     return zero_vector;
4296   }
4297   if (GetPhiIndexAndCheckBounds(FilterPhiPos(pos.Phi()), &p) == BoundsCase::OutOfBounds)
4298   {
4299     return zero_vector;
4300   }
4301   if (GetZindexAndCheckBounds(pos.Z(), &z) == BoundsCase::OutOfBounds)
4302   {
4303     if (hasTwin)
4304     {
4305       return twin->GetBFieldAt(pos);
4306     }
4307     return zero_vector;
4308   }
4309   return Bfield->Get(r, p, z);
4310 }
4311 
4312 // NOLINTNEXTLINE(misc-no-recursion)
4313 float AnnularFieldSim::GetChargeAt(const TVector3 &pos)
4314 {
4315   int z;
4316   BoundsCase zbound = GetZindexAndCheckBounds(pos.Z(), &z);  //==BoundsCase::OutOfBounds) return zero_vector;
4317   if (zbound == OutOfBounds)
4318   {
4319     if (hasTwin)
4320     {
4321       return twin->GetChargeAt(pos);
4322     }
4323     std::cout << boost::str(boost::format("Caution:  tried to read charge at zbin=%d!  No twin available to handle this") % z) << std::endl;
4324     return -999;
4325   }
4326 
4327   return q->GetChargeAtPosition(pos.Perp(), FilterPhiPos(pos.Phi()), pos.Z());  // because tvectors take position to be -phi to phi, we always have to filter.
4328   // actually, we should probably just yield to that assumption in more places to speed this up.
4329   /*
4330   //assume pos is in native units (see header)
4331   int r, p, z;
4332 
4333   //get the bounds, but we don't want to actually check the cases, because the charge can go outside the vector region.
4334   r = GetRindex(pos.Perp());
4335   p = GetPhiIndex(pos.Phi());
4336 
4337   BoundsCase zbound = GetZindexAndCheckBounds(pos.Z(), &z);  //==BoundsCase::OutOfBounds) return zero_vector;
4338   if (zbound == OutOfBounds && hasTwin) return twin->GetChargeAt(pos);
4339   return q->Get(r, p, z);
4340   */
4341 }