View Javadoc

1   /*
2     The TauP Toolkit: Flexible Seismic Travel-Time and Raypath Utilities.
3     Copyright (C) 1998-2000 University of South Carolina
4   
5     This program is free software; you can redistribute it and/or
6     modify it under the terms of the GNU General Public License
7     as published by the Free Software Foundation; either version 2
8     of the License, or (at your option) any later version.
9   
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14  
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18  
19    The current version can be found at 
20    <A HREF="www.seis.sc.edu">http://www.seis.sc.edu</A>
21  
22    Bug reports and comments should be directed to 
23    H. Philip Crotwell, crotwell@seis.sc.edu or
24    Tom Owens, owens@seis.sc.edu
25  
26  */
27  
28  /***
29   * package for storage and manipulation of seismic earth models.
30   *
31   */
32  package edu.sc.seis.TauP;
33  
34  
35  import java.io.BufferedInputStream;
36  import java.io.BufferedOutputStream;
37  import java.io.DataInputStream;
38  import java.io.DataOutputStream;
39  import java.io.FileInputStream;
40  import java.io.FileNotFoundException;
41  import java.io.FileOutputStream;
42  import java.io.FileReader;
43  import java.io.IOException;
44  import java.io.Serializable;
45  import java.io.StreamTokenizer;
46  import java.util.Vector;
47  
48  /***
49    * This class defines basic classes to store and manipulate
50    * a velocity model.
51    *
52    * @version 1.1.3 Wed Jul 18 15:00:35 GMT 2001
53  
54  
55  
56    * @author H. Philip Crotwell
57    */
58  public class VelocityModel 
59     implements Cloneable, Serializable
60  {
61        /*** name of the velocity model. */
62     protected String modelName = "unknown";
63  
64        /*** type of velocity file to be read in, either "nd" for named
65         *  discontinuities or "tvel" for ttimes style files. */
66     protected String fileType = "nd";
67  
68        /*** reference radius (km), usually radius of the earth, 
69         *  by default 6371 kilometers. */
70     protected double radiusOfEarth = 6371.0;   // kilometers
71  
72        /*** Depth (km) of the moho. It can be input from velocity model (*.nd) or
73         *  should be explicitly set. 
74         *  By default it is 35 kilometers (from Iasp91). For phase naming, the
75         *  tau model will choose the closest 1st order discontinuity. Thus
76         *  for most simple earth models these values are satisfactory. Take
77         *  proper care if your model has a thicker crust and a discontinuity
78         *  near 35 km depth. */
79     protected double mohoDepth = 35.0;   // kilometers
80  
81        /*** Depth (km) of the cmb (core mantle boundary). It can be input 
82         *  from velocity model (*.nd) or should be explicitly set. 
83         *  By default it is 2889 kilometers (from Iasp91). For phase naming, the
84         *  tau model will choose the closest 1st order discontinuity. Thus
85         *  for most simple earth models these values are satisfactory. */
86     protected double cmbDepth = 2889.0;   // kilometers
87  
88        /*** Depth (km) of the iocb (inner core outer core boundary).
89         *  It can be input from velocity model (*.nd) or
90         *  should be explicitly set. 
91         *  By default it is 5153.9 kilometers (from Iasp91). For phase naming,
92         *  the tau model will choose the closest 1st order discontinuity. Thus
93         *  for most simple earth models these values are satisfactory. */
94     protected double iocbDepth = 5153.9;   // kilometers
95  
96        /*** Not used, mean Density (kg/m^3), default 5517.0 */
97     protected double meanDensity = 5517.0;     // kg / m^3
98  
99        /*** Not used, gravitational constant, default 6.67e-11 m^3 / kg s^2 */
100    protected double G = 6.67e-11;             // m^3 / kg s^2
101 
102       /*** minimum radius of the model (km), default 0.0 */
103    protected double minRadius = 0.0;          // kilometers
104 
105       /*** maximum radius of the model (km), default 6371.0 */
106    protected double maxRadius = 6371.0;       // kilometers
107 
108       /*** is this a spherical model? Default is true. */
109    protected boolean spherical = true;
110 
111       /*** the initial length of the layer vector. */
112    protected static int vectorLength = 16;
113 
114       /*** expandable array to hold the layers
115        * @see java.util.Vector
116        * @see edu.sc.seis.TauP.VelocityLayer */
117    protected Vector layer = new Vector(vectorLength);
118 
119 /*----------------------------------------
120                 METHODS
121   ----------------------------------------*/
122 
123 // Accessor methods
124 
125       /*** get the model name. */
126    public String getModelName() {
127       return modelName;
128    }
129       /*** set the model name. */
130    public void setModelName(String modelName) {
131 		if (modelName.length() > 0) {
132 	      this.modelName = modelName;
133 		} else {
134 			this.modelName = "unknown";
135 		}
136    }
137 
138       /*** sets file type, either "nd" for named discontinuities or "tvel"
139        *  for ttimes tvel models. */
140    public void setFileType(String fileType) {
141       this.fileType = fileType;
142    }
143 
144       /*** sets radius of the earth (km), 
145        *  by default 6371 kilometers. */
146    public void setRadiusOfEarth(double radiusOfEarth) {
147       this.radiusOfEarth = radiusOfEarth;
148    }
149    
150       /*** gets radius of the earth (km), 
151        *  by default 6371 kilometers. */
152    public double getRadiusOfEarth() {
153       return radiusOfEarth;
154    }
155 
156 		/*** @returns the depths of discontinuities within the velocity model */
157 	public double[] getDisconDepths() {
158 		double[] disconDepths = new double[getNumLayers()+2];
159 		int numFound = 0;
160 		VelocityLayer aboveLayer, belowLayer;
161 		disconDepths[numFound++] = getVelocityLayer(0).getTopDepth();
162 		for (int layerNum = 0; layerNum < getNumLayers()-1; layerNum++) {
163 			aboveLayer = getVelocityLayer(layerNum);
164 			belowLayer = getVelocityLayer(layerNum+1);
165 			if (aboveLayer.getBotPVelocity() != belowLayer.getTopPVelocity() ||
166 	      aboveLayer.getBotSVelocity() != belowLayer.getTopSVelocity()) {
167 					// a discontinuity
168 				disconDepths[numFound++] = aboveLayer.getBotDepth();
169 			}
170 		}
171 		disconDepths[numFound++] = getVelocityLayer(getNumLayers()-1).getBotDepth();
172 		double[] temp = new double[numFound];
173 		System.arraycopy(disconDepths, 0, temp, 0, numFound);
174 		return temp;
175 	}
176 
177       /*** @returns depth (km) of the moho. It can be input from velocity model
178        *  (*.nd) or should be explicitly set. 
179        *  By default it is 35 kilometers (from Iasp91). For phase naming, the
180        *  tau model will choose the closest 1st order discontinuity. Thus
181        *  for most simple earth models these values are satisfactory. Take
182        *  proper care if your model has a thicker crust and a discontinuity
183        *  near 35 km depth. */
184    public double getMohoDepth() {
185       return mohoDepth;
186    }
187 
188    public void setMohoDepth(double mohoDepth) {
189       this.mohoDepth = mohoDepth;
190    }
191 
192       /*** @returns depth (km) of the cmb (core mantle boundary). It can be input
193        *  from velocity model (*.nd) or should be explicitly set. 
194        *  By default it is 2889 kilometers (from Iasp91). For phase naming, the
195        *  tau model will choose the closest 1st order discontinuity. Thus
196        *  for most simple earth models these values are satisfactory. */
197    public double getCmbDepth() {
198       return cmbDepth;
199    }
200 
201    public void setCmbDepth(double cmbDepth) {
202       this.cmbDepth = cmbDepth;
203    }
204 
205       /*** @returns the depth (km) of the iocb (inner core outer core boundary).
206        *  It can be input from velocity model (*.nd) or
207        *  should be explicitly set. 
208        *  By default it is 5153.9 kilometers (from Iasp91). For phase naming,
209        *  the tau model will choose the closest 1st order discontinuity. Thus
210        *  for most simple earth models these values are satisfactory. */
211    public double getIocbDepth() {
212       return iocbDepth;
213    }
214       
215    public void setIocbDepth(double iocbDepth) {
216       this.iocbDepth = iocbDepth;
217    }
218       
219    public double getMeanDensity() {
220       return meanDensity;
221    }
222    
223    public void setMeanDensity(double meanDensity) {
224       this.meanDensity = meanDensity;
225    }
226    
227    public double getMinRadius() {
228       return minRadius;
229    }
230    
231    public void setMinRadius(double minRadius) {
232       this.minRadius = minRadius;
233    }
234    
235    public double getMaxRadius() {
236       return maxRadius;
237    }
238    
239    public void setMaxRadius(double maxRadius) {
240       this.maxRadius = maxRadius;
241    }
242    
243    public double getG() {
244       return G;
245    }
246    
247    public void setG(double G) {
248       this.G = G;
249    }
250    
251    public boolean getSpherical() {
252       return spherical;
253    }
254    
255    public void setSpherical(boolean spherical) {
256       this.spherical = spherical;
257    }
258    
259    public VelocityLayer getVelocityLayerClone(int layerNum) {
260       return (VelocityLayer)((VelocityLayer)layer.elementAt(layerNum)).clone();
261    }
262       
263    public VelocityLayer getVelocityLayer(int layerNum) {
264       return (VelocityLayer)layer.elementAt(layerNum);
265    }
266 
267       /*** Returns the number of layers in this velocity model. */
268    public int getNumLayers() {
269       return layer.size();
270    }
271 
272 //normal methods
273 
274       /*** 
275        * Finds the layer containing the given depth. Note this returns the
276        * upper layer if the depth happens to be at a layer boundary.
277        *
278        * @return the layer number
279        * @exception NoSuchLayerException occurs if no layer contains the
280        *      given depth.
281        */
282    public int layerNumberAbove(double depth)
283       throws NoSuchLayerException
284    {
285       VelocityLayer tempLayer;
286 
287          /* first check to see if depth is at top of top layer. */
288       tempLayer = (VelocityLayer)getVelocityLayer(0);
289       if (depth == tempLayer.getTopDepth()) {
290          return 0;
291       } else {
292 			int tooSmallNum = 0;
293 	      int tooLargeNum = getNumLayers()-1;
294 	      int currentNum = 0;
295 	      boolean found = false;
296 
297 	      if (depth < tempLayer.getTopDepth() ||
298 	      getVelocityLayer(tooLargeNum).getBotDepth() < depth) {
299 	         throw new NoSuchLayerException(depth);
300 	      }
301 
302 	      while ( ! found) {
303 	         currentNum = Math.round((tooSmallNum + tooLargeNum) / 2.0f);
304 	         tempLayer = getVelocityLayer(currentNum);
305  
306 	         if (tempLayer.getTopDepth() >= depth) {
307 	            tooLargeNum = currentNum-1;
308 	         } else if (tempLayer.getBotDepth() < depth) {
309 	            tooSmallNum = currentNum+1;
310 	         } else {
311 	            found = true;
312 	         }
313 	      }
314 	      return currentNum;
315       }
316    }
317 
318  
319       /***
320        * Finds the layer containing the given depth. Note this returns the
321        * lower layer if the depth happens to be at a layer boundary.
322        *
323        * @return the layer number
324        * @exception NoSuchLayerException occurs if no layer contains the
325        *      given depth.
326        */
327    public int layerNumberBelow(double depth)
328       throws NoSuchLayerException
329    {
330       VelocityLayer tempLayer = getVelocityLayer(0);
331       int tooSmallNum = 0;
332       int tooLargeNum = getNumLayers()-1;
333       int currentNum = 0;
334       boolean found = false;
335  
336          /* first check to see if depth is at top of top layer. */
337       if (depth == tempLayer.getTopDepth()) {
338          return 0;
339       } else if (getVelocityLayer(tooLargeNum).getBotDepth() == depth) {
340             /* and check the bottommost layer. */
341          return tooLargeNum;
342       } else {
343  
344          if (depth < tempLayer.getTopDepth() ||
345          getVelocityLayer(tooLargeNum).getBotDepth() < depth) {
346             throw new NoSuchLayerException(depth);
347          }
348  
349          while ( ! found) {
350             currentNum = Math.round((tooSmallNum + tooLargeNum) / 2.0f);
351             tempLayer = getVelocityLayer(currentNum);
352  
353             if (tempLayer.getTopDepth() > depth) {
354                tooLargeNum = currentNum-1;
355             } else if (tempLayer.getBotDepth() <= depth) {
356                tooSmallNum = currentNum+1;
357             } else {
358                found = true;
359             }
360          }
361          return currentNum;
362       }
363    }
364 
365       /*** 
366        * returns the value of the given material property, usually 
367        * P or S velocity, at the given depth. Note this returns the
368        * value at the bottom of the upper layer if the depth happens
369        * to be at a layer boundary.
370        *
371        * @return the value of the given material property
372        * @exception NoSuchLayerException occurs if no layer contains the given
373        *                          depth.
374        * @exception NoSuchMatPropException occurs if the material
375        *                          property is not recognized.
376        */
377    public double evaluateAbove(double depth, char materialProperty)
378       throws NoSuchLayerException, NoSuchMatPropException
379    {
380       VelocityLayer tempLayer;
381       tempLayer = (VelocityLayer)getVelocityLayer(layerNumberAbove(depth));
382       return tempLayer.evaluateAt(depth, materialProperty);
383    }
384 
385       /*** 
386        * returns the value of the given material property, usually 
387        * P or S velocity, at the given depth. Note this returns the
388        * value at the top of the lower layer if the depth happens
389        * to be at a layer boundary.
390        *
391        * @return the value of the given material property
392        * @exception NoSuchLayerException occurs if no layer contains the given
393        *                          depth.
394        * @exception NoSuchMatPropException occurs if the material
395        *                          property is not recognized.
396        */
397    public double evaluateBelow(double depth, char materialProperty)
398       throws NoSuchLayerException, NoSuchMatPropException
399    {
400       VelocityLayer tempLayer;
401       tempLayer = (VelocityLayer)getVelocityLayer(layerNumberBelow(depth));
402       return tempLayer.evaluateAt(depth, materialProperty);
403    }
404 
405       /*** 
406        * returns the value of the given material property, usually 
407        * P or S velocity, at the top of the given layer.
408        * @return the value of the given material property
409        * @exception NoSuchMatPropException occurs if the material
410        *                          property is not recognized.
411        */
412    public double evaluateAtTop(int layerNumber, char materialProperty) 
413       throws NoSuchMatPropException
414    {
415       VelocityLayer tempLayer;
416 
417       tempLayer = (VelocityLayer)getVelocityLayer(layerNumber);
418       return tempLayer.evaluateAtTop(materialProperty);
419    }
420 
421       /*** 
422        * returns the value of the given material property, usually 
423        * P or S velocity, at the bottom of the given layer.
424        * @return the value of the given material property
425        * @exception NoSuchMatPropException occurs if the material
426        *                          property is not recognized.
427        */
428    public double evaluateAtBottom(int layerNumber, char materialProperty) 
429       throws NoSuchMatPropException
430    {
431       VelocityLayer tempLayer;
432 
433       tempLayer = (VelocityLayer)getVelocityLayer(layerNumber);
434       return tempLayer.evaluateAtBottom(materialProperty);
435    }
436 
437       /*** 
438        * returns the depth at the top of the given layer.
439        * @return the depth.
440        */
441    public double depthAtTop(int layerNumber) 
442    {
443       VelocityLayer tempLayer;
444 
445       tempLayer = (VelocityLayer)getVelocityLayer(layerNumber);
446       return tempLayer.getTopDepth();
447    }
448 
449       /*** 
450        * returns the depth at the bottom of the given layer.
451        * @return the depth.
452        * @exception NoSuchMatPropException occurs if the material
453        *                          property is not recognized.
454        */
455    public double depthAtBottom(int layerNumber) 
456       throws NoSuchMatPropException
457    {
458       VelocityLayer tempLayer;
459 
460       tempLayer = (VelocityLayer)getVelocityLayer(layerNumber);
461       return tempLayer.getBotDepth();
462    }
463 
464       /* replaces layers in the velocity model with new layers. The number
465        * of old and new layers need not be the same.
466        * @param matchTop false if the top should be a discontinuity, true
467        *    if the top velocity should be forced to match the existing
468        *    velocity at the top.
469        * @param matchBot similar for the bottom.
470        */
471    public void replaceLayers(VelocityLayer[] newLayers, boolean matchTop,
472       boolean matchBot) throws NoSuchLayerException
473    {
474 
475       int topLayerNum = layerNumberBelow(newLayers[0].getTopDepth());
476       VelocityLayer topLayer = getVelocityLayer(topLayerNum);
477       int botLayerNum = layerNumberAbove(newLayers[newLayers.length-1].getBotDepth());
478       VelocityLayer botLayer = getVelocityLayer(botLayerNum);
479 
480       if (matchTop) {
481          try {
482             newLayers[0].setTopPVelocity(topLayer.evaluateAt(
483                newLayers[0].getTopDepth(), 'P'));
484             newLayers[0].setTopSVelocity(topLayer.evaluateAt(
485                newLayers[0].getTopDepth(), 'S'));
486          } catch (NoSuchMatPropException e) {
487                // can't happen, but...
488             System.err.println("Caught NoSuchMatPropException: "+
489                e.getMessage());
490             e.printStackTrace();
491          }
492       }
493 
494       if (matchBot) {
495          try {
496             newLayers[newLayers.length-1].setBotPVelocity(botLayer.evaluateAt(newLayers[newLayers.length-1].getBotDepth(), 'P'));
497             newLayers[newLayers.length-1].setBotSVelocity(botLayer.evaluateAt(newLayers[newLayers.length-1].getBotDepth(), 'S'));
498          } catch (NoSuchMatPropException e) {
499                // can't happen, but...
500             System.err.println("Caught NoSuchMatPropException: "+
501                e.getMessage());
502 e.printStackTrace();
503          }
504       }
505 
506       if (topLayer.getBotDepth() > newLayers[0].getTopDepth()) {
507             /* need to split this layer. */
508          VelocityLayer newVLayer = (VelocityLayer)topLayer.clone();
509 
510          try {
511             topLayer.setBotPVelocity(topLayer.evaluateAt(
512                newLayers[0].getTopDepth(), 'P'));
513             topLayer.setBotSVelocity(topLayer.evaluateAt(
514                newLayers[0].getTopDepth(), 'S'));
515             topLayer.setBotDepth(newLayers[0].getTopDepth());
516          } catch (NoSuchMatPropException e) {
517                // can't happen, but...
518             System.err.println("Caught NoSuchMatPropException: "+
519                e.getMessage());
520 			e.printStackTrace();
521          }
522 
523          newVLayer.setTopPVelocity(topLayer.getBotPVelocity());
524          newVLayer.setTopSVelocity(topLayer.getBotSVelocity());
525          newVLayer.setTopDepth(topLayer.getBotDepth());
526          layer.insertElementAt(newVLayer, topLayerNum+1);
527          botLayerNum++;
528          topLayerNum++;
529       }
530       if (botLayer.getBotDepth() > newLayers[newLayers.length-1].getBotDepth()) {
531             /* need to split this layer. */
532          VelocityLayer newVLayer = (VelocityLayer)botLayer.clone();
533 
534          try {
535             botLayer.setBotPVelocity(botLayer.evaluateAt(
536                newLayers[newLayers.length-1].getBotDepth(), 'P'));
537             botLayer.setBotSVelocity(botLayer.evaluateAt(
538                newLayers[newLayers.length-1].getBotDepth(), 'S'));
539             botLayer.setBotDepth(newLayers[newLayers.length-1].getBotDepth());
540          } catch (NoSuchMatPropException e) {
541                // can't happen, but...
542             System.err.println("Caught NoSuchMatPropException: "+
543                e.getMessage());
544 			e.printStackTrace();
545          }
546  
547          newVLayer.setTopPVelocity(botLayer.getBotPVelocity());
548          newVLayer.setTopSVelocity(botLayer.getBotSVelocity());
549          newVLayer.setTopDepth(botLayer.getBotDepth());
550          layer.insertElementAt(newVLayer, botLayerNum+1);
551          botLayerNum++;
552       }
553 
554       for (int i=topLayerNum;i<=botLayerNum; i++) {
555          layer.removeElementAt(topLayerNum);
556       }
557       for (int i=0;i<newLayers.length;i++) {
558          layer.insertElementAt(newLayers[i], topLayerNum+i);
559       }
560       validate();
561    }
562 
563       /*** prints out the velocity model into a file in a form suitable for
564        *  plotting with GMT. */
565    public void printGMT(String filename) throws IOException {
566       DataOutputStream dos = new DataOutputStream(
567          new BufferedOutputStream(
568          new FileOutputStream(filename)));
569       printGMT(dos);
570       dos.close();
571    }
572 
573       /*** prints out the velocity model into a file in a for suitable for
574        *  plotting with GMT. */
575    public void printGMT(DataOutputStream dos) throws IOException {
576 
577       double depth = 0.0;
578       double pVel = -1.0;
579       double sVel = -1.0;
580       VelocityLayer currVelocityLayer;
581       
582       dos.writeBytes("> P velocity for "+modelName+"  below\n");
583       for (int layerNum=0;layerNum<getNumLayers();layerNum++) {
584          currVelocityLayer = getVelocityLayer(layerNum);
585          if (currVelocityLayer.getTopPVelocity() != pVel) {
586             dos.writeBytes((float)currVelocityLayer.getTopDepth()+" "+
587                (float)currVelocityLayer.getTopPVelocity()+"\n");
588          }
589          dos.writeBytes((float)currVelocityLayer.getBotDepth()+" "+
590             (float)currVelocityLayer.getBotPVelocity()+"\n");
591          pVel = currVelocityLayer.getBotPVelocity();
592       }
593       
594       dos.writeBytes("> S velocity for "+modelName+"  below\n");
595       for (int layerNum=0;layerNum<getNumLayers();layerNum++) {
596          currVelocityLayer = getVelocityLayer(layerNum);
597          if (currVelocityLayer.getTopSVelocity() != sVel) {
598             dos.writeBytes((float)currVelocityLayer.getTopDepth()+" "+
599                (float)currVelocityLayer.getTopSVelocity()+"\n");
600          }
601          dos.writeBytes((float)currVelocityLayer.getBotDepth()+" "+
602             (float)currVelocityLayer.getBotSVelocity()+"\n");
603          sVel = currVelocityLayer.getBotSVelocity();
604       }
605    }
606 
607       /***
608        * Performs internal consistency checks on the velocity model.
609        */
610    public boolean validate() {
611       VelocityLayer currVelocityLayer, prevVelocityLayer;
612 
613          /* is radiusOfEarth positive? */
614       if (radiusOfEarth <= 0.0) {
615          System.err.println(
616             "Radius of earth is not positive. radiusOfEarth = "+radiusOfEarth);
617          return false;
618       }
619          /* is mohoDepth non-negative? */
620       if (mohoDepth < 0.0) {
621          System.err.println(
622             "mohoDepth is not non-negative. mohoDepth = "+mohoDepth);
623          return false;
624       }
625          /* is cmbDepth >= mohoDepth? */
626       if (cmbDepth < mohoDepth) {
627          System.err.println(
628             "cmbDepth < mohoDepth. cmbDepth = "+cmbDepth+
629             " mohoDepth = "+mohoDepth);
630          return false;
631       }
632          /* is cmbDepth positive? */
633       if (cmbDepth <= 0.0) {
634          System.err.println(
635             "cmbDepth is not positive. cmbDepth = "+cmbDepth);
636          return false;
637       }
638          /* is iocbDepth >= cmbDepth? */
639       if (iocbDepth < cmbDepth) {
640          System.err.println(
641             "iocbDepth < cmbDepth. iocbDepth = "+iocbDepth+
642             " cmbDepth = "+cmbDepth);
643          return false;
644       }
645          /* is iocbDepth positive? */
646       if (iocbDepth <= 0.0) {
647          System.err.println(
648             "iocbDepth is not positive. iocbDepth = "+iocbDepth);
649          return false;
650       }
651          /* is minRadius non-negative? */
652       if (minRadius < 0.0) {
653          System.err.println(
654             "minRadius is not non-negative. minRadius = "+minRadius);
655          return false;
656       }
657          /* is maxRadius positive? */
658       if (maxRadius <= 0.0) {
659          System.err.println(
660             "maxRadius is not positive. maxRadius = "+maxRadius);
661          return false;
662       }
663          /* is maxRadius > minRadius? */
664       if (maxRadius <= minRadius) {
665          System.err.println(
666             "maxRadius <= minRadius. maxRadius = "+maxRadius+
667             " minRadius = "+minRadius);
668          return false;
669       }
670 
671       currVelocityLayer = getVelocityLayer(0);
672       prevVelocityLayer = new VelocityLayer();
673       prevVelocityLayer.setBotDepth(currVelocityLayer.getTopDepth());
674       prevVelocityLayer.setBotPVelocity(currVelocityLayer.getTopPVelocity());
675       prevVelocityLayer.setBotSVelocity(currVelocityLayer.getTopSVelocity());
676       prevVelocityLayer.setBotDensity(currVelocityLayer.getTopDensity());
677 
678       for (int layerNum=0;layerNum<getNumLayers();layerNum++) {
679          currVelocityLayer = getVelocityLayer(layerNum);
680 
681          if (prevVelocityLayer.getBotDepth() != currVelocityLayer.getTopDepth()) {
682             /*
683              * There is a gap in the velocity model!
684              */
685             System.err.println("There is a gap in the velocity model "+
686                "between layers "+(layerNum-1)+" and "+layerNum);
687             System.err.println("prevVelocityLayer="+prevVelocityLayer);
688             System.err.println("currVelocityLayer="+currVelocityLayer);
689             return false;
690          }
691          if (currVelocityLayer.getBotDepth() == currVelocityLayer.getTopDepth()) {
692             /*
693              * This layer has zero thickness.
694              */
695             System.err.println("There is a zero thickness layer in the "+
696                "velocity model at layer "+layerNum);
697             System.err.println("prevVelocityLayer="+prevVelocityLayer);
698             System.err.println("currVelocityLayer="+currVelocityLayer);
699             return false;
700          }
701          if (currVelocityLayer.getTopPVelocity() <= 0.0 ||
702              currVelocityLayer.getBotPVelocity() <= 0.0) {
703             /*
704              * This layer has a negative or zero P velocity.
705              */
706             System.err.println("There is a negative P velocity layer in the "+
707                "velocity model at layer "+layerNum);
708             return false;
709          }
710          if (currVelocityLayer.getTopSVelocity() < 0.0 ||
711              currVelocityLayer.getBotSVelocity() < 0.0) {
712             /*
713              * This layer has a negative S velocity.
714              */
715             System.err.println("There is a negative S velocity layer in the "+
716                "velocity model at layer "+layerNum);
717             return false;
718          }
719          if ((currVelocityLayer.getTopPVelocity() != 0.0 &&
720              currVelocityLayer.getBotPVelocity() == 0.0) || (
721              currVelocityLayer.getTopPVelocity() == 0.0 &&
722              currVelocityLayer.getBotPVelocity() != 0.0)) {
723             /*
724              * This layer goes to zero P velocity without a discontinuity.
725              */
726             System.err.println("There is a layer that goes to zero P velocity "+
727                "without a discontinuity in the "+
728                "velocity model at layer "+layerNum +
729                "\nThis would cause a divide by zero within this " +
730                "depth range. Try making the velocity small, followed by a " +
731                "discontinuity to zero velocity.");
732             return false;
733          }
734          if ((currVelocityLayer.getTopSVelocity() != 0.0 &&
735              currVelocityLayer.getBotSVelocity() == 0.0) || (
736              currVelocityLayer.getTopSVelocity() == 0.0 &&
737              currVelocityLayer.getBotSVelocity() != 0.0)) {
738             /*
739              * This layer goes to zero S velocity without a discontinuity.
740              */
741             System.err.println("There is a layer that goes to zero S velocity "+
742                "without a discontinuity in the "+
743                "velocity model at layer "+layerNum +
744                "\nThis would cause a divide by zero within this " +
745                "depth range. Try making the velocity small, followed by a " +
746                "discontinuity to zero velocity.");
747             return false;
748          }
749          prevVelocityLayer = currVelocityLayer;
750       }
751       return true;
752    }
753 
754    public String toString() {
755       String desc = "modelName="+modelName +"\n"+
756          "\n radiusOfEarth="+radiusOfEarth+
757          "\n mohoDepth="+mohoDepth+
758          "\n cmbDepth="+cmbDepth+
759          "\n iocbDepth="+iocbDepth+
760          "\n meanDensity="+meanDensity+
761          "\n G="+G+
762          "\n minRadius="+minRadius+
763          "\n maxRadius="+maxRadius+
764          "\n spherical="+spherical;
765 
766       desc += "\ngetNumLayers()="+getNumLayers() + "\n";
767       return desc;
768    }
769 
770    public Object clone() {
771       VelocityModel newObject;
772       try {
773          newObject = (VelocityModel)super.clone();
774          newObject.layer = new Vector(getNumLayers());
775          for (int i=0;i<getNumLayers();i++) {
776             newObject.layer.addElement(getVelocityLayerClone(i));
777          }
778          return newObject;
779 
780       } catch (CloneNotSupportedException e) {
781 	         // Cannot happen, we support clone
782 	         // and so do vectors.
783          throw new InternalError(e.toString());
784       }
785    }
786 
787    public void print() {
788       for (int i=0;i<getNumLayers();i++) {
789          System.out.println(getVelocityLayer(i));
790       }
791    }
792 
793       /***
794        * Reads in a velocity file. The type of file is determined by the
795        * fileType var.
796        * Calls readTVelFile or readNDFile.
797        * @exception VelocityModelException if the type of file cannot 
798        *    be determined.
799        */
800    public void readVelocityFile(String filename)
801       throws IOException, VelocityModelException
802    {
803 
804 
805       int j = filename.lastIndexOf(System.getProperty("file.separator"));
806       String modelFilename = filename.substring(j+1);
807 
808       if (modelFilename.endsWith("tvel")) {
809          modelName = modelFilename.substring(0,modelFilename.length() - 5);
810       } else if (modelFilename.endsWith(".nd")) {
811          modelName = modelFilename.substring(0,modelFilename.length() - 3);
812       } else if (modelFilename.startsWith("GB.")) {
813          modelName = modelFilename.substring(3,modelFilename.length());
814       } else {
815          modelName = modelFilename;
816       }
817       if (fileType.equalsIgnoreCase("nd")) {
818          readNDFile(filename);
819       } else if (fileType.equalsIgnoreCase("tvel")) {
820          readTVelFile(filename);
821       } else {
822          throw new VelocityModelException(
823             "What type of velocity file, .tvel or .nd?");
824       }
825 		boolean changeMade = fixDisconDepths();
826    }
827  
828 
829       /***
830        * Reads in a cubic spline file, the original format of the ttimes
831        * code. <em>not yet implemented since linear interpolation (.tvel)
832        * files are conceptually simpler. */
833    public void readCubicSplineFile(String filename, String lookForModelName) 
834       throws IOException {
835       System.err.println("readCubicSplineFile not yet implemented.");
836    }
837 
838       /*** 
839          This method reads in a velocity model from a "tvel" ASCII text file. 
840          The name of the model file for model "modelname" should be 
841          "modelname.tvel". The format of the file is:
842             comment line - generally info about the P velocity model
843             comment line - generally info about the S velocity model
844             depth    pVel sVel Density
845             depth    pVel sVel Density
846               .
847               .
848               .
849 
850          The velocities are assumed to be linear between sample points.
851          Because this type of model file doesn't give complete information
852          we make the following assumptions:
853             modelname     - from the filename, with ".tvel" dropped if present
854             radiusOfEarth - the largest depth in the model
855             meanDensity   - 5517.0
856             G             - 6.67e-11
857 
858          Also, because this method makes use of the string tokenizer, comments
859          are allowed. # as well as // signify that the rest of the line is a 
860          comment. C style slash-star comments are also allowed.
861       *
862       * @exception VelocityModelException occurs if an EOL should have been
863       *    read but wasn't. This may indicate a poorly formatted tvel file.
864       */
865    public void readTVelFile(String filename) 
866       throws IOException, VelocityModelException
867    {
868          FileReader fileIn = new FileReader(filename);
869          StreamTokenizer tokenIn = new StreamTokenizer(fileIn);
870 
871          tokenIn.commentChar('#');         // '#' means ignore to end of line
872          tokenIn.slashStarComments(true);  // '/*...*/' means a comment
873          tokenIn.slashSlashComments(true); // '//' means ignore to end of line
874          tokenIn.eolIsSignificant(true);   // end of line is important
875          tokenIn.parseNumbers();           /* Differentiate between words and
876                                               numbers. Note 1.1e3 is considered
877                                               a string instead of a number.
878                                             */
879    
880             /*  Read until we get 2 end of lines. */
881          while (tokenIn.nextToken() != StreamTokenizer.TT_EOL) {}
882          while (tokenIn.nextToken() != StreamTokenizer.TT_EOL) {}
883             /*
884              *  Now we have passed both comment lines and are ready to read
885              *  the velocity model.
886              */
887 
888             /* Some temporary variables to store the current line from the
889              * file and the current layer.
890              */
891          int myLayerNumber = 0;
892          VelocityLayer tempLayer = new VelocityLayer(myLayerNumber);
893          double depth, pVel, sVel, density;
894 
895             /* Preload the first line of the model */
896          tokenIn.nextToken();
897          depth = tokenIn.nval;
898          tokenIn.nextToken();
899          pVel = tokenIn.nval;
900          tokenIn.nextToken();
901          sVel = tokenIn.nval;
902          tokenIn.nextToken();
903          if (tokenIn.ttype != StreamTokenizer.TT_EOL) { 
904                // density is not used and so is optional
905             density = tokenIn.nval;
906             tokenIn.nextToken();
907          } else { density = 5571.0;}
908          if (tokenIn.ttype != StreamTokenizer.TT_EOL) { 
909                // this token should be an EOL, if not
910             throw new VelocityModelException(
911               "Should have found an EOL but didn't"+
912               " Layer="+myLayerNumber+
913               " tokenIn="+tokenIn);
914          } else {tokenIn.nextToken();}
915 
916          while (tokenIn.ttype != StreamTokenizer.TT_EOF) { 
917                // Loop until we hit the end of file
918 
919             tempLayer.setTopDepth(depth);
920             tempLayer.setTopPVelocity(pVel);
921             tempLayer.setTopSVelocity(sVel);
922             tempLayer.setTopDensity(density);
923            
924             tempLayer.setBotDepth(depth = tokenIn.nval);
925             tokenIn.nextToken();
926             tempLayer.setBotPVelocity(pVel = tokenIn.nval);
927             tokenIn.nextToken();
928             tempLayer.setBotSVelocity(sVel = tokenIn.nval);
929             tokenIn.nextToken();
930             if (tokenIn.ttype != StreamTokenizer.TT_EOL) { 
931                  // density is not used and is optional
932                tempLayer.setBotDensity(density = tokenIn.nval);
933                tokenIn.nextToken();
934             }
935             if (tokenIn.ttype != StreamTokenizer.TT_EOL) { 
936                  // this token should be an EOL, if not
937                throw new VelocityModelException(
938                  "Should have found an EOL but didn't"+
939                  " Layer="+myLayerNumber+
940                  " tokenIn="+tokenIn);
941             } else {tokenIn.nextToken();}
942             if (tempLayer.getTopDepth() != tempLayer.getBotDepth()) {
943                  /*
944                   * Don't use zero thickness layers, first order discontinuities
945                   * are taken care of by storing top and bottom depths.
946                   */
947                layer.addElement(tempLayer);
948                myLayerNumber++;
949                tempLayer = new VelocityLayer(myLayerNumber);
950             }
951          }
952          radiusOfEarth = depth;
953          maxRadius = depth;    // I assume that this is a whole earth model
954                                // so the maximum depth is equal to the 
955                                // maximum radius is equal to the earth radius.
956    }
957 
958 
959       /*** 
960        * This method reads in a velocity model from a "nd" ASCII text file,
961        * the format used by Xgbm. 
962        * The name of the model file for model "modelname" should be 
963        * "modelname.nd". The format of the file is:
964        *    depth    pVel sVel Density Qp Qs
965        *    depth    pVel sVel Density Qp Qs
966        *      .
967        *      .
968        *      .
969        * with each major boundary separated with a line with "mantle",
970        * "outer-core" or "inner-core". This feature makes phase interpretation
971        * much easier to code. Also, as they are not needed for travel time
972        * calculations, the density, Qp and Qs may be omitted.
973        *
974        * The velocities are assumed to be linear between sample points.
975        * Because this type of model file doesn't give complete information
976        * we make the following assumptions:
977        *    modelname     - from the filename, with ".nd" dropped, if present
978        *    radiusOfEarth - the largest depth in the model
979        *
980        * Also, because this method makes use of the string tokenizer, comments
981        * are allowed. # as well as // signify that the rest of the line is a 
982        * comment. C style slash-star comments are also allowed.
983        *
984        * @exception VelocityModelException occurs if an EOL should have been
985        *    read but wasn't. This may indicate a poorly formatted model file.
986        */
987    public void readNDFile(String filename) 
988       throws IOException, VelocityModelException
989    {
990          FileReader fileIn = new FileReader(filename);
991          StreamTokenizer tokenIn = new StreamTokenizer(fileIn);
992 
993          tokenIn.commentChar('#');         // '#' means ignore to end of line
994          tokenIn.slashStarComments(true);  // '/*...*/' means a comment
995          tokenIn.slashSlashComments(true); // '//' means ignore to end of line
996          tokenIn.eolIsSignificant(true);   // end of line is important
997          tokenIn.parseNumbers();           /* Differentiate between words and
998                                               numbers. Note 1.1e3 is considered
999                                               a string instead of a number.
1000                                             */
1001    
1002             /* Some temporary variables to store the current line from the
1003              * file and the current layer.
1004              */
1005          int myLayerNumber = 0;
1006          VelocityLayer tempLayer = new VelocityLayer(myLayerNumber);
1007          double depth, pVel, sVel, density=2.6, qp=1000, qs=2000;
1008 
1009             /* Preload the first line of the model */
1010          tokenIn.nextToken();
1011          depth = tokenIn.nval;
1012          tokenIn.nextToken();
1013          pVel = tokenIn.nval;
1014          tokenIn.nextToken();
1015          sVel = tokenIn.nval;
1016          tokenIn.nextToken();
1017          if (tokenIn.ttype != StreamTokenizer.TT_EOL) { 
1018                // density is not used and so is optional
1019             density = tokenIn.nval;
1020             tokenIn.nextToken();
1021             if (tokenIn.ttype != StreamTokenizer.TT_EOL) { 
1022                   // Qp is not used and so is optional
1023                qp = tokenIn.nval;
1024                tokenIn.nextToken();
1025                if (tokenIn.ttype != StreamTokenizer.TT_EOL) { 
1026                      // Qs is not used and so is optional
1027                   qs = tokenIn.nval;
1028                   tokenIn.nextToken();
1029                }
1030             }
1031          }
1032          if (tokenIn.ttype != StreamTokenizer.TT_EOL) { 
1033                // this token should be an EOL, if not
1034             throw new VelocityModelException(
1035               "Should have found an EOL but didn't"+
1036               " Layer="+myLayerNumber+
1037               " tokenIn="+tokenIn);
1038          } else {tokenIn.nextToken();}
1039 
1040          while (tokenIn.ttype != StreamTokenizer.TT_EOF) { 
1041                // Loop until we hit the end of file
1042 
1043             if (tokenIn.ttype == StreamTokenizer.TT_WORD) {
1044                if (tokenIn.sval.equalsIgnoreCase("mantle")) {
1045                   mohoDepth = depth;  // Moho
1046                }
1047                if (tokenIn.sval.equalsIgnoreCase("outer-core")) {
1048                   cmbDepth = depth;  // Core Mantle Boundary
1049                }
1050                if (tokenIn.sval.equalsIgnoreCase("inner-core")) {
1051                   iocbDepth = depth;  // Inner Outer Core Boundary
1052                }
1053                while (tokenIn.ttype != StreamTokenizer.TT_EOL) {
1054                   tokenIn.nextToken();
1055                }
1056                tokenIn.nextToken();
1057             }
1058             tempLayer.setTopDepth(depth);
1059             tempLayer.setTopPVelocity(pVel);
1060             tempLayer.setTopSVelocity(sVel);
1061             tempLayer.setTopDensity(density);
1062             tempLayer.setTopQp(qp);
1063             tempLayer.setTopQs(qs);
1064            
1065             tempLayer.setBotDepth(depth = tokenIn.nval);
1066             tokenIn.nextToken();
1067             tempLayer.setBotPVelocity(pVel = tokenIn.nval);
1068             tokenIn.nextToken();
1069             tempLayer.setBotSVelocity(sVel = tokenIn.nval);
1070             tokenIn.nextToken();
1071 
1072             if (tokenIn.ttype != StreamTokenizer.TT_EOL) {
1073                   // density is not used and so is optional
1074                tempLayer.setBotDensity(density = tokenIn.nval);
1075                tokenIn.nextToken();
1076                if (tokenIn.ttype != StreamTokenizer.TT_EOL) {
1077                      // Qp is not used and so is optional
1078                   tempLayer.setBotQp(qp = tokenIn.nval);
1079                   tokenIn.nextToken();
1080                   if (tokenIn.ttype != StreamTokenizer.TT_EOL) {
1081                         // Qs is not used and so is optional
1082                      tempLayer.setBotQs(qs = tokenIn.nval);
1083                      tokenIn.nextToken();
1084                   }
1085                }
1086             }
1087             if (tokenIn.ttype != StreamTokenizer.TT_EOL) { 
1088                  // this token should be an EOL, if not
1089                throw new VelocityModelException(
1090                  "Should have found an EOL but didn't"+
1091                  " Layer="+myLayerNumber+
1092                  " tokenIn="+tokenIn);
1093             } else {tokenIn.nextToken();}
1094             if (tempLayer.getTopDepth() != tempLayer.getBotDepth()) {
1095                  /*
1096                   * Don't use zero thickness layers, first order discontinuities
1097                   * are taken care of by storing top and bottom depths.
1098                   */
1099                layer.addElement(tempLayer);
1100                myLayerNumber++;
1101                tempLayer = new VelocityLayer(myLayerNumber);
1102             }
1103          }
1104          radiusOfEarth = depth;
1105          maxRadius = depth;    // I assume that this is a whole earth model
1106                                // so the maximum depth is equal to the 
1107                                // maximum radius is equal to the earth radius.
1108    }
1109 
1110 	/*** resets depths of major discontinuities to match those existing in the
1111 	 *  input velocity model. The initial values are set such that if there
1112 	 *  is no discontinuity within the top 100 km then the moho is set to 0.0.
1113 	 *  Similarly, if there are no discontinuities at al then the cmb is set
1114 	 *  to the radius of the earth.  Similarly for the iocb, except it must
1115 	 *  be a fluid to solid boundary and deeper than 100km to avoid problems
1116 	 *  with shallower fluid layers, eg oceans.
1117 	 */
1118 	public boolean fixDisconDepths() {
1119 		boolean changeMade = false;
1120 		VelocityLayer aboveLayer, belowLayer;
1121 		double mohoMin = 65.0, cmbMin=radiusOfEarth, iocbMin=radiusOfEarth-100.0;
1122 		double tempMohoDepth=0.0, tempCmbDepth=radiusOfEarth, 
1123 			tempIocbDepth=radiusOfEarth;
1124 
1125 		for (int layerNum = 0; layerNum < getNumLayers()-1; layerNum++) {
1126 			aboveLayer = getVelocityLayer(layerNum);
1127 			belowLayer = getVelocityLayer(layerNum+1);
1128 			if (aboveLayer.getBotPVelocity() != belowLayer.getTopPVelocity() ||
1129 	      aboveLayer.getBotSVelocity() != belowLayer.getTopSVelocity()) {
1130 					// a discontinuity
1131 				if (Math.abs(mohoDepth - aboveLayer.getBotDepth()) < mohoMin) {
1132 					tempMohoDepth = aboveLayer.getBotDepth();
1133 					mohoMin = Math.abs(mohoDepth - aboveLayer.getBotDepth());
1134 				}
1135 				if (Math.abs(cmbDepth - aboveLayer.getBotDepth()) < cmbMin) {
1136 					tempCmbDepth = aboveLayer.getBotDepth();
1137 					cmbMin = Math.abs(cmbDepth - aboveLayer.getBotDepth());
1138 				}
1139 				if (aboveLayer.getBotSVelocity() == 0.0 &&
1140 				belowLayer.getTopSVelocity() > 0.0 &&
1141 				Math.abs(iocbDepth - aboveLayer.getBotDepth()) < iocbMin) {
1142 					tempIocbDepth = aboveLayer.getBotDepth();
1143 					iocbMin = Math.abs(iocbDepth - aboveLayer.getBotDepth());
1144 				}
1145 			}
1146 		}
1147 		if (mohoDepth != tempMohoDepth || cmbDepth != tempCmbDepth ||
1148 		iocbDepth != tempIocbDepth) {
1149 			changeMade = true;
1150 		}
1151 		mohoDepth = tempMohoDepth;
1152 		cmbDepth = tempCmbDepth;
1153 		iocbDepth = (tempCmbDepth !=tempIocbDepth ? tempIocbDepth: radiusOfEarth);
1154 		return changeMade;
1155 	}
1156 
1157    /***
1158     * Returns a flat velocity model object equivalent to the spherical velocity
1159     * model via the earth flattening transform.
1160     * 
1161     * @return the flattened VelocityModel object.
1162     * @exception VelocityModelException
1163     *                occurs ???.
1164     */
1165     public VelocityModel earthFlattenTransform() throws VelocityModelException {
1166         VelocityModel flatModel;
1167         VelocityLayer newLayer, oldLayer;
1168         flatModel = (VelocityModel)this.clone();
1169         flatModel.spherical = false;
1170         flatModel.layer = new Vector(vectorLength);
1171         for(int i = 0; i < getNumLayers(); i++) {
1172             oldLayer = getVelocityLayer(i);
1173             newLayer = new VelocityLayer(i,
1174                                          radiusOfEarth
1175                                                  * Math.log(oldLayer.getTopDepth()
1176                                                          / radiusOfEarth),
1177                                          radiusOfEarth
1178                                                  * Math.log(oldLayer.getBotDepth()
1179                                                          / radiusOfEarth),
1180                                          radiusOfEarth
1181                                                  * oldLayer.getTopPVelocity()
1182                                                  / oldLayer.getTopDepth(),
1183                                          radiusOfEarth
1184                                                  * oldLayer.getBotPVelocity()
1185                                                  / oldLayer.getBotDepth(),
1186                                          radiusOfEarth
1187                                                  * oldLayer.getTopSVelocity()
1188                                                  / oldLayer.getTopDepth(),
1189                                          radiusOfEarth
1190                                                  * oldLayer.getBotSVelocity()
1191                                                  / oldLayer.getBotDepth());
1192             flatModel.layer.addElement(newLayer);
1193         }
1194         return flatModel;
1195     }
1196    
1197       /*** Just for debugging purposes. */
1198    public static void main(String[] args) {
1199       VelocityModel vMod = new VelocityModel();
1200       String modelFilename;
1201       if (args.length >= 1) {
1202          modelFilename = args[0];
1203       } else {
1204          modelFilename = "iasp91.tvel";
1205       }
1206       boolean DEBUG = true;
1207  
1208       try {
1209          vMod.readVelocityFile(modelFilename);
1210  
1211          System.out.println("Done reading.");
1212          if (!vMod.validate()) {
1213             System.out.println("FAILED VELOCITY MODEL VALIDATION!");
1214          }
1215          vMod.printGMT(vMod.modelName+".gmt");
1216       } catch (IOException e) {
1217          System.out.println("Tried to read!\n Caught IOException "
1218                             + e.getMessage());
1219       } catch (VelocityModelException e) {
1220          System.out.println("Tried to read!\n Caught VelocityModelException "
1221                             + e.getMessage());
1222          e.printStackTrace();
1223       } finally {
1224          System.out.println("Done!\n");
1225       }
1226    }
1227 }