Author: [log in to unmask]
Date: Wed Jan 7 17:10:28 2015
New Revision: 1895
Log:
Add cluster energy and position calculation classes extracted from HPSEcalCluster and HPSEcalClusterIC.
Added:
java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ReconClusterEnergyCorrection.java
java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ReconClusterPositionCorrection.java
java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ReconClusterPropertyCalculator.java
Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ReconClusterEnergyCorrection.java
=============================================================================
--- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ReconClusterEnergyCorrection.java (added)
+++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ReconClusterEnergyCorrection.java Wed Jan 7 17:10:28 2015
@@ -0,0 +1,82 @@
+package org.hps.recon.ecal.cluster;
+
+import org.lcsim.event.Cluster;
+import org.lcsim.event.base.BaseCluster;
+
+/**
+ * Cluster energy correction algorithm extracted from <code>HPSEcalClusterIC</code> class.
+ *
+ * @author Jeremy McCormick <[log in to unmask]>
+ */
+public final class ReconClusterEnergyCorrection {
+
+ // Variables for electron energy corrections.
+ static final double ELECTRON_ENERGY_A = -0.0027;
+ static final double ELECTRON_ENERGY_B = -0.06;
+ static final double ELECTRON_ENERGY_C = 0.95;
+
+ // Variables for positron energy corrections.
+ static final double POSITRON_ENERGY_A = -0.0096;
+ static final double POSITRON_ENERGY_B = -0.042;
+ static final double POSITRON_ENERGY_C = 0.94;
+
+ // Variables for photon energy corrections.
+ static final double PHOTON_ENERGY_A = 0.0015;
+ static final double PHOTON_ENERGY_B = -0.047;
+ static final double PHOTON_ENERGY_C = 0.94;
+
+ /**
+ * Calculate the corrected energy for the cluster.
+ * @param cluster The input cluster.
+ * @return The corrected energy.
+ */
+ public static double calculateCorrectedEnergy(Cluster cluster) {
+ double rawE = cluster.getEnergy();
+ return computeCorrectedEnergy(cluster.getParticleId(), rawE);
+ }
+
+ /**
+ * Calculate the corrected energy and set on the cluster.
+ * @param cluster The input cluster.
+ */
+ public static void setCorrectedEnergy(BaseCluster cluster) {
+ double correctedEnergy = calculateCorrectedEnergy(cluster);
+ cluster.setEnergy(correctedEnergy);
+ }
+
+ /**
+ * Calculates energy correction based on cluster raw energy and particle type as per
+ * <a href="https://misportal.jlab.org/mis/physics/hps_notes/index.cfm?note_year=2014">HPS Note 2014-001</a>
+ * @param pdg Particle id as per PDG
+ * @param rawEnergy Raw Energy of the cluster (sum of hits with shared hit distribution)
+ * @return Corrected Energy
+ */
+ private static double computeCorrectedEnergy(int pdg, double rawEnergy) {
+ switch(pdg) {
+ case 11:
+ // electron
+ return computeCorrectedEnergy(rawEnergy, ELECTRON_ENERGY_A, ELECTRON_ENERGY_B, ELECTRON_ENERGY_C);
+ case -11:
+ // positron
+ return computeCorrectedEnergy(rawEnergy, POSITRON_ENERGY_A, POSITRON_ENERGY_B, POSITRON_ENERGY_C);
+ case 22:
+ // photon
+ return computeCorrectedEnergy(rawEnergy, PHOTON_ENERGY_A, PHOTON_ENERGY_B, PHOTON_ENERGY_C);
+ default:
+ // unknown
+ return rawEnergy;
+ }
+ }
+
+ /**
+ * Calculates the energy correction to a cluster given the variables from the fit as per
+ * <a href="https://misportal.jlab.org/mis/physics/hps_notes/index.cfm?note_year=2014">HPS Note 2014-001</a>
+ * @param rawEnergy Raw energy of the cluster
+ * @param A,B,C from fitting in note
+ * @return Corrected Energy
+ */
+ private static double computeCorrectedEnergy(double rawEnergy, double varA, double varB, double varC){
+ double corrEnergy = rawEnergy / (varA * rawEnergy + varB / (Math.sqrt(rawEnergy)) + varC);
+ return corrEnergy;
+ }
+}
Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ReconClusterPositionCorrection.java
=============================================================================
--- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ReconClusterPositionCorrection.java (added)
+++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ReconClusterPositionCorrection.java Wed Jan 7 17:10:28 2015
@@ -0,0 +1,95 @@
+package org.hps.recon.ecal.cluster;
+
+import org.lcsim.event.Cluster;
+import org.lcsim.event.base.BaseCluster;
+
+/**
+ * <p>
+ * Cluster position corrections taken from the <code>HPSEcalClusterIC</code> class.
+ * <p>
+ * This should be used before the energy is corrected on the Cluster.
+ *
+ * @author Jeremy McCormick <[log in to unmask]>
+ */
+public final class ReconClusterPositionCorrection {
+
+ // Variables for electron position corrections.
+ static final double ELECTRON_POS_A = 0.0066;
+ static final double ELECTRON_POS_B = -0.03;
+ static final double ELECTRON_POS_C = 0.028;
+ static final double ELECTRON_POS_D = -0.45;
+ static final double ELECTRON_POS_E = 0.465;
+
+ // Variables for positron position corrections.
+ static final double POSITRON_POS_A = 0.0072;
+ static final double POSITRON_POS_B = -0.031;
+ static final double POSITRON_POS_C = 0.007;
+ static final double POSITRON_POS_D = 0.342;
+ static final double POSITRON_POS_E = 0.108;
+
+ // Variables for photon position corrections.
+ static final double PHOTON_POS_A = 0.005;
+ static final double PHOTON_POS_B = -0.032;
+ static final double PHOTON_POS_C = 0.011;
+ static final double PHOTON_POS_D = -0.037;
+ static final double PHOTON_POS_E = 0.294;
+
+ public static double[] calculateCorrectedPosition(Cluster cluster) {
+ double clusterPosition[] = cluster.getPosition();
+ double correctedPosition = computeCorrectedPosition(cluster.getParticleId(), clusterPosition[0], cluster.getEnergy());
+ double[] position = new double[3];
+ position[0] = correctedPosition;
+ position[1] = clusterPosition[1];
+ position[2] = clusterPosition[2];
+ return position;
+ }
+
+ public static void setCorrectedPosition(BaseCluster cluster) {
+ cluster.setPosition(calculateCorrectedPosition(cluster));
+ }
+
+ /**
+ * Calculates position correction based on cluster raw energy, x calculated position,
+ * and particle type as per
+ * <a href="https://misportal.jlab.org/mis/physics/hps_notes/index.cfm?note_year=2014">HPS Note 2014-001</a>
+ * @param pdg Particle id as per PDG
+ * @param xCl Calculated x centroid position of the cluster, uncorrected, at face
+ * @param rawEnergy Raw energy of the cluster (sum of hits with shared hit distribution)
+ * @return Corrected x position
+ */
+ private static double computeCorrectedPosition(int pdg, double xPos, double rawEnergy) {
+ double xCl = xPos / 10.0;//convert to mm
+ double xCorr;
+ switch(pdg) {
+ case 11: //Particle is electron
+ xCorr = positionCorrection(xCl, rawEnergy, ELECTRON_POS_A, ELECTRON_POS_B, ELECTRON_POS_C, ELECTRON_POS_D, ELECTRON_POS_E);
+ return xCorr * 10.0;
+ case -11:// Particle is positron
+ xCorr = positionCorrection(xCl, rawEnergy, POSITRON_POS_A, POSITRON_POS_B, POSITRON_POS_C, POSITRON_POS_D, POSITRON_POS_E);
+ return xCorr * 10.0;
+ case 22: // Particle is photon
+ xCorr = positionCorrection(xCl, rawEnergy, PHOTON_POS_A, PHOTON_POS_B, PHOTON_POS_C, PHOTON_POS_D, PHOTON_POS_E);
+ return xCorr * 10.0;
+ default: //Unknown
+ xCorr = xCl;
+ return xCorr * 10.0;
+ }
+ }
+
+ /**
+ * Calculates the position correction in cm using the raw energy and variables associated with the fit
+ * of the particle as described in
+ * <a href="https://misportal.jlab.org/mis/physics/hps_notes/index.cfm?note_year=2014">HPS Note 2014-001</a>
+ * @param xCl
+ * @param rawEnergy
+ * @param varA
+ * @param varB
+ * @param varC
+ * @param varD
+ * @param varE
+ * @return
+ */
+ private static double positionCorrection(double xCl, double rawEnergy, double varA, double varB, double varC, double varD, double varE) {
+ return xCl - (varA / Math.sqrt(rawEnergy) + varB ) * xCl - (varC * rawEnergy + varD / Math.sqrt(rawEnergy) + varE);
+ }
+}
Added: java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ReconClusterPropertyCalculator.java
=============================================================================
--- java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ReconClusterPropertyCalculator.java (added)
+++ java/trunk/ecal-recon/src/main/java/org/hps/recon/ecal/cluster/ReconClusterPropertyCalculator.java Wed Jan 7 17:10:28 2015
@@ -0,0 +1,319 @@
+package org.hps.recon.ecal.cluster;
+
+import hep.physics.vec.BasicHep3Vector;
+import hep.physics.vec.Hep3Vector;
+import hep.physics.vec.VecOp;
+
+import java.util.List;
+
+import org.hps.recon.ecal.ECalUtils;
+import org.lcsim.detector.IGeometryInfo;
+import org.lcsim.detector.solids.Trd;
+import org.lcsim.event.CalorimeterHit;
+import org.lcsim.event.Cluster;
+import org.lcsim.event.base.AbstractClusterPropertyCalculator;
+
+/**
+ * <p>
+ * Compute cluster properties for an HPS ECAL cluster.
+ * <p>
+ * This was extracted from the <code>HPSEcalCluster</code> class.
+ *
+ * @author Jeremy McCormick <[log in to unmask]>
+ */
+public final class ReconClusterPropertyCalculator extends AbstractClusterPropertyCalculator {
+
+ static final double eCriticalW = 800.0 * ECalUtils.MeV / (74 + 1);
+ static final double radLenW = 8.8; // mm
+
+ private double dmax;
+
+ /**
+ * Calculate the max distance parameter for use in the property calculation.
+ * @param energy The energy of the cluster.
+ */
+ private void calculateMaxDistance(double energy, boolean isElectron) {
+ double y = energy / eCriticalW;
+ double Cj = isElectron ? -0.5 : 0.5;
+ double tmax = Math.log(y) + Cj; //Maximum of dE/dt profile in units of rad. len.
+ dmax = tmax * radLenW; //mm
+ }
+
+ /**
+ * <p>
+ * Perform the cluster property calculations, which sets this object's state.
+ * <p>
+ * Copied and modified from
+ * {@link org.lcsim.event.base.TensorClusterPropertyCalculator#calculateProperties(List)}.
+ */
+ public void calculateProperties(Cluster cluster) {
+
+ calculateMaxDistance(cluster.getEnergy(), cluster.getParticleId() == 11 || cluster.getParticleId() == 0);
+
+ List<CalorimeterHit> hits = cluster.getCalorimeterHits();
+
+ double[] mm_NE = new double[3];
+ double[] mm_CE = new double[3];
+ double[][] mm_PA = new double[3][3];
+ for (int i = 0; i < 3; ++i) {
+ mm_NE[i] = 0.;
+ mm_CE[i] = 0.;
+ for (int j = 0; j < 3; ++j) {
+ mm_PA[i][j] = 0.;
+ }
+ }
+ double Etot = 0.0;
+ double Exx = 0.0;
+ double Eyy = 0.0;
+ double Ezz = 0.0;
+ double Exy = 0.0;
+ double Eyz = 0.0;
+ double Exz = 0.0;
+ double CEx = 0.0;
+ double CEy = 0.0;
+ double CEz = 0.0;
+ //double CEr = 0.0;
+ double E1 = 0.0;
+ double E2 = 0.0;
+ double E3 = 0.0;
+ double NE1 = 0.0;
+ double NE2 = 0.0;
+ double NE3 = 0.0;
+ double Tr = 0.0;
+ double M = 0.0;
+ double Det = 0.0;
+ int nhits = hits.size();
+ for (int i = 0; i < hits.size(); i++) {
+ CalorimeterHit hit = hits.get(i);
+
+ //Find position at shower max
+ IGeometryInfo geom = hit.getDetectorElement().getGeometry();
+ double[] pos = geom.transformLocalToGlobal(VecOp.add(geom.transformGlobalToLocal(geom.getPosition()), (Hep3Vector) new BasicHep3Vector(0, 0, dmax - 1 * ((Trd) geom.getLogicalVolume().getSolid()).getZHalfLength()))).v();
+
+ //pos = global_pos_tmax.v();
+ double E = hit.getCorrectedEnergy();
+ Etot += E;
+ CEx += E * pos[0];
+ CEy += E * pos[1];
+ CEz += E * pos[2];
+ Exx += E * (pos[1] * pos[1] + pos[2] * pos[2]);
+ Eyy += E * (pos[0] * pos[0] + pos[2] * pos[2]);
+ Ezz += E * (pos[1] * pos[1] + pos[0] * pos[0]);
+ Exy -= E * pos[0] * pos[1];
+ Eyz -= E * pos[1] * pos[2];
+ Exz -= E * pos[0] * pos[2];
+ }
+ CEx = CEx / Etot;
+ CEy = CEy / Etot;
+ CEz = CEz / Etot;
+ double CErSq = CEx * CEx + CEy * CEy + CEz * CEz;
+ //CEr = Math.sqrt(CErSq);
+ // now go to center of energy coords.
+ if (nhits > 3) {
+ Exx = Exx - Etot * (CErSq - CEx * CEx);
+ Eyy = Eyy - Etot * (CErSq - CEy * CEy);
+ Ezz = Ezz - Etot * (CErSq - CEz * CEz);
+ Exy = Exy + Etot * CEx * CEy;
+ Eyz = Eyz + Etot * CEy * CEz;
+ Exz = Exz + Etot * CEz * CEx;
+
+ //
+ Tr = Exx + Eyy + Ezz;
+ double Dxx = Eyy * Ezz - Eyz * Eyz;
+ double Dyy = Ezz * Exx - Exz * Exz;
+ double Dzz = Exx * Eyy - Exy * Exy;
+ M = Dxx + Dyy + Dzz;
+ double Dxy = Exy * Ezz - Exz * Eyz;
+ double Dxz = Exy * Eyz - Exz * Eyy;
+ Det = Exx * Dxx - Exy * Dxy + Exz * Dxz;
+ //double xt = Tr * Tr - 3 * M;
+ //double sqrtxt = Math.sqrt(xt);
+ // eqn to solve for eigenvalues is x**3 - x**2*Tr + x*M - Det = 0
+ // crosses y axis at -Det and inflection points are
+
+ double mE1 = 0.;
+ double mE2 = 0.;
+ double mE3 = 0.;
+ double a = (3 * M - Tr * Tr) / 3.;
+ double b = (-2 * Tr * Tr * Tr + 9 * Tr * M - 27 * Det) / 27.;
+ double test = b * b / 4. + a * a * a / 27.;
+ if (test >= 0.01) {
+ //System.out.println("AbstractCluster: Only 1 real root!!!");
+ //System.out.println(" nhits = " + nhits + "\n");
+ //System.out.println(" a,b,test = " + a + " " + b + " " + test + "\n");
+ } else {
+ double temp;
+ if (test >= 0.) {
+ temp = 1.;
+ } else {
+ temp = Math.sqrt(b * b * 27. / (-a * a * a * 4.));
+ }
+ if (b > 0.) {
+ temp = -temp;
+ }
+ double phi = Math.acos(temp);
+ //double temp1 = 2. * Math.sqrt(-a / 3.);
+ mE1 = Tr / 3. + 2. * Math.sqrt(-a / 3.) * Math.cos(phi / 3.);
+ mE2 = Tr / 3. + 2. * Math.sqrt(-a / 3.) * Math.cos(phi / 3. + 2. * Math.PI / 3.);
+ mE3 = Tr / 3. + 2. * Math.sqrt(-a / 3.) * Math.cos(phi / 3. + 4. * Math.PI / 3.);
+ }
+ if (mE1 < mE2) {
+ if (mE1 < mE3) {
+ E1 = mE1;
+ if (mE2 < mE3) {
+ E2 = mE2;
+ E3 = mE3;
+ } else {
+ E2 = mE3;
+ E3 = mE2;
+ }
+ } else {
+ E1 = mE3;
+ E2 = mE1;
+ E3 = mE2;
+ }
+ } else {
+ if (mE2 < mE3) {
+ E1 = mE2;
+ if (mE1 < mE3) {
+ E2 = mE1;
+ E3 = mE3;
+ } else {
+ E2 = mE3;
+ E3 = mE1;
+ }
+ } else {
+ E1 = mE3;
+ E2 = mE2;
+ E3 = mE1;
+ }
+ }
+
+ NE1 = E1 / Etot;
+ NE2 = E2 / Etot;
+ NE3 = E3 / Etot;
+ double[] EV = new double[3];
+ EV[0] = E1;
+ EV[1] = E2;
+ EV[2] = E3;
+ // Now calculate principal axes
+ // For eigenvalue EV, the axis is (nx, ny, nz) where:
+ // (Exx - EV)nx + (Exy)ny + (Exz)nz = 0
+ // (Eyx)nx + (Eyy - EV)ny + (Eyz)nz = 0
+ // (Ezx)nx + (Ezy)ny + (Ezz - EV)nz = 0
+ // Setting nx = 1, we have:
+ // (Exx - EV) + (Exy)ny + (Exz)nz = 0
+ // (Eyx) + (Eyy - EV)ny + (Eyz)nz = 0
+ // (Ezx) + (Ezy)ny + (Ezz - EV)nz = 0
+ // and so
+ // (Exy)ny = EV - Exx - (Exz)nz => ny = (EV - Exx - Exz*nz)/Exy
+ // What if Exy = 0? Then provided Eyz is non-zero we can write:
+ // (Ezx) + (Ezy)ny + (Ezz - EV)nz = 0
+ // ny = (Exz - (Ezz-EV)*nz)/Eyz
+ // What if Exy = 0 and Eyz = 0 but (Eyy - EV) is non-zero?
+ // (Eyy - EV)ny + (Eyz)nz = 0
+ // ny = -(Eyz*nz)/(Eyy-EV)
+
+ // In the pathological case where Exz = Eyz = Ezz = 0:
+ // (Exx - EV)nx + (Exy)ny = 0 => ny/nx = -(Exx-EV)/Exy
+ // (Eyx)nx + (Eyy - EV)ny = 0 => ny/nx = -Eyx/(Eyy-EV)
+ // (EV)nz = 0
+ // so
+ // -ny/nx = (EV-Exx)/Exy = Eyx/(EV-Eyy)
+ // But watch out for order! Recalculate eigenvalues for this pathological case.
+ // (EV-Exx)(EV-Eyy) = Eyx*Exy
+ // EV^2 - EV(Exx+Eyy) + Exx*Eyy - Eyx*Exy = 0
+ //
+ // In another pathological case, Exz = Exy = 0:
+ // (Exx - EV)nx = 0
+ // (Eyy - EV)ny + (Eyz)nz = 0 => ny/nz = -(Eyz)/(Eyy-EV)
+ // (Ezy)ny + (Ezz - EV)nz = 0 => ny/nz = -(Ezz-EV)/(Ezy)
+ // so we cannot set nx = 1. Instead, write:
+ // -ny/nz = (Eyz)/(Eyy-EV) = (Ezz-EV)/(Ezy)
+ // Then
+ // (Eyz)(Ezy) = (Eyy-EV)(Ezz-EV)
+ // (Eyz)^2 = (Eyy)(Ezz) - (Eyy)(EV) - (Ezz)(EV) + (EV)^2
+ // EV^2 - EV(Eyy+Ezz) + Eyy*Ezz - Eyz*Eyz = 0
+ // Handle pathological case
+ if (Exz == 0.0 && Eyz == 0.0) {
+ // Recompute eigenvectors.
+ EV[0] = 0.5 * (Exx + Eyy) + 0.5 * Math.sqrt((Exx + Eyy) * (Exx + Eyy) + 4.0 * Exy * Exy);
+ EV[1] = 0.5 * (Exx + Eyy) - 0.5 * Math.sqrt((Exx + Eyy) * (Exx + Eyy) + 4.0 * Exy * Exy);
+ EV[2] = 0.0;
+ for (int i = 0; i < 2; i++) {
+ double nx_over_ny = Exy / (Exx - EV[i]);
+ double nx_unnormalized = nx_over_ny;
+ double ny_unnormalized = 1.0;
+ double norm = Math.sqrt(nx_unnormalized * nx_unnormalized + ny_unnormalized * ny_unnormalized);
+ mm_PA[i][0] = ny_unnormalized / norm;
+ mm_PA[i][1] = nx_unnormalized / norm;
+ mm_PA[i][2] = 0.0;
+ }
+ // ... and now set third eigenvector to the z direction:
+ mm_PA[2][0] = 0.0;
+ mm_PA[2][1] = 0.0;
+ mm_PA[2][2] = 1.0;
+ } else if (Exz == 0.0 && Exy == 0.0) {
+ // Another pathological case
+ EV[0] = 0.5 * (Eyy + Ezz) + 0.5 * Math.sqrt((Eyy + Ezz) * (Eyy + Ezz) + 4.0 * Eyz * Eyz);
+ EV[1] = 0.5 * (Eyy + Ezz) - 0.5 * Math.sqrt((Eyy + Ezz) * (Eyy + Ezz) + 4.0 * Eyz * Eyz);
+ EV[2] = 0.0;
+ for (int i = 0; i < 2; i++) {
+ double ny_over_nz = Eyz / (Eyy - EV[i]);
+ double ny_unnormalized = ny_over_nz;
+ double nz_unnormalized = 1.0;
+ double norm = Math.sqrt(ny_unnormalized * ny_unnormalized + nz_unnormalized * nz_unnormalized);
+ mm_PA[i][0] = nz_unnormalized / norm;
+ mm_PA[i][1] = ny_unnormalized / norm;
+ mm_PA[i][2] = 0.0;
+ }
+ mm_PA[2][0] = 0.0;
+ mm_PA[2][1] = 0.0;
+ mm_PA[2][2] = 1.0;
+ } else {
+ for (int i = 0; i < 3; i++) {
+ double[] C = new double[3];
+ C[0] = 1.0;
+ C[2] = (Exy * Exy + (Eyy - EV[i]) * (EV[i] - Exx))
+ / ((Eyy - EV[i]) * Exz - Eyz * Exy);
+ C[1] = (EV[i] - Exx - Exz * C[2]) / Exy;
+ if (Exy == 0.0) {
+ // Recompute
+ if (Eyz != 0.0) {
+ // ny = (Exz - (Ezz-EV)*nz)/Eyz
+ C[1] = (Exz - (Ezz - EV[i]) * C[2]) / Eyz;
+ } else {
+ // ny = -(Eyz*nz)/(Eyy-EV)
+ C[1] = -(Eyz * C[2]) / (Eyy - EV[i]);
+ }
+ }
+ double norm = Math.sqrt(C[0] * C[0] + C[1] * C[1] + C[2] * C[2]);
+ mm_PA[i][0] = C[0] / norm;
+ mm_PA[i][1] = C[1] / norm;
+ mm_PA[i][2] = C[2] / norm;
+ }
+ }
+ }
+ mm_NE[0] = NE1;
+ mm_NE[1] = NE2;
+ mm_NE[2] = NE3;
+ mm_CE[0] = CEx;
+ mm_CE[1] = CEy;
+ mm_CE[2] = CEz;
+ for (int i = 0; i < 6; i++) {
+ positionError[i] = 0.;
+ directionError[i] = 0.;
+ shapeParameters[i] = 0.;
+ }
+ for (int i = 0; i < 3; i++) {
+ position[i] = mm_CE[i];
+ shapeParameters[i] = mm_NE[i];
+ }
+
+ // Compute theta angle from origin to cluster center.
+ itheta = Math.atan(position[1] / position[2]);
+
+ // Compute phi angle from origin to cluster center.
+ iphi = Math.atan(position[0] / position[2]);
+ }
+}
|