View Javadoc

1   package org.lcsim.material;
2   
3   import static java.lang.Math.abs;
4   
5   import java.util.ArrayList;
6   import java.util.List;
7   
8   /**
9    * Implementation of detector material, mostly based on Geant4's G4Material.
10   * 
11   * @author jeremym
12   * @version $Id: Material.java,v 1.23 2011/03/11 19:22:20 jeremy Exp $
13   */
14  public class Material implements IMaterial
15  {
16      public static final double DEFAULT_TEMPERATURE = 273.15; // Default temp in Kelvin.    
17      public static final double DEFAULT_PRESSURE = 1.0; // Default pressure in atmospheres.
18      
19      private static final double MAX_X0 = java.lang.Double.MAX_VALUE; // Max X0.
20      private static final double MAX_LAMBDA = java.lang.Double.MAX_VALUE; // Max lambda.
21  
22      double _density; // Density in g/cm^3.
23  
24      boolean _isElement; // Is this a chemical element?
25  
26      private double _Zeff; // Effective Z.
27      private double _Aeff; // Effective A.
28  
29      MaterialState _state; // Material's state (gas, liquid, water, unknown).
30      String _name; // Name of material.
31      String _formula; // Formula for material.
32  
33      int _nComponents; // Number of components currently added.
34      int _nComponentsMax; // Maximum number of components that can be added.
35      int _nElements; // Number of elements in mass fraction list.
36  
37      private List<MaterialElement> _elements = new ArrayList<MaterialElement>(); // List of element composition.
38      private List<Double> _massFractions = new ArrayList<Double>(); // List of mass fractions.
39      private List<Integer> _atoms = new ArrayList<Integer>(); // List of number of atoms.
40  
41      double _radiationLength; // Radiation length in g/cm^2.
42      double _radiationLengthWithDensity; // Radiation length div by density.
43  
44      double _nuclearInteractionLength; // Nuclear interaction length in g/cm^2.
45      double _nuclearInteractionLengthWithDensity; // Nuclear interaction length div by density.
46  
47      /**
48       * This is a constructor for materials that are chemical elements. 
49       * It is not accessible outside the package, as users should not
50       * be able to create their own elements. 
51       * 
52       * @param matName The full name of the material (e.g. "Tungsten").
53       * @param formula The formula of the element (e.g. "W" for Tungsten).
54       * @param z The atomic number.
55       * @param a The atomic weight.
56       * @param density The density in g/cm3.
57       * @param X0 The radiation length in g/cm-2.
58       * @param lambda The nuclear interaction length in g/cm-2.
59       * @param state The state of the element (liquid, gas, solid, or unknown).
60       */
61      Material(String matName, 
62              String formula, 
63              double z, 
64              double a, 
65              double density, 
66              double X0, 
67              double lambda, 
68              MaterialState state)
69      {
70          // System.out.println("Material " + matName + " for element " + elemName);
71  
72          // Set basic parameters.
73          _name = matName;
74          _density = density;
75          _formula = formula;
76          _state = state;
77          
78          // Z and A don't need compute for elements.
79          _Zeff = z;
80          _Aeff = a;
81  
82          // Setup component variables.
83          _nComponents = 0;
84          _nComponentsMax = 1;
85          _isElement = true;
86  
87          // Add a new chemical element for this Material.
88          this.addElement(new MaterialElement(formula, z, a, X0, lambda), 1.0);
89  
90          // Compute the radiation length.
91          computeRadiationLength();
92          
93          // Compute the nuclear interaction length.
94          computeNuclearInteractionLength();        
95                  
96          // Add to global material store.
97          MaterialManager.instance().addMaterial(this);
98      }
99  
100     /**
101      * Construct a material with a number of sub-components, which will be
102      * either a set of mass fractions or set of number of atoms.
103      */
104     // TODO Add formula arg.
105     public Material(String name, 
106             int nComponents, 
107             double density, 
108             MaterialState state)
109     {
110         _name = name;
111         _density = density;
112         _state = state;
113        // _formula = " ";
114 
115         if (nComponents < 1)
116         {
117             throw new IllegalArgumentException("nComponents must be > 0.");
118         }
119 
120         _nComponentsMax = nComponents;
121         _nComponents = _nElements = 0;
122         _isElement = false;
123 
124         MaterialManager.instance().addMaterial(this);        
125     }
126 
127     /**
128      * Get the name of the Material.
129      * @return The name of this Material.
130      */
131     public String getName()
132     {
133         return _name;
134     }
135 
136     /**
137      * Get the density (g/cm^3).
138      * @return The density in g/cm3.
139      **/
140     public double getDensity()
141     {
142         return _density;
143     }
144 
145     /** @return whether this material has a 1 to 1 correspondance to a single chemical element */
146     public boolean isElement()
147     {
148         return _isElement;
149     }
150 
151     /**
152      * @return The effective Z of this material.
153      */
154     // FIXME: Duplicates getZ().
155     public double getZeff()
156     {
157         return _Zeff;
158     }
159 
160     /**
161      * @return The effective A of this material.
162      */
163     // FIXME: Duplicates getA().
164     public double getAeff()
165     {
166         return _Aeff;
167     }
168 
169     /** 
170      * @return The maximum number of components that can be added. 
171      **/
172     protected int getNComponentsMax()
173     {
174         return _nComponentsMax;
175     }
176 
177     /** 
178      * @return The number of components defined so far in this material. 
179      **/
180     protected int getNComponents()
181     {
182         return _nComponents;
183     }
184 
185     /** 
186      * @return number of elements defined in this material 
187      */
188     protected int getNElements()
189     {
190         return _nElements;
191     }
192 
193     /**
194      * @return the state of this material
195      */
196     public MaterialState getState()
197     {
198         return _state;
199     }
200 
201     /** 
202      * @return X0 
203      */
204     public double getRadiationLength()
205     {
206         return _radiationLength;
207     }
208 
209     /** 
210      * @return The nuclear interaction length; also called lambda. 
211      */
212     public double getNuclearInteractionLength()
213     {
214         return _nuclearInteractionLength;
215     }
216 
217     /** 
218      * @return lambda / density 
219      */
220     public double getNuclearInteractionLengthWithDensity()
221     {
222         return _nuclearInteractionLengthWithDensity;
223     }
224 
225     /** 
226      * @return X0 in cm = (X0/density) 
227      */
228     public double getRadiationLengthWithDensity()
229     {
230         return _radiationLengthWithDensity;
231     }
232 
233     /** 
234      * @return total number of (unique) elements in the element list for this material 
235      **/
236     protected int getNumberOfElements()
237     {
238         return _nElements;
239     }
240 
241     /** 
242      * Get the list of component MaterialElements referenced by this material.
243      * @return A list of elements in this material. 
244      **/
245     public List<MaterialElement> getElements()
246     {
247         return _elements;
248     }
249 
250     /**
251      * @return The mass fractions for each element in the material.
252      */
253     public List<Double> getMassFractions()
254     {
255         return _massFractions;
256     }
257 
258     /**
259      * Add an element to the material by atom count.
260      * 
261      * Based on Geant4's G4Material::AddElement() method.
262      */
263     protected void addElement(MaterialElement element, int nAtoms)
264     {
265         if (_nElements < _nComponentsMax)
266         {
267             _elements.add(element);
268             _atoms.add(_nElements, nAtoms);
269             _nComponents = ++_nElements;
270         }
271         else
272         {
273             throw new RuntimeException("Attempting to add more than declared number of elements for this material: " + _name);
274         }
275 
276         if (isFilled())
277         {
278             // double Zmol = 0;
279             double Amol = 0;
280 
281             int atomCnt = 0;
282             for (MaterialElement e : _elements)
283             {
284                 Amol += _atoms.get(atomCnt) * e.getA();
285                 ++atomCnt;
286             }
287 
288             int massCnt = 0;
289             for (MaterialElement ee : _elements)
290             {
291                 _massFractions.add(_atoms.get(massCnt) * ee.getA() / Amol);
292                 ++massCnt;
293             }
294 
295             computeDerivedQuantities();
296         }
297     }
298 
299     /**
300      * Add chemical element to the material by fraction of mass.  When
301      * all components are added, the fractions must add up to 1.0 .
302      */
303     protected void addElement(MaterialElement element, double fraction)
304     {
305         if (_nComponents < _nComponentsMax)
306         {
307             int elCnt = 0;
308             while ((elCnt < _nElements) && element != _elements.get(elCnt))
309                 elCnt++ ;
310 
311             // / Element already exists. Increase its mass fraction.
312             if (elCnt < _nElements)
313             {
314                 double currFrac = _massFractions.get(elCnt);
315                 currFrac += fraction;
316                 _massFractions.add(elCnt, currFrac);
317             }
318             // Element does not exist in list. Add a new mass fraction.
319             else
320             {
321                 _elements.add(element);
322                 _massFractions.add(elCnt, fraction);
323                 ++_nElements;
324             }
325             ++_nComponents;
326         }
327         else
328         {
329             throw new RuntimeException("Attempting to add more than declared number of components to material: " + getName());
330         }
331 
332         if (isFilled())
333         {
334             checkMassSum();
335             computeDerivedQuantities();
336         }
337     }
338 
339     /**
340      * Add a component material by fraction of mass.  When done, must add up to 1.0.    
341      * @param The material to add.
342      * @param The fraction of total which must be > 0 and <= 1.
343      */
344     public void addMaterial(Material material, double fraction)
345     {
346         if (_atoms.size() > 0)
347         {
348             throw new RuntimeException("Material is already defined by atoms: " + getName());
349         }
350 
351         if (_nComponents < _nComponentsMax)
352         {
353             // Loop over elements.
354             for (int i = 0, n = material.getNumberOfElements(); i < n; i++ )
355             {
356                 MaterialElement element = material.getElements().get(i);
357                 int elCnt = 0;
358                 // Find the element.
359                 while ((elCnt < _nElements) && (element != _elements.get(elCnt)))
360                 {
361                     ++elCnt;
362                 }
363 
364                 // Add fraction to existing element.
365                 if (elCnt < _nElements)
366                 {
367                     double currFrac = _massFractions.get(elCnt);
368                     currFrac += fraction * material.getMassFractions().get(i);
369                     _massFractions.set(elCnt, currFrac);
370                 }
371                 // Add a new element.
372                 else
373                 {
374                     // System.out.println("adding mass fraction: " + element.getName() + "=" + fraction *
375                     // material.getMassFractions().get(i) );
376 
377                     _elements.add(element);
378 
379                     // Add the mass fraction of this element, scaled by its percentage in the material's
380                     _massFractions.add(elCnt, fraction * material.getMassFractions().get(i));
381                     ++_nElements;
382                 }
383             }
384             ++_nComponents;
385         }
386         else
387         {
388             throw new RuntimeException("Attempting to add more than allowed umber of components to material: " + getName());
389         }
390 
391         if (isFilled())
392         {
393             checkMassSum();
394             computeDerivedQuantities();
395         }
396     }
397     
398     // FIXME Remove as just uses default?
399     public double getPressure()
400     {
401         return DEFAULT_PRESSURE;
402     }
403     
404     // FIXME Remove as just uses default?
405     public double getTemperature()
406     {
407         return DEFAULT_TEMPERATURE;
408     }
409     
410     /** 
411      * Translate this material to a human-readable string.
412      * @return String rep of this object. 
413      */
414     public String toString()
415     {
416         StringBuffer buff = new StringBuffer();
417         buff.append(getName() + "; state=" + _state.toString() + "; Z=" + _Zeff + "; A=" + _Aeff + "; dens=" + _density + "; X0=" + _radiationLength + "; lambda=" + _nuclearInteractionLength);
418 
419         for (int i = 0; i < this.getNElements(); i++ )
420         {
421             buff.append("\n\t" + getElements().get(i).getName() + " " + this.getMassFractions().get(i) * 100);
422         }
423 
424         return buff.toString();
425     }
426         
427     /**
428      * True if all components have been added; false if not.
429      * @return True if all component elements and materials have been added.
430      */
431     protected boolean isFilled()
432     {
433         return (_nComponents == _nComponentsMax);
434     }
435 
436     /** 
437      * Check that the massFractions list adds up to 1.0.
438      * @throw RuntimeException If mass fractions do not add to 1.0. 
439      */
440     private void checkMassSum()
441     {
442         double weightSum = 0;
443         for (int i = 0; i < _massFractions.size(); i++ )
444         {
445             weightSum += _massFractions.get(i);
446         }
447 
448         if (abs(1 - weightSum) > 0.001)
449         {
450             throw new RuntimeException("Mass fractions do not sum to 1 within 0.001 tolerance for this material: " + getName());
451         }
452     }
453 
454     /** 
455      * Compute the effective Z for this material using its element list. 
456      * @return The effective Z.
457      */
458     private double computeZeff()
459     {
460         double ZsumNotNorm = 0;
461         double atomCntFracSum = 0;
462 
463         int nelem = this.getNElements();
464         for (int i = 0; i < nelem; i++ )
465         {
466             MaterialElement me = this.getElements().get(i);
467             double massFrac = this.getMassFractions().get(i);
468             double atomCntFrac = massFrac / me.getA();
469             ZsumNotNorm += atomCntFrac * me.getZ();
470             atomCntFracSum += atomCntFrac;
471         }
472 
473         double ZsumNorm = ZsumNotNorm / atomCntFracSum;
474         _Zeff = ZsumNorm;
475         return _Zeff;
476     }
477 
478     /** 
479      * Compute the effective A for this material using its element list.
480      * @return The effective A. 
481      */
482     private double computeAeff()
483     {
484         double AsumNotNorm = 0;
485         double atomCntFracSum = 0;
486 
487         int nelem = this.getNElements();
488         for (int i = 0; i < nelem; i++ )
489         {
490             MaterialElement me = this.getElements().get(i);
491             double massFrac = this.getMassFractions().get(i);
492             double atomCntFrac = massFrac / me.getA();
493             AsumNotNorm += atomCntFrac * me.getA();
494             atomCntFracSum += atomCntFrac;
495         }
496         double ZsumNorm = AsumNotNorm / atomCntFracSum;
497         _Aeff = ZsumNorm;
498         return _Aeff;
499     }
500       
501     /**
502      * Compute the nuclear interaction length (lambda).
503      */
504     private void computeNuclearInteractionLength()
505     {
506         double NILinv = 0.0;
507 
508         for (int i = 0; i < _nElements; i++ )
509         {
510             MaterialElement me = _elements.get(i);
511             NILinv += _massFractions.get(i) / me.getNuclearInteractionLength();
512         }
513 
514         _nuclearInteractionLength = (NILinv <= 0.0 ? MAX_LAMBDA : 1.0 / NILinv);
515         _nuclearInteractionLengthWithDensity = _nuclearInteractionLength * _density;
516     }
517 
518     /** 
519      * Compute the radiation length (x0) based on mass fractions of elements. 
520      */
521     private void computeRadiationLength()
522     {
523         double rlinv = 0.0;
524 
525         for (int i = 0; i < _nElements; i++ )
526         {
527             MaterialElement me = _elements.get(i);
528             rlinv += _massFractions.get(i) / me.getRadiationLength();
529         }
530 
531         _radiationLength = (rlinv <= 0.0 ? MAX_X0 : 1.0 / rlinv);
532         _radiationLengthWithDensity = _radiationLength * _density;
533         
534         // NOTE Critical energy and moliere radius calcs left here for reference.
535         
536         // Set the critical energy
537         //double criticalEnergy = 2.66 * pow(_radiationLength * _Zeff / _Aeff, 1.1);
538 
539         // Set Moliere radius. What is magic number?
540         // _moliereRadius = _radiationLengthWithDensity * 21.2052 / criticalEnergy;
541         // _moliereRadius = 0.01 * rMoliere / _density;
542     }
543 
544     /**
545      * Computes and caches derived computed quantities after all components have been added to the material.
546      * This method is called in Material ctors.  For Materials with sub-components, it is called after
547      * all components are added.
548      */
549     private void computeDerivedQuantities()
550     {
551         computeZeff();
552         computeAeff();
553         computeRadiationLength();
554         computeNuclearInteractionLength();
555     }
556 
557     /**
558      * Get the A of this Material, which is a computed, effective A.
559      */
560     public double getA()
561     {
562         return _Aeff;
563     }
564         
565     /**
566      * Get the Z of this Material, which is a computed, effective Z.
567      */
568     public double getZ()
569     {
570         return _Zeff;
571     }
572 }