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]); + } +}