Commit in lcsim/src/org/lcsim/contrib/seedtracker on MAIN
MaterialPolyconeSegment.java+106added 1.1
MaterialManager.java+385-2361.8 -> 1.9
+491-236
1 added + 1 modified, total 2 files
CD - big MaterialManager update...

lcsim/src/org/lcsim/contrib/seedtracker
MaterialPolyconeSegment.java added at 1.1
diff -N MaterialPolyconeSegment.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ MaterialPolyconeSegment.java	1 Jul 2008 22:29:09 -0000	1.1
@@ -0,0 +1,106 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.lcsim.contrib.seedtracker;
+
+import org.lcsim.detector.IPhysicalVolume;
+import org.lcsim.detector.solids.Polycone;
+import org.lcsim.detector.solids.Polycone.ZPlane;
+
+/**
+ * This class represents a non-cylindrical portion of a polycone. It is assumed
+ * that (rmax-rmin) ~ constant throughout the Polycone so that the Polycone 
+ * segment's thickness is always close to its thickness at its average radius.
+ * 
+ *  
+ * @author cozzy
+ */
+public class MaterialPolyconeSegment {
+    IPhysicalVolume _pv;  
+    ZPlane _zp1;
+    ZPlane _zp2; 
+    double _thickness;
+    Polycone _pc; 
+    double _angle; 
+    
+    /**
+     * Constructs a new Polycone segment. It is assumed that the inner and outer
+     * surfaces are roughly parallel. 
+     * 
+     * @param pv The associated physical volume. 
+     * @param zp1 One of the bounding ZPlanes
+     * @param zp2 The other bounding ZPlane
+     * @param thickness The thickness normal to planes.  
+     * @param angle The z-r angle of the planes w.r.t to r=0
+     */
+    public MaterialPolyconeSegment(IPhysicalVolume pv, ZPlane zp1, ZPlane zp2, double thickness, double angle){
+        
+        if(!(pv.getLogicalVolume().getSolid() instanceof Polycone)){
+            throw new RuntimeException("Non-Polycone physical volume in MaterialPolycone constructor");
+        }
+        _pc = (Polycone) pv.getLogicalVolume().getSolid(); 
+        _pv = pv; 
+        if (zp1.getZ()<zp2.getZ()) {
+            _zp1 = zp1; 
+            _zp2 = zp2; 
+        }
+        else {
+            _zp1 = zp2; 
+            _zp2 = zp1; 
+        }
+        _thickness = thickness; 
+        _angle = angle; 
+        
+    }
+    
+    public double zMin(){
+        return _zp1.getZ(); 
+    }
+    
+    public double zMax(){
+        return _zp2.getZ(); 
+    }
+    
+    /**
+     * Returns the average of the inner and outer radii at the given z. A  
+     * RuntimException is thrown if the requested z is outside the bounds. 
+     * @param z a z-coordinate in mm
+     * @return the average of the inner and outer radii in mm
+     */
+    public double rAvgAtZ(double z){
+        if (z<zMin() || z > zMax()) {
+            throw new RuntimeException("Requested Z Value outside segment bounds");  
+        }
+        return 0.5*(_pc.getOuterRadiusAtZ(z) + _pc.getInnerRadiusAtZ(z));          
+    }
+    
+    /**
+     * Returns the thickness of the polycone in Radiation Lengths. Note that currently 
+     * this value is only accurate if the inner and outer surfaces are close to 
+     * parallel (i.e. rmax - rmin ~ constant throughout the polycone segment). 
+     * This is the thickness measured normal to the plane of the polycone. 
+     *
+     * @return The thickness in radiation lengths
+     */
+    public double getThicknessInRL(){
+        return _thickness; 
+    }
+    
+    /**
+     * Get the z-r angle of the of the plane of the polycone segment from horizontal (r=0)
+     * This is calculated using the average r-values at the end points. 
+     * @return the angle in radians
+     */
+    public double getAngle(){
+        return _angle; 
+    }
+    
+    @Override
+    public String toString(){
+        StringBuffer sb = new StringBuffer("Tracker MaterialPolycone "+_pv.getName()+"\n");
+        sb.append("Segment from z = "+_zp1.getZ()+" to "+_zp2.getZ()+"\n"); 
+        return sb.toString();
+    }
+}

lcsim/src/org/lcsim/contrib/seedtracker
MaterialManager.java 1.8 -> 1.9
diff -u -r1.8 -r1.9
--- MaterialManager.java	24 Jun 2008 22:02:35 -0000	1.8
+++ MaterialManager.java	1 Jul 2008 22:29:09 -0000	1.9
@@ -11,9 +11,10 @@
 
 import hep.physics.vec.Hep3Vector;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
-import org.lcsim.event.EventHeader;
+import java.util.Map;
 import org.lcsim.detector.DetectorIdentifierHelper;
 import org.lcsim.detector.IDetectorElement;
 import org.lcsim.detector.identifier.IIdentifier;
@@ -29,6 +30,7 @@
 import org.lcsim.detector.solids.ISolid;
 import org.lcsim.detector.solids.Point3D;
 import org.lcsim.detector.solids.Polycone;
+import org.lcsim.detector.solids.Polycone.ZPlane;
 import org.lcsim.detector.solids.Tube;
 import org.lcsim.geometry.Detector;
 
@@ -38,15 +40,23 @@
  * @version 1.0
  */
 public class MaterialManager {
-    List<MaterialCylinder> _matcyl = new ArrayList<MaterialCylinder>();
-    List<MaterialDisk> _matdsk = new ArrayList<MaterialDisk>();
-    double _rmax;
+    
+    private static final boolean DEBUG = false; //enable debug output
+    private static final boolean TUBE_ONLY = true; //only use Tube elements for calculating volume. 
+    
+    private List<MaterialPolyconeSegment> _matpc = new ArrayList<MaterialPolyconeSegment>(); 
+    private List<MaterialCylinder> _matcyl = new ArrayList<MaterialCylinder>();
+    private List<MaterialDisk> _matdsk = new ArrayList<MaterialDisk>();
+    private double _rmax;
+    private CalculationCache cache; 
       
     /** Creates a new instance of MaterialManager */
     public MaterialManager() {
     }
     
     public void BuildModel(Detector det) {
+        
+        cache = new CalculationCache(det); 
         //  Build the model of tracker material
         //  Each volume defined in the compact.xml file is modelled as either
         //  a thin cylinder or disk with a thickness in radiation lengths
@@ -66,20 +76,27 @@
            //  Calculate the total volume of material, skip this object if 0
            double vtot = TotalVolume(pvflat);
           
+           
+           if (pvtree.getLogicalVolume().getSolid() instanceof Polycone){
+               handlePolycone(pvtree); 
+               continue; 
+           }
             
             if (vtot > 0.) {
+                
                 //  Calculate the average radiation length for this volume
                 double X0 = RadiationLength(pvflat);
-                //  Determine if this volume should be modeled as a barrel or disk
-                if (isCylinder(pvtree, pvflat)) {
+                
+                //  Determine if this volume should be modeled as barrel or disk
+                if (isCylinder(pvflat)) {
                     // Calculate the weighted radius of the elements
                     double rwgt = WeightedRadius(pvflat);
-                    double zmin = zmin(pvtree, pvflat);
-                    double zmax = zmax(pvtree, pvflat);
+                    double zmin = zmin(pvflat);
+                    double zmax = zmax(pvflat);
                     double zlen = zmax - zmin;
                     double thickness = vtot / (2. * Math.PI * rwgt * zlen * X0);
                     
-                    if (false){
+                    if (DEBUG){
                         System.out.println(pvtree.getName()); 
                         System.out.println("x0: "+X0 + "| zmin: "+zmin + "| zmax: "+zmax + "| vtot: "+vtot + "| thickness: "+thickness + 
                                 "| rmin: "+(rmin(pvflat)) + "| rmax: "+rmax(pvflat)); 
@@ -88,14 +105,14 @@
                     
                     _matcyl.add(new MaterialCylinder(pvtree, rwgt, zmin, zmax, thickness));
                 } else {
-                    double zwgt = WeightedZ(pvtree, pvflat);
+                    double zwgt = WeightedZ(pvflat);
                     double rmin = rmin(pvflat);
                     double rmax = rmax(pvflat);
                     double thickness = vtot / (Math.PI * (rmax*rmax - rmin*rmin) * X0);
                     
-                    if (false){
+                    if (DEBUG){
                         System.out.println(pvtree.getName()); 
-                        System.out.println("x0: "+X0 + "| zmin: "+zmin(pvtree,pvflat) + "| zmax: "+zmax(pvtree,pvflat) + "| vtot: "+vtot + "| thickness: "+thickness + 
+                        System.out.println("x0: "+X0 + "| zmin: "+zmin(pvflat) + "| zmax: "+zmax(pvflat) + "| vtot: "+vtot + "| thickness: "+thickness + 
                                 "| rmin: "+rmin + "| rmax: "+rmax); 
                         System.out.println(); 
                     }
@@ -104,6 +121,8 @@
                 }
             }
         }
+        if(DEBUG) cache.printSizeInfo(); 
+        cache.clear(); // clear cache, so stuff doesn't get held in memory. 
         
         //  Find the outer radius of the tracking volume
         //  First loop over all the subdetector elements
@@ -144,25 +163,14 @@
         return _matdsk;
     }
     
+    public List<MaterialPolyconeSegment> getMaterialPolyconeSegments() {
+        return _matpc; 
+    }
+    
     public double getRMax() {
         return _rmax;
     }
     
-//    private List<IPhysicalVolume> Flatten(IPhysicalVolume xmlvol) {        //  Flatten the geometry tree to find all the physical volumes for
-//        //  this tracker element that have no daughters
-//        LinkedList<IPhysicalVolume> pvtree = new LinkedList<IPhysicalVolume>();
-//        pvtree.add(xmlvol);
-//        List<IPhysicalVolume> pvflat = new ArrayList<IPhysicalVolume>();
-//        while (pvtree.size()>0) {
-//            IPhysicalVolume pv = pvtree.poll();
-//            if (pv.getLogicalVolume().getNumberOfDaughters()==0) {
-//                pvflat.add(pv);
-//            } else {
-//                pvtree.addAll(pv.getLogicalVolume().getDaughters());
-//            }
-//        }
-//        return pvflat;
-//    }
     
     private List<UniquePV> Flatten(IPhysicalVolume vol, IPhysicalVolumeNavigator nav){
         
@@ -176,14 +184,13 @@
             IPhysicalVolume pv = upv.getPV(); 
             
             if (pv.getLogicalVolume().getNumberOfDaughters()==0) {
-//                if(pvflat.contains(upv)) System.out.println("DUPE!!!!"); 
                 pvflat.add(upv); 
             }
-            
+ 
             else {
                 for(IPhysicalVolume p : pv.getLogicalVolume().getDaughters()) {
-                   pvtree.add(upv.createDaughterUniquePV(p)); 
-                }        
+                   pvtree.add(upv.createDaughterUniquePV(p));      
+                }
             }
         }
         
@@ -191,269 +198,391 @@
     }
     
     
+    //Tube only for now... (note that the problem with top-level polycones should no longer exist)
     private double TotalVolume(List<UniquePV> pvlist) {
-        double vtot = 0.;
-        for (UniquePV pv : pvlist) {
-            ISolid solid = pv.getPV().getLogicalVolume().getSolid();
-            
-            if (solid instanceof Tube) vtot += safeCubicVolume(solid);
-            
-        }
-        return vtot;
+        if (TUBE_ONLY) return cache.getVolumeGroupCalculations(pvlist).vtot_tube_only; 
+        return cache.getVolumeGroupCalculations(pvlist).vtot; 
     }
     
     private double RadiationLength(List<UniquePV> pvlist) {
-        double X0wgt = 0.;
-        double invX0 = 0.;
-        double vtot = 0.;
-        for (UniquePV pv : pvlist) {
-            ISolid solid = pv.getPV().getLogicalVolume().getSolid();
-            
-            double vol = safeCubicVolume(solid);
-            vtot += vol;
-            IMaterial mat = pv.getPV().getLogicalVolume().getMaterial();
-            double X0 = 10. * mat.getRadiationLength() / mat.getDensity();
-            if (X0 > 0.) invX0 += vol / X0;
-            
-        }
-        if (invX0 > 0.) X0wgt = vtot / invX0;
-        return X0wgt;
+        return cache.getVolumeGroupCalculations(pvlist).X0; 
     }
     
     private double rmin(List<UniquePV> pvlist) {
-        double rmin = 1.e10;
-        for (UniquePV pv : pvlist) {
-            ISolid solid = pv.getPV().getLogicalVolume().getSolid();
-            rmin = Math.min(rmin, calculateRMinSolid(solid,pv));
-        }
-        return rmin; 
+        return cache.getVolumeGroupCalculations(pvlist).rmin; 
     }
     
     private double rmax(List<UniquePV> pvlist) {
-        double rmax = 0.;
-        for (UniquePV pv : pvlist) {
-            ISolid solid = pv.getPV().getLogicalVolume().getSolid();
-            rmax = Math.max(rmax, calculateRMaxSolid(solid,pv));
-        }
-        return rmax;
+        return cache.getVolumeGroupCalculations(pvlist).rmax; 
     }
     
-    private double zmin(IPhysicalVolume pvtree, List<UniquePV> pvlist) {
-        double zmin = 1.e10;
-        for (UniquePV pv : pvlist) {
-            ISolid solid = pv.getPV().getLogicalVolume().getSolid();
-
-            double z0 = pvtree.getTranslation().z();
-            if (solid instanceof Tube) {
-                Tube tube = (Tube) solid;
-                zmin = Math.min(zmin, z0-tube.getZHalfLength());
-            }
-            else if (solid instanceof Box) {
-                Box box = (Box) solid; 
-                
-//                if (pvtree.getName().equals("SiTrackerBarrel_layer0")) {
-//                    System.out.println("====new box====");
-//                    System.out.println(pv.toString());
-//                    System.out.println("Claimed dimensions: "+2*box.getXHalfLength()+"x"+2*box.getYHalfLength()+"x"+2*box.getZHalfLength()); 
-//                
-//                }
-                for (Point3D p : box.getVertices()){
-                    Hep3Vector transformed = pv.localToGlobal(p.getHep3Vector()); 
-                    
-//                    if (pvtree.getName().equals("SiTrackerBarrel_layer0")) {
-//                        System.out.println(transformed.toString());
-//                    } 
-                    
-                    zmin = Math.min(transformed.z(), zmin); 
-//                    if (zmin==transformed.z()){
-                        //System.out.println("New zmin for "+pvtree.getName()+" at "+zmin+". Part name is "+pv.getName()); 
-//                    }
-                }
-            }
-            else if (solid instanceof Polycone) {
-                Polycone polycone = (Polycone) solid;
-                zmin = Math.min(zmin, z0-polycone.getZHalfLength());
-            }
-        }
-        return zmin;
+    private double zmin(List<UniquePV> pvlist) {
+       return cache.getVolumeGroupCalculations(pvlist).zmin; 
     }
     
-    private double zmax(IPhysicalVolume pvtree, List<UniquePV> pvlist) {
-        double zmax = -1.e10;
-        for (UniquePV pv : pvlist) {
-            ISolid solid = pv.getPV().getLogicalVolume().getSolid();
-            
-            double z0 = pvtree.getTranslation().z();
-
-            if (solid instanceof Tube) {
-                Tube tube = (Tube) solid;
-                zmax = Math.max(zmax, z0+tube.getZHalfLength());
-            }
-            else if (solid instanceof Box) {
-                Box box = (Box) solid;
-                for (Point3D p : box.getVertices()){
-                    Hep3Vector transformed = pv.localToGlobal(p.getHep3Vector());
-                    zmax = Math.max(transformed.z(), zmax); 
-                }
-            }
-            else if (solid instanceof Polycone) {
-                Polycone polycone = (Polycone) solid;
-                zmax = Math.max(zmax, z0+polycone.getZHalfLength());
-            }
-        }
-        return zmax;
+    private double zmax(List<UniquePV> pvlist) {
+        return cache.getVolumeGroupCalculations(pvlist).zmax; 
     }
     
-    private boolean isCylinder(IPhysicalVolume pvtree, List<UniquePV> pvlist) {
+    private boolean isCylinder(List<UniquePV> pvlist) {
         double rmin = rmin(pvlist);
         double rmax = rmax(pvlist);
-        double zmin = zmin(pvtree, pvlist);
-        double zmax = zmax(pvtree, pvlist);
+        double zmin = zmin(pvlist);
+        double zmax = zmax(pvlist);
         return (rmax - rmin) * Math.abs(zmax + zmin) < (zmax - zmin) * (rmax + rmin);
     }
     
     private double WeightedRadius(List<UniquePV> pvlist) {
-        double rwgt = 0.;
-        double totwgt = 0.;
-        for (UniquePV pv : pvlist) {
-            ISolid solid = pv.getPV().getLogicalVolume().getSolid();
-            
-            double rmin = calculateRMinSolid(solid, pv);
-            double rmax = calculateRMaxSolid(solid, pv);
-            double vol = safeCubicVolume(solid);
-            IMaterial mat = pv.getPV().getLogicalVolume().getMaterial();
-            double X0 = 10. * mat.getRadiationLength() / mat.getDensity();
-            double wgt = vol / X0;
-            rwgt += 0.5 * (rmin + rmax) * wgt;
-            totwgt += wgt;
-            
-        }
-        if (totwgt > 0.) rwgt /= totwgt;
-        return rwgt;
+        return cache.getVolumeGroupCalculations(pvlist).weighted_r; 
     }
     
-    private double WeightedZ(IPhysicalVolume pvtree, List<UniquePV> pvlist) {
-        double zwgt = 0.;
-        double totwgt = 0.;
-        for (UniquePV pv : pvlist) {
-            ISolid solid = pv.getPV().getLogicalVolume().getSolid();
-            
-            double z0 = pvtree.getTranslation().z();
-            double vol = safeCubicVolume(solid);
-            IMaterial mat = pv.getPV().getLogicalVolume().getMaterial();
-            double X0 = 10. * mat.getRadiationLength() / mat.getDensity();
-            double wgt = vol / X0;
-            zwgt += z0 * wgt;
-            totwgt += wgt;  
-            
-        }
-        if (totwgt > 0.) zwgt /= totwgt;
-        return zwgt;
+    private double WeightedZ(List<UniquePV> pvlist) {
+       return cache.getVolumeGroupCalculations(pvlist).weighted_z; 
     }
     
-    
-    //calculates the cubic volume and represses runtime exceptions by returning 0....
-    //just in case there's something that isn't implemented
-    private double safeCubicVolume(ISolid solid){
-        
-        try {
-            return solid.getCubicVolume();
-        }
-        catch(Exception e) {
-            return 0.0; 
+    // special handling for Polycone...
+    private void handlePolycone(IPhysicalVolume pv){
+        Polycone pc = (Polycone) pv.getLogicalVolume().getSolid(); 
+        IMaterial mat = pv.getLogicalVolume().getMaterial(); 
+        
+        //Loop through each segment
+        for (int i = 0; i < pc.getNumberOfZPlanes()-1; i++){
+            ZPlane zp1 = pc.getZPlane(i);
+            ZPlane zp2 = pc.getZPlane(i+1); 
+            
+            double z1 = zp1.getZ(); 
+            double z2 = zp2.getZ(); 
+            double vol = Polycone.getSegmentVolume(zp1, zp2); 
+            double zlen = Math.abs(z2-z1); 
+            double ravg = 0.25*(zp1.getRMax() + zp1.getRMin() + zp2.getRMax() + zp2.getRMin());  
+            double ang = Math.atan2( 0.5 * (zp1.getRMax() + zp1.getRMin() - zp2.getRMax() - zp2.getRMin()) 
+                                    , zlen ); 
+            double X0 = 10*mat.getRadiationLength()/mat.getDensity(); 
+            double thickness = Math.cos(ang) * vol / (2 * Math.PI * ravg * zlen * X0); 
+            
+            //This is a cylinder
+            if(zp1.getRMax()==zp2.getRMax() && zp1.getRMin()==zp2.getRMin()) {
+                _matcyl.add(new MaterialCylinder(pv, ravg, Math.min(z1,z2), Math.max(z1,z2), thickness));
+                if(DEBUG){    
+                    System.out.println("Cylindrical segment of "+pv.getName()); 
+                    System.out.println("zmin = "+ z1 + "| zmax = "+z2 + "| ravg = "+ravg + "| thickness = "+thickness); 
+                }
+            }
+            
+            //Otherwise this is a non-cylindrical polycone segment 
+            else {
+                _matpc.add(new MaterialPolyconeSegment(pv, zp1, zp2, thickness, ang)); 
+                if(DEBUG){   
+                    System.out.println("Non-Cylindrical segment of "+pv.getName()); 
+                    System.out.println("ZPlane 1: "+zp1.toString() + "| ZPlane 2: "+zp2.toString() + "| thickness = "+thickness); 
+                }
+            }
         }
     }
     
- 
-    
-    //functions that give the rmin and rmax of a solid... 
-    private double calculateRMinSolid(ISolid solid, UniquePV pv){
-        
-        double rmin = 1e10; 
-        if (solid instanceof Tube) {
-           Tube tube = (Tube) solid;
-           return tube.getInnerRadius();
-        }
-        
-        
-        else if (solid instanceof Box) {
-           Box box = (Box) solid;  
-           List<Point3D> vtx = box.getVertices();
-           
-           for (Point3D p : vtx) {
-               
-               Hep3Vector transformed = pv.localToGlobal(p.getHep3Vector());
-               double r = Math.sqrt(transformed.x()*transformed.x() + transformed.y()*transformed.y()); 
-               rmin = Math.min(rmin,r);
-           }
-           return rmin; 
-        }
-        
-        //this takes the inner radius at z = 0 of the polycone
-        else if (solid instanceof Polycone) {
-            Polycone poly = (Polycone) solid; 
-            return poly.getInnerRadiusAtZ(0);
-        }
-        
-        return rmin;
+    /**
+     * A "struct" holding geometry information about a single physical volume
+     */
+    class VolumeInfo {
+        double rmax = 0.0; 
+        double rmin = 1.e10; 
+        double zmin = 1.e10;
+        double zmax = -1.e10; 
     }
     
     
-    private double calculateRMaxSolid(ISolid solid, UniquePV pv){
-        
-        double rMax = 0; 
-        if (solid instanceof Tube) {
-           Tube tube = (Tube) solid;
-           return tube.getOuterRadius();
+    /**
+    *  A "struct" holding geometry information about lists of physical volumes
+    */
+    class VolumeGroupInfo{
+        double rmax = 0.0; 
+        double rmin = 1.e10; 
+        double zmin = 1.e10; 
+        double zmax = -1.e10; 
+        double X0 = 0.0; 
+        double weighted_r = 0.0; 
+        double weighted_z = 0.0; 
+        double vtot_tube_only = 0.; 
+        double vtot = 0.0; 
+    }
+    
+    /**
+     * A class that calculates / caches geometry information about physical volumes. 
+     */
+    class CalculationCache {
+        Map<UniquePV,VolumeInfo> pv_map;
+        Map<List<UniquePV>,VolumeGroupInfo> pv_group_map;
+        Map<ISolid,Double> solid_vol_map;
+        private final float load_factor = 0.75f; //(default)
+        
+        /**
+         * Constructor. The detector is used to help determine the initial values of the hashmaps. 
+         * @param det
+         */
+        public CalculationCache(Detector det){
+            int pv_map_size; 
+            int pv_group_map_size;
+            int solid_vol_map_size; 
+            
+            
+            //Resizing HashMaps is slow, so we use some magic numbers to speed things up a little
+            if(det.getName().indexOf("planar")>-1){ //these values should work well for the current planar model
+                if(DEBUG) System.out.println("Using initial map values for planar detector"); 
+                pv_map_size = (int) (130000/load_factor); 
+                pv_group_map_size = (int) (120/load_factor); 
+                solid_vol_map_size = (int) (1300/load_factor); 
+            } else { //these values should work pretty well for sid01
+                pv_map_size = (int) (500/load_factor); 
+                pv_group_map_size = (int) (200/load_factor); 
+                solid_vol_map_size = (int) (250/load_factor);
+            }
+            
+            pv_map = new HashMap<UniquePV,VolumeInfo>(pv_map_size,load_factor);  
+            pv_group_map = new HashMap<List<UniquePV>,VolumeGroupInfo>(pv_group_map_size,load_factor); 
+            solid_vol_map = new HashMap<ISolid,Double>(solid_vol_map_size, load_factor); 
+            
+            
+        }
+        
+        //clear function... after we've created the MaterialCylinders and
+        //material disks, we don't want to have this information cached anymore
+        //because it may sit around in RAM (not sure if the GC is smart enough 
+        //to clear it by itself).   
+        
+        /**
+         * Releases the resources associated with the cache. 
+         */
+        public void clear(){
+            pv_map.clear();
+            pv_group_map.clear(); 
+            solid_vol_map.clear(); 
+            System.gc();
+        }
+        
+        /**
+         * Prints out the current number of objects inside the cache's data structure
+         */
+        public void printSizeInfo(){
+            System.out.println("pv_map size: "+pv_map.size()); 
+            System.out.println("pv_group_map size: "+pv_group_map.size()); 
+            System.out.println("solid_vol_map size: "+solid_vol_map.size()); 
+        }
+        
+        /**
+         * Returns a VolumeGroupInfo object containing geometry and radiation-lenght
+         * information about the target collection of physical volumes. 
+         * 
+         * If a single-element collection consisting of a polycone is passed, 
+         * no calculation is performed (top-level polycones are handled separately in 
+         * MaterialManager). 
+         * 
+         * @param volgroup A list of UniquePVs 
+         * @return A VolumeGroupInfo object
+         */
+        public VolumeGroupInfo getVolumeGroupCalculations(List<UniquePV> volgroup) {
+            if (pv_group_map.containsKey(volgroup)) {
+               // System.out.println("Using cached calculation"); 
+                return pv_group_map.get(volgroup); 
+            }
+            else return performVolumeGroupCalculations(volgroup); 
+        }
+        
+        //This function performs all the calculations on lists of physical volumes
+        private VolumeGroupInfo performVolumeGroupCalculations(List<UniquePV> volgroup) {
+            
+            VolumeGroupInfo vgi = new VolumeGroupInfo(); 
+            
+            //If we have a top-level polycone, don't bother doing anything, because it'll be handled specially
+            if (volgroup.size()==1 && volgroup.get(0).getSolid() instanceof Polycone){
+                return vgi; 
+            }
+            
+            //The normal case
+            double totwgt = 0.0; 
+            if (DEBUG && volgroup.isEmpty()) System.out.println("Empty volume group..."); 
+            for(UniquePV pv : volgroup) {
+                
+                //increment total volume
+                double vol =  this.getVolumeOfSolid(pv.getSolid());  
+                if (pv.getSolid() instanceof Tube) vgi.vtot_tube_only += vol; 
+                vgi.vtot+=vol;
+                //calculate weighted R / Z / Radiation Length
+                VolumeInfo vi = this.getVolumeCalculations(pv); 
+                IMaterial mat = pv.getPV().getLogicalVolume().getMaterial(); 
+                double matX0 = 10.0 * mat.getRadiationLength() / mat.getDensity(); 
+                double wgt = vol / matX0; 
+                double z0 = pv.getLtoGTransform().getTranslation().z(); 
+                vgi.weighted_r+= 0.5 * (vi.rmin + vi.rmax) * wgt; 
+                vgi.weighted_z+= z0 * wgt; 
+                totwgt += wgt; 
+                
+                //grab (z/r)(mins/maxes) 
+                vgi.zmin = Math.min(vi.zmin,vgi.zmin);
+                vgi.zmax = Math.max(vi.zmax,vgi.zmax);
+                vgi.rmin = Math.min(vi.rmin,vgi.rmin);
+                vgi.rmax = Math.max(vi.rmax,vgi.rmax); 
+                
+            }
+            
+            //finish weighted R/Z calculations + perform X0 calculation
+            if (totwgt > 0.) {
+                vgi.weighted_r /= totwgt;
+                vgi.weighted_z /= totwgt; 
+                vgi.X0 = vgi.vtot / totwgt; 
+            } 
+            
+            
+            pv_group_map.put(volgroup,vgi); 
+            return vgi; 
+        }
+        
+        /**
+         * Returns the cubic volume of the specified solid. Caches after first 
+         * call. If solid.getCubicVolume() throws an exception, 0.0 is returned. 
+         * @param solid The target ISolid
+         * @return a double corresponding to the cubic volume in mm^3 
+         */
+        public double getVolumeOfSolid(ISolid solid){
+            if (solid_vol_map.containsKey(solid)){
+                return solid_vol_map.get(solid).doubleValue(); 
+            }
+            
+            else {
+                double vol; 
+                try{ vol = solid.getCubicVolume(); } 
+                catch(Exception e) { vol = 0.0; }
+                solid_vol_map.put(solid,vol); 
+                return vol; 
+            }
         }
         
-        else if (solid instanceof Box) {
-           Box box = (Box) solid;  
-           List<Point3D> vtx = box.getVertices();
-           
-           for (Point3D p : vtx) {
-               
-               Hep3Vector transformed = pv.localToGlobal(p.getHep3Vector());
-               double r = Math.sqrt(transformed.x()*transformed.x() + transformed.y()*transformed.y()); 
-               rMax = Math.max(r, rMax); 
-           }
-           return rMax; 
+        /**
+         * Returns a VolumeInfo object containing information about the target
+         * UniquePV (rmin, rmax, zmin, zmax). The value is cached after the first
+         * call. 
+         * 
+         * @param pv The target UniquePV
+         * @return VolumeInfo containing information about the geometry of the PV. 
+         */
+        public VolumeInfo getVolumeCalculations(UniquePV pv){
+            
+            // check if the result has been cached already
+            if (pv_map.containsKey(pv)) {
+                //System.out.println("Using cached calculation"); 
+                return pv_map.get(pv); 
+            }
+            //otherwise calculate them...
+            else return performVolumeCalculations(pv); 
+            
         }
         
-        else if (solid instanceof Polycone) {
-            Polycone poly = (Polycone) solid; 
-            return poly.getOuterRadiusAtZ(0);
+        private VolumeInfo performVolumeCalculations(UniquePV pv){
+            
+            VolumeInfo vi = new VolumeInfo(); 
+            ISolid solid = pv.getSolid();
+
+            //ASSUMPTION: tube is along z-axis and has center at r = 0
+            if (solid instanceof Tube) {
+                Tube tube = (Tube) solid;
+                double z0 = pv.getLtoGTransform().getTranslation().z();
+                vi.zmax = z0 + tube.getZHalfLength(); 
+                vi.zmin = z0 - tube.getZHalfLength(); 
+                vi.rmin = tube.getInnerRadius(); 
+                vi.rmax = tube.getOuterRadius(); 
+            }
+
+            else if (solid instanceof Box) {
+                Box box = (Box) solid; 
+                for (Point3D p : box.getVertices()){
+                    Hep3Vector transformed = pv.localToGlobal(p.getHep3Vector());
+                    vi.zmin = Math.min(transformed.z(), vi.zmin);
+                    vi.zmax = Math.max(transformed.z(), vi.zmax);
+                    double r = Math.sqrt(transformed.x() * transformed.x() + transformed.y() * transformed.y()); 
+                    vi.rmin = Math.min(vi.rmin, r);
+                    vi.rmax = Math.max(vi.rmax, r); 
+                }
+            }
+            
+            //Note: this information will NOT be used most of the time... 
+            // Polycones that are top-level elements (e.g. the beampipe) are 
+            // handled specially (since the radiation length is a function of z). 
+            // The information here will only be used in case a top-level element
+            // has a subelement that is a Polycone, in which case it'll be 
+            // approximated as the smallest possible cylinder. 
+            else if (solid instanceof Polycone) {
+                Polycone pc = (Polycone) solid; 
+                List<Polycone.ZPlane> zplanes = pc.getZPlanes();                 
+                
+                //For now, just take the minimum rmin and rmax of the polycone
+                for (Polycone.ZPlane z : zplanes){
+                    if (z.getRMax()>0 && z.getRMin()>0) {
+                        vi.rmin = Math.min(vi.rmin,z.getRMin());
+                        vi.rmax = vi.rmax > 0. ? Math.min(vi.rmax,z.getRMax()) : z.getRMax(); 
+                    }
+                }
+
+                vi.zmin = pc.getZPlanes().get(0).getZ(); 
+                vi.zmax = pc.getZPlanes().get(pc.getZPlanes().size()-1).getZ(); 
+                
+                //check for wrong order 
+                if (vi.zmin > vi.zmax) {
+                    double temp = vi.zmin; 
+                    vi.zmin = vi.zmax;
+                    vi.zmax = temp; 
+                }
+            }
+                
+            pv_map.put(pv,vi); 
+            return vi; 
         }
-        
-        return rMax;
     }
     
- 
     
+ 
+    /**
+     * A UniquePV is a wrapper around IPhysicalVolumePath which provides 
+     * some convenience methods and caches transformations. 
+     */
     class UniquePV{
         
         IPhysicalVolumePath path; 
         IPhysicalVolumeNavigator nav; 
         ITransform3D transform = null; 
         
+        /**
+         * Generates a top-level UniquePV. 
+         * @param root The top-level IPhysicalVolume
+         * @param navigator The IPhysicalVolumeNavigator associated with the detector
+         */
         public UniquePV(IPhysicalVolume root, IPhysicalVolumeNavigator navigator){
             path = new PhysicalVolumePath(); 
             nav = navigator; 
             path.add(root); 
-            
         }
         
+        /**
+         * Generates a UniquePV from a path. (Shallow copy of path)
+         * @param path 
+         * @param navigator
+         */
         public UniquePV(IPhysicalVolumePath path, IPhysicalVolumeNavigator navigator){
             this.path = path; 
             nav = navigator; 
         }
-
+        
+        /**
+         * Returns the IPhysicalVolume (the last element of the path) 
+         */
         public IPhysicalVolume getPV(){
             return path.getLeafVolume();
         }
         
+        /**
+         * Creates a UniquePV that is a daughter of the current UniquePV (deep copy made)
+         * @param daughter
+         * @return
+         */
         public UniquePV createDaughterUniquePV(IPhysicalVolume daughter){
-            
             IPhysicalVolumePath np = new PhysicalVolumePath();  
             np.addAll(path); 
             np.add(daughter);        
@@ -461,13 +590,33 @@
         }
         
         
+        /**
+         * Transforms the given vector from local to global coords.
+         * @param v the untransformed local Hep3Vector
+         * @return the transformed global Hep3Vector
+         */
         public Hep3Vector localToGlobal(Hep3Vector v) {
             
-            if(transform == null){
+            return getLtoGTransform().transformed(v);       
+        }
+        
+        /**
+         * Returns the solid associated with the physical volume. 
+         * @return
+         */
+        public ISolid getSolid(){
+            return this.getPV().getLogicalVolume().getSolid(); 
+        }
+        
+        /**
+         * Returns the local-to-global transform
+         * @return an ITransform3D from local coordinates to global coordinates. 
+         */
+        public ITransform3D getLtoGTransform() {
+            if (transform == null) {
                 transform = nav.getTransform(path); 
             }
-            return transform.transformed(v); 
-              
+            return transform; 
         }
         
         @Override 
CVSspam 0.2.8