List 1

List 1


/*
 * Heat flow simulation model of multiple terrestrial geological columns
 * Single 500 m ultrafine snow layer at 47.5 degrees N 
 *
 * Source file: Glaciation201.java
 *
 * author: C F Davis 
 * version: 05 Oct 2019
 */
package glaciation;


import java.awt.*;
import java.util.*;
import java.awt.event.*;
import java.lang.Math;
import java.text.SimpleDateFormat;
import javax.swing.JTable;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.lang.Object;


public class Glaciation extends java.applet.Applet implements Runnable {
    private Thread testThread = null;
    EventHandler eventHandler;
    int count, xmax, ymax;
    Image offImage;
    Graphics offGraphics;
    Color colorpalette[] = new Color[15];
    int xpoints[] = new int[4];
    int ypoints[] = new int[4];
    int x1, x2, y1, y2, paletteindex;
    int p1, p2, p3, p4;
    boolean go = true;
    ItemGraphCanvas canvas1;
    Image fieldImage;
    Graphics fieldGraphics;
    LayerDisplay layerDisplay;
    Image layerImage;
    Graphics layerGraphics;
    Image textImage;
    Graphics textGraphics;
    int layerMargin;
    boolean printManager = false;


    int mode = 1;
    int runmode = 1;
    boolean clearScreen = true;

    double timeOfDay = 0.0;                 // time of day 0 - 23
    double hoursPerDay = 10.0;
    double hourstimestep = 0.5;            // time step interval (hrs)
    int day = 0;
    int outcount = 0;
//  double conductionHeatGain = 0.0;
//  double subtemp[][] = new double[2][500]; // subsurface temperatures
    double tcount, tsum, tmean;
    int selector = 0;
    int opcontrol = 2;

    // asteroid additions
//  GeologicalColumn asteroid;
    PseudoMap map;
    GeologicalColumn column[] =  new GeologicalColumn[20];
    double secondsPerYear = 31540000.0;
    double scale;
    double screenWidth = 400;
    Dimension appletSize;
    double year, century, millennium;
    long epochCZYN = 66043000;            // nominal current epoch  
    long currentCZYN = 66043000;     // Cenozoic Tear Bumber 0 is 66 million years before 1 Jan 2000 (Chicxulub crater)
    long eemianCZYN = 66043000 - 100000;  // Emian interglacial
    long longCZYN = 66043000;
    long startCZYN = eemianCZYN;
    double initialRadius = 6371000;
    double maxRadius = 6371000;
    double initialLayerHeight = 17;
    double fractionGranite = 1.0;
    double heatGranite = 1E-9;
    long tick = 0;
    long longPeriodIndex = 0;
    double previousGlaciationTotal = 0;
    double glaciationTotal = 0;         // counter
    double glaciationPeriod = 0;        // counter
    double glaciationMaxPeriod = 1000;  // period over which to measure glaciation
    double elapsedSecs;
    double glaciationPercent = 0;       // % glaciation over glaciationMaxPeriod
    boolean rollingGraphUpdated = false;
    int rollingGraphWidth;
    int rollingGraphHeight;
    int rollingGraphStreams;
    double longDay = 1.0 / 365.0;           // there are 365 longDays in a year, 365.25 days in a year
    int timeStepLongDays =   1;             // timestep is an integer number of long days : valid numbers 1. 5, 73
//  double timeStepYears = 1.0 / 365.25;
    double timeStepYears = timeStepLongDays * (double)longDay;
//  String csvFilename = "IcedHotAsteroid.csv";
//  String csvFilename = "Earth1al5.csv";
///    String csvFilename = "Earth_20pcCC_45c.hcm";
//  String csvFilename = "Ground1.csv"; 
//  String csvFilename = "Earth_20cc_solar2_80.csv";
//  String csvFilename = "E9.hcm";
//  String csvFilename = "Earth_double_air_mass.hcm";
    String csvFilename = "y120k.hcm";
//  String csvFilename = "y65million.hcm";
    double stime;
    int degreesPerLatitude = 5;
    int nLatitudes = 90 / degreesPerLatitude;
    double viewLatitude = 47.5;
    int nviewLatitude;
    int solar = 2;
    Milankovitch mil;
    boolean milankovitch = false;
//  double cloudCover = 0.20;               // http://www.climate4you.com/ClimateAndClouds.htm
    double cloudCover = 0.20;             
    boolean waterRunoff =  true;
    int icedropCounter = 0;                 // countdown starts when ice is dropped on surface
    int icedropLayer = 67;                  // layer temperature to record after ice drop
    double pauseDate =   449000;
    boolean constantAddLayerDepther = false; // maintain added ice/snow layer depth 
    double addLayerMaterial =  3.05;        // added layer material 
//  double addLayerMaterial =  11.0;        // added layer material 
    double addLayerDepth = 20.0;            // added layer depth
    boolean relativeTemperatures = false;   // relative temperature = current temperature - initial temperature
    boolean heatEntireLayers =  true;       // true = changee layer pseudotemperature during phase change, false to melt off water
    boolean varyIceCharacter = false;       // true to vary ice conductivity with temperature
    boolean varyAirEmissivity = false;
    
    double addedIceMass = 0;
    double addedIceHeatContentPerKg = 0;
    double H20LayerHeatContent;
    double H20LayerMassxSpht;;
    double H20LayerMassxLatht;
    double H20LayerHeatAdded;
    double tempvar1 = 88.0;         // any value other than 3.0 to activate old phase handling code
    double  snowHeatAdded = 0;
    boolean albedoTest = false;     // albedo test stepss thin surface layer albedo from 0 to 1 
    double meanGlobalTemperatureK;
    double usefulVariable1 =  0;          // variable that can be used in any way desired
    double usefulVariable2 =  0;          // variable that can be used in any way desired
    DailyValues ocean[] = new DailyValues[ 2 * nLatitudes ];          // pre-calculated ocean daily air temperatures 36 latitudes
    DailyValues land[] = new DailyValues[ 2 * nLatitudes ];           // pre-calculated ocean daily air temperatures 36 latitudes
    DailyValues snow[] = new DailyValues[ 2 * nLatitudes ];           // pre-calculated ocean daily air temperatures 36 latitudes
    DailyValues combined[] = new DailyValues[ 2 * nLatitudes ];       // combined daily air temperatures 36 latitudes
    DailyValues active[] = new DailyValues[ 2 * nLatitudes ];         // active daily air temperatures used in surface temperature calculation
    DailyValues global = new DailyValues();                           // pre-calculated global daily mean air temperature
    int nStartLatitude = 0;
    int nStopLatitude = 17;
    double annualGlobalMeanTemperature;
    long suspendCalculationDate = 7000000;                            // debug aid: when not in use use value of 1000 million
    boolean snowing = false;
    long delayedStart = 12000;
    int rollingMaxValue, rollingMinValue, rollingCalendarStep, rollingCalendarDisplayInterval;
    double topLayerDepth = 40000;
    int nband[] = new int[10];
    boolean removeFlag =  false;
    boolean flag1 = false;
    
    
    int videoWindow[][] = {
        {   360,  240, },
        {   480,  360, },
        {   640,  480, },
        {  1280,  720, },
        {  1920, 1080, }
    };
    

    public void init() {
        stime = System.currentTimeMillis();
        this.enableEvents( AWTEvent.MOUSE_EVENT_MASK );

        double var =  0.4  / ( 10E3 * this.secondsPerYear);
        System.out.println( "var " + var );

        MaterialPhase.phase(  500, 100e3 );

//      setSize( (int)screenWidth, (int)screenWidth );
        int nScreen = 2;
        screenWidth = videoWindow[nScreen][0];
        setSize( videoWindow[nScreen][0], videoWindow[nScreen][1] );
        appletSize = this.getSize();
        xmax = appletSize.width;         // graphics width
        ymax = appletSize.height;        // graphics height

        setBackground(Color.white);
        count=0;
        year = 0;
        century = 0;
        millennium = 0;
        nviewLatitude = (int)( ( this.viewLatitude - 2.5 ) / 5.0 );
//      nStartLatitude = nviewLatitude;
//      nStopLatitude = nviewLatitude;
        System.out.println( "nviewLatitude " + nviewLatitude );

        scale = videoWindow[nScreen][0] / ( maxRadius * 2.5 );
        offImage = createImage( videoWindow[nScreen][0], videoWindow[nScreen][0] );
        offGraphics = offImage.getGraphics();

        layerDisplay = new LayerDisplay( this );

        // set up textImage for text display
        textImage = createImage( ( 3 * videoWindow[nScreen][0] / 4 ), ( videoWindow[nScreen][0] / 2 ) );
        textGraphics = textImage.getGraphics();
        
        map =  new PseudoMap();
        for ( int n=0; n<nLatitudes*2; n++ ) {
            ocean[n] = new DailyValues();
            land[n] = new DailyValues();
            snow[n] = new DailyValues();
            combined[n] = new DailyValues();
            active[n] = new DailyValues();
        }
        // glaciate pseudoMap above viewLatitude
        map.glaciateLandPseudoMap( this.nviewLatitude );
        map.printPseudoMap();
        
        // layer display setup
        layerMargin = videoWindow[nScreen][0] / 4;
        layerImage = createImage( layerMargin, videoWindow[nScreen][0] );
        layerGraphics = layerImage.getGraphics();
        layerDisplay.setDisplaySize( layerMargin, videoWindow[nScreen][1] );
        System.out.println( "layerMargin " + layerMargin );

        // rolling graph display setup
        rollingGraphWidth = videoWindow[nScreen][0] * 3 / 4;
        rollingGraphHeight = videoWindow[nScreen][1] / 2;
        canvas1 = new ItemGraphCanvas( this, offGraphics, rollingGraphWidth, rollingGraphHeight, 5,  true );
        fieldImage = createImage( rollingGraphWidth, rollingGraphHeight );
        fieldGraphics = fieldImage.getGraphics();
        fieldGraphics.setColor(Color.white);
        fieldGraphics.fillRect(0, 0, rollingGraphWidth, rollingGraphHeight );
        canvas1.calendarLabel = "e5";
        canvas1.calendarStep = 1000;                 // years per pixel
        


        /* get displaymode */
        try {
          mode =  Integer.parseInt( getParameter("MODE" ) );
        } catch( Exception e ) {
          mode = 1;
        }

        colorpalette[0] = new Color(000, 000, 000);     // black
        colorpalette[1] = new Color( 64,  64,  64);     // light grey
        colorpalette[2] = new Color(128, 128, 128);     // mid gray
        colorpalette[3] = new Color(192, 192, 192);     // darke gray
        colorpalette[4] = new Color(255, 255, 255);     // white
        colorpalette[5] = Color.RED;
        colorpalette[6] = Color.ORANGE;
        colorpalette[7] = Color.YELLOW;
        colorpalette[8] = Color.CYAN;
        colorpalette[9] = new Color( 85,  52,  52);     // brown?
        colorpalette[10] = new Color(135,206, 250);     // sky blue
        colorpalette[11] = new Color(240,240, 240);     // steam
        colorpalette[12] = new Color( 0,  0,  255);     // blue
        colorpalette[13] = new Color( 0,  0,  255);     // blue
        colorpalette[14] = new Color( 0,  0,  255);     // blue

//      asteroid = new GeologicalColumn( this, csvFilename, viewLatitude, 0 );
//      asteroid = new GeologicalColumn( this, 0 );
//      asteroid.setRadioactiveHeatGeneration( 2, mantle( 6371000, 3400000, 4650, 9990, 20E12 ) );

        for ( int c=0; c<=17; c++ ) {
            column[c] = new GeologicalColumn( this, csvFilename, (c*5)+2.5, 1  );
            column[c].columnActiveCalculation = true;
        }
        
//      System.out.println( "Terrain albedo " +  column[ nviewLatitude ].terrainAlbedo( 500, 1000, 0.4, 200, 0.8 ) );

        if ( timeStepLongDays > 1 ) preliminaries();        
     
        // initialise graphics
        this.pauseDate = 450000;
        this.rollingMaxValue = 500;
        this.rollingMinValue = 0;
        this.rollingCalendarStep = 1000;
        canvas1.calendarStep = this.rollingCalendarStep; 
        this.rollingCalendarDisplayInterval =  100000;
        canvas1.calendarDisplayInterval = this.rollingCalendarDisplayInterval; 
        canvas1.calendarLabel = "e5";
        this.topLayerDepth = 200000;
        layerDisplay.topLayerDepth = this.topLayerDepth;
        
        // initialise geological columns for runtime
        this.timeStepLongDays = 1;
        setTimeStepYears( this.timeStepLongDays );
        this.viewLatitude = 47.5;
        this.nviewLatitude = (int)( ( this.viewLatitude - 2.5 ) / 5.0 );        
        nStartLatitude = this.nviewLatitude;
        nStopLatitude = this.nviewLatitude;
        for ( int c=0; c<=17; c++ ) {
            column[c] = new GeologicalColumn( this, csvFilename, (c*5)+2.5, 1  );
            if ( c >= nStartLatitude && c <= nStopLatitude ) {   
                column[c].columnActiveCalculation = true;
            } else {
                column[c].columnActiveCalculation = false;                
            }
            
            // explicitly initialise experiment variables
            column[c].airMixingFactor = 0;
            column[c].snowStartCZYN = this.startCZYN + 12000;    // start snow after 12000 years
            column[c].snowStopCZYN = this.startCZYN +  12240;
            column[c].snowType = 3.05;
            column[c].snowLayerThickness = 500;
            column[c].snowDepositionInterval = 350;
            column[c].terrainHeight = 0;
            column[c].terrainWidth = 600;
            
            // surface rock bands for rollingGraph
            nband[0] = column[c].surfaceRockBand;
            nband[1] = nband[0] - 1;
            nband[2] = nband[1] - 1;
        }

        System.out.println( "nviewLatitude " + nviewLatitude );        

        eventHandler = new EventHandler( this );
        eventHandler.insertEvent( eemianCZYN );
        eventHandler.insertEvent( eemianCZYN + 1007 );
        eventHandler.insertEvent( eemianCZYN + 15018 );
        currentCZYN = eventHandler.eventQueue[0];
        eventHandler.printEventQueue();
        System.out.println( (long)(eemianCZYN + 15018) );

//      milankovitchTest();
//      mil = new Milankovitch( "INSOLN.LA2004.BTL.100.ASC" );

        column[ nviewLatitude ].setRadioactiveHeatGeneration( 2, 2.1773010891400347E-8 );

//      double r1 = 100.0;
//      double r2 = 110.0;
//      double v = column[ nviewLatitude ].sphereVolume(r2) - column[ nviewLatitude ].sphereVolume(r1);
//      System.out.println( "r2 = " + column[ nviewLatitude ].layerRadius( v, column[ nviewLatitude ].sphereVolume(r1) ) );

        tsum = 0.0; tcount = 0.0;
        year = 0;

        mil = new Milankovitch( this, "milankovitch250kyrs.csv", this.epochCZYN, 1000 );

        
    }
    
    void setTimeStepYears( int longDays ) {
        if ( longDays==1 || longDays==5 || longDays==73 ) {
            timeStepLongDays = longDays;
            timeStepYears = timeStepLongDays * (double)longDay;
            System.out.println( "timeStepYears " + timeStepYears );
        }
    }

    public void start() {
        if (testThread == null) {
            testThread = new Thread(this, "Test1");
            testThread.start();
        }
    }


    public void processMouseEvent( MouseEvent e) {

      if ( e.getID() == MouseEvent.MOUSE_ENTERED ) { go =  false; }
      else if ( e.getID() == MouseEvent.MOUSE_EXITED ) { go =  true; }
      else if ( e.getID() == MouseEvent.MOUSE_RELEASED ) {
        offGraphics.setPaintMode();
        offGraphics.setColor( Color.white );
        offGraphics.fillRect( 0, 0, xmax, ymax );
        x2 = e.getX();
        y2 = e.getY();
        p1 = 1 + x2 % 5;
        p2 = 1 + y2 % 5;
        p3 = 1 + Math.abs( x2-y2 ) % 5;
        p4 = 1 + (x2 + y2) % 6;
        go =  true;
      }
      else super.processMouseEvent(e);
//    System.out.println("The line number is " + new Exception().getStackTrace()[0].getLineNumber());
      Object o = this;
      Class c = o.getClass();
//    System.out.println("class name is: " + c.getName());
//    System.out.println("method name is: " + new Exception().getStackTrace()[0].getMethodName());
//    System.out.println("calling method name is: " +  Thread.currentThread().getStackTrace()[2].getMethodName());
//    System.out.println("The class name is " + java.lang.Class.getSimpleName() );

    }


    public void run() {
        runmode = mode;
        Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
        Thread myThread = Thread.currentThread();
        
//      preliminaries();
        
        while (testThread == myThread) {
            if ( go ) {

//              if ( this.year > 15000 && this.year < 15001 ) this.setTimeStepYears( 5 );
                mathEngine();

                if ( opcontrol == 2 ) {
//                 clearScreen = true;
//                 repaint();
                }
                
//              if (  (currentCZYN - eemianCZYN) > 6.5942E7  ) System.out.println(  year + " "  + eventHandler.printAsteroidCZYN + " "  + eventHandler.rollingGraphCZYN + " " + eventHandler.pauseEventCZYN );

                runmode++;                     // change display graph mode
                if ( runmode > 5) { runmode= 0; }
                count++;
           }
           try {
                Thread.sleep(0);
           } catch (InterruptedException e){ }
       }

    }
    
    // set all initial temperatures to current temperatures
    void setInitialTemperatures() {
        for ( int nlat=0; nlat<this.nLatitudes; nlat++ ) {
            column[ nlat ].setInitialTemperatures();
        }
    }
        
    
    void printAirTemperatureTables( int nlat ) {
        int n = 0;
        System.out.println( "  day   ocean     land      snow    nlat=" + nlat );
        for ( n=1; n<=365; n++ ) {
            System.out.printf("% 5d   ", n );
            System.out.printf("%-10.2f", ocean[ nlat ].getDailyValue( n ) );
            System.out.printf("%-10.2f", land[ nlat ].getDailyValue( n ) );
            System.out.printf("%-10.2f", snow[ nlat ].getDailyValue( n ) );
            System.out.println();
        }
    }

    
    
    void mathEngine() {
        boolean columnAlbedoChange = false;
        double ytimestep = this.timeStepYears;

        // do eventchecking first
        eventHandler.checkEventQueue();

        // 365 longDay year loop
        for ( int dayIndex=this.timeStepLongDays; dayIndex<=365; dayIndex+= this.timeStepLongDays ) {

            year += ytimestep;        
            century = year / 100.0;
            millennium = year / 1000.0;
            
//          if ( year > 100 && year < 101 ) System.out.println( year + " -- " + dayIndex );

            // find new temperatures
            for ( int nlat= 0; nlat<=17; nlat++ ) {
                if ( column[ nlat ].columnActiveCalculation ) {                 
                    column[ nlat ].getNewLayerTemperatures( ( this.secondsPerYear * ytimestep ), this.timeStepLongDays, this.nviewLatitude, dayIndex );
                    if ( column[ nlat ].albedoChange ) { 
                        columnAlbedoChange = true;
                    }
                }                
                // at end of year, get new daily mean air temperature at latitude nlat
                if ( dayIndex == 365 ) active[ nlat ].getMeanDailyValue();
            }
            tick += timeStepLongDays;                     
        }

        // if a column's albedo has changed, find new global air temperatures
        if ( columnAlbedoChange ) findGlobalMeanAirTemperature();

/* code to remove top rock layers        
        if ( year > 12000 && !removeFlag ) {
            // remove top two surface rock bands
            column[ nviewLatitude ].removeLayer( column[ nviewLatitude ].surfaceRockBand );
            column[ nviewLatitude ].reSynchronise();
            column[ nviewLatitude ].removeLayer( column[ nviewLatitude ].surfaceRockBand );
            column[ nviewLatitude ].reSynchronise();
            this.nband[0] = column[ nviewLatitude ].surfaceRockBand;
            removeFlag = true;
        }
*/
        
        currentCZYN++;
        longCZYN++;

    }
    

    void rollingGraphOutput( double interval) {
        String s;
        int maxn = 5;
        canvas1.day = (int)(interval);
//      canvas1.calendarStep = (int)(interval*10);   // years per pixel
//      canvas1.calendarStep = 20;                 // years per pixel
        double maxtemp =  500;                      //absolute values K
        double mintemp =    0;
        if ( relativeTemperatures ) {
            maxtemp =   30;
            mintemp =  -20;
        }
     
        int n = this.nband[0];
//      int n = column[ this.nviewLatitude ].surfaceRockBand;
        double var = column[ this.nviewLatitude ].layerTable[ n ].averageTemperatureK.currentAverage;
        if ( relativeTemperatures ) var = var - column[ this.nviewLatitude ].layerTable[ n ].initialTemperature; 
        canvas1.nexty[0] = var;
        s = "" + 0;
        canvas1.setStreamLabel(0, s);
        canvas1.setStreamMaxMin( 0, maxtemp, mintemp );

//      n--;
//      var = column[ this.nviewLatitude ].layerTable[ n ].averageTemperatureK.currentAverage;
//      if ( relativeTemperatures ) var = var - column[ this.nviewLatitude ].layerTable[ n ].initialTemperature;
        n = this.nband[0] + 1;                  // air temperature
        var = column[ this.nviewLatitude ].layerTable[ n ].currentTemperatureK;
        // only plot snow temperature if snow/ice/water present
        if ( var > 0 && column[ this.nviewLatitude ].layerTable[ n ].genericMaterialType() == 3 ) {
            if ( flag1 == true ) {
                canvas1.plot[1] = true;
            } else {
                canvas1.plot[1] = false;    // don't plot surface rock temperature
                flag1 = true;
            }
        } else {
            canvas1.plot[1] = false;  
            flag1 = false;
        }
        canvas1.nexty[1] = var;
        s = "" + 1;
        canvas1.setStreamLabel(1, s);
        canvas1.setStreamMaxMin( 1, maxtemp, mintemp );

//      n--;
        n = this.nband[0] - 1;
        var = column[ this.nviewLatitude ].layerTable[ n ].averageTemperatureK.currentAverage;
        if ( relativeTemperatures ) var = var - column[ this.nviewLatitude ].layerTable[ n ].initialTemperature;
        canvas1.nexty[2] = var;
        s = "" + 2;
        canvas1.setStreamLabel(2, s);
        canvas1.setStreamMaxMin( 2, maxtemp, mintemp );

//      n = column[ this.nviewLatitude ].airLowestBand;
//      var = column[ this.nviewLatitude ].layerTable[ n ].averageTemperatureK.currentAverage;
        var = active[ this.nviewLatitude ].meanDailyValue;
//      var = column[ this.nviewLatitude ].airLowestBandTemperatureK;
        if ( relativeTemperatures ) var = var - column[ this.nviewLatitude ].layerTable[ n ].initialTemperature;
        canvas1.nexty[3] = var;
        s = "" + 3;
        canvas1.setStreamLabel(3, s);
        canvas1.setStreamMaxMin( 3, maxtemp, mintemp );

//      n = column[ this.nviewLatitude ].thinSurfaceBand;
//      var = column[ this.nviewLatitude ].layerTable[ n ].averageTemperatureK.currentAverage;
        var = column[ this.nviewLatitude ].iceWaterDepth;        
//      if ( relativeTemperatures ) var = var - column[ this.nviewLatitude ].layerTable[ n ].initialTemperature;
        canvas1.nexty[4] = var;
        s = "" + 4;
        canvas1.setStreamLabel(4, s);
        canvas1.setStreamMaxMin( 4, maxtemp, mintemp );

    }


    public void paint(Graphics g) {
        update(g);
    }


    public void update( Graphics g ) {
        int n, bradius, nlayers;
        long lyear;
        int xcentre, ycentre, xtopleft, ytopleft, hr;
        String str;
        double topRadiusMaterial[] = new double[20];


        if ( rollingGraphUpdated ) {
            canvas1.paint( fieldGraphics );
            this.rollingGraphUpdated = false;
        } else {
//          System.out.println( "roling graph not updated ");
        }

        // extract date from graphicLayer array of layer data
        n = 0;
        int material = -1;
        while ( column[ nviewLatitude ].graphicLayer[n][0] >= 0 ) {
            if ( column[ nviewLatitude ].graphicLayer[n][1] != material ) {
                material = (int)column[ nviewLatitude ].graphicLayer[n][1];
                topRadiusMaterial[ material ] = column[ nviewLatitude ].graphicLayer[n][2];
            }
            n++;
        }
        nlayers = n;


        offGraphics.setPaintMode();
        if ( clearScreen ) {
            offGraphics.setColor( Color.white );
            offGraphics.fillRect( 0, 0, xmax, ymax/2 );     // only clear top half of offImage
        }

        lyear = (long)year;

        // display screen text
        textGraphics.setPaintMode();
        textGraphics.setColor( Color.white );
        textGraphics.fillRect( 0, 0, xmax, ymax );
        textGraphics.setColor( Color.black );
        int px = 15;
        int hpx = 200;
        elapsedSecs = (System.currentTimeMillis() - stime)/1000.0;
        if ( !go ) { str = " PAUSED" ; } else { str = ""; } 
        textGraphics.drawString( "CZYN " + (float)(currentCZYN - eemianCZYN) + str, 2, px );
        textGraphics.drawString( "Elapsed secs " + elapsedSecs , hpx, px );
        px += 15;
        textGraphics.drawString( "air temperature " + (float)active[ this.nviewLatitude ].meanDailyValue, 2, px );
        textGraphics.drawString( "CZY/sec " + (float)( (currentCZYN - eemianCZYN)/elapsedSecs ), hpx, px );
        px += 15;
//      textGraphics.drawString( "Rock " + (int)column[ nviewLatitude ].surfaceRockBand + " hfr=" + (float)column[ nviewLatitude ].surfaceRockHeatFlowRate + " (" + (float)column[ nviewLatitude ].maxSurfaceRockHeatFlowRate + ")", 2, px );
        textGraphics.drawString( "Rock " + (int)column[ nviewLatitude ].surfaceRockBand + " hfr=" + (float)column[ nviewLatitude ].annualMeanHeatflow, 2, px );
        textGraphics.drawString( "DT  " + (float)(timeStepYears * 365.25) + " days", hpx, px );
        px += 15;
        textGraphics.drawString( "IceWater Depth " + (float)column[ nviewLatitude ].iceWaterDepth, 2, px );
        textGraphics.drawString( "Filename " + csvFilename, hpx, px );
        px += 15;
        textGraphics.drawString( "Latitude " + viewLatitude, hpx, px );
        double var;
        boolean showTemperatures =  true;
        // show 3 surface rock layers
        for ( n=column[ nviewLatitude ].graphicSurfaceRockBand-5; n<column[ nviewLatitude ].graphicLayerCount; n++ ) {
            if ( column[ nviewLatitude ].graphicLayer[n][0] >= 0 ) {
                if ( showTemperatures ) {
                    var = column[ nviewLatitude ].graphicLayer[column[ nviewLatitude ].graphicLayerCount-n-1][4];
                    if ( this.relativeTemperatures ) var = column[ nviewLatitude ].graphicLayer[column[ nviewLatitude ].graphicLayerCount-n-1][4] - column[ nviewLatitude ].graphicLayer[column[ nviewLatitude ].graphicLayerCount-n-1][9];
                } else {
                    // show conductive heat flows to layer above
                    var = column[ nviewLatitude ].graphicLayer[column[ nviewLatitude ].graphicLayerCount-n-1][6];
                }
                textGraphics.drawString( "" + (int)column[ nviewLatitude ].graphicLayer[column[ nviewLatitude ].graphicLayerCount-n-1][1] + " UNI" + (int)column[ nviewLatitude ].graphicLayer[column[ nviewLatitude ].graphicLayerCount-n-1][7] + " T" + (int)column[ nviewLatitude ].graphicLayer[column[ nviewLatitude ].graphicLayerCount-n-1][0] + "= " + (float)var, 2, px  );
                px += 15;
            }
        }


        // paint the asteroid layers into layerImage
        layerDisplay.paint( layerGraphics, topRadiusMaterial[ 2 ]  );
//      layerDisplay.paint( layerGraphics, column[ nviewLatitude ], topRadiusMaterial[ 2 ]  );

        // draw the layerimage into top left corner of offImage
        offGraphics.drawImage( layerImage, 0, 0, this );

        // draw the textImage offset into offImage
        offGraphics.drawImage( textImage, layerMargin, 0, this );

        // draw the rolling graph fieldImage offset into offImage
        offGraphics.drawImage( fieldImage, layerMargin, ymax/2, this );

        g.drawImage( offImage, 0, 0, this );


    }

    Color setColour( int index ) {
        Color colour = Color.white;
        if ( index == 1 ) colour = Color.black;
        if ( index == 2 ) colour = Color.darkGray;
        if ( index == 3 ) colour = Color.lightGray;
        if ( index == 4 ) colour = Color.cyan;
        return( colour );
    }
/*
    Color setColour( int index , double temperature ) {
        Color colour = Color.white;
        Material mt = new Material( index );
        int cindex = 0;
        if ( temperature > mt.phaseTemperature1 ) cindex = 1;
        if ( temperature > mt.phaseTemperature2 ) cindex = 2;
        colour = colorpalette[ mt.colourIndex[ cindex ] ];
//      if ( index == 1 ) colour = Color.black;
//      if ( index == 2 ) colour = Color.darkGray;
        return( colour );
    }
*/
    Color setColour( int index, double phase ) {
        Color colour = Color.white;
        Material mt = new Material( column[ nviewLatitude ], index );
        int cindex = 0;
        if ( phase == 1 ) cindex = 1;
        if ( phase == 2 ) cindex = 2;
        colour = colorpalette[ mt.colourIndex[ cindex ] ];
        return( colour );
    }

    int scaleRadius( double r, double jscale ) {
        int pixelHeight = (int)( r * jscale );
        return( pixelHeight );
    }

    public void stop() {
        testThread = null;
    }
    
/*** start preliminaries *******************************************************/    

    // prelinary simulation to find air teemperatures at different latitudes and albedos
    // using the simulation model
    void preliminaries() {
        int indexDay;
        double initialTimestep = this.timeStepYears;
        double y, latitudeAlbedo;
        int y1 = 33;
        int y2 = 66;
        int y3 = 500;

        // force timestep of one day
        timeStepYears = 1.0 / 365.25;;
        // enable calculation of atmosphere and thin surface layers
        for ( int nlat=0; nlat<this.nLatitudes; nlat++ ) {
            column[ nlat ].airMixingFactor = 0;
            column[ nlat ].setActiveCalculationLayers (  column[ nlat ].topLayerOfAtmosphere , column[ nlat ].surfaceRockBand );            
        }
        System.out.println( "setcCalculationLayers " + column[ 0 ].topLayerOfAtmosphere + " to " + column[ 0 ].surfaceRockBand );
        
        int someTimePeriod = (int)( 1.0  /  this.timeStepYears );
        for ( int years=0; years<y3; years++ ) {
            System.out.println( "preliminary year " + years );
            latitudeAlbedo = 0.1;                           // ocean albedo
            if ( years >= y1 ) latitudeAlbedo = 0.4 ;       // granite/forest albedo
            if ( years >= y2 ) latitudeAlbedo = 0.8 ;        // snow albedo
            
            indexDay = 1;
            for ( int period=1; period<=someTimePeriod; period++ ) {
  
                year += this.timeStepYears;        // time step monthly
                century = year / 100.0;
                millennium = year / 1000.0;
  
                for ( int nlat=0; nlat<this.nLatitudes; nlat++ ) {
                    column[ nlat ].setThinSurfaceBandAlbedo( latitudeAlbedo );
                    
                    // find new temperatures, with surface rock temperatures fixed
                    column[ nlat ].getNewLayerTemperatures( ( this.secondsPerYear * this.timeStepYears ), 1, nlat, indexDay );
                    // duplicate N hemisphere temperatures offset 6 months in S hemispher         
                    if ( latitudeAlbedo == 0.1 ) {
                        this.ocean[nlat].setDailyValue( column[ nlat ].airLowestBandTemperatureK, indexDay );
                        this.ocean[nlat + this.nLatitudes].setDailySouthernHemisphereValue( column[ nlat ].airLowestBandTemperatureK, indexDay );                        
                    } else if ( latitudeAlbedo == 0.4 ) {
                        this.land[nlat].setDailyValue( column[ nlat ].airLowestBandTemperatureK, indexDay);
                        this.land[nlat + this.nLatitudes].setDailySouthernHemisphereValue( column[ nlat ].airLowestBandTemperatureK, indexDay);
                    } else if ( latitudeAlbedo == 0.8  ) {
                        this.snow[ nlat ].setDailyValue( column[ nlat ].airLowestBandTemperatureK, indexDay);
//                      if ( nlat == this.nviewLatitude ) System.out.println( indexDay + ": " + (float)column[ nlat ].airLowestBandTemperatureK + ", " + (float)column[ nlat ].activeAirTemperature );
                        this.snow[nlat + this.nLatitudes].setDailySouthernHemisphereValue( column[ nlat ].airLowestBandTemperatureK, indexDay);
//                      if ( nlat + this.nLatitudes == 35 && years > 98 ) System.out.println( indexDay + " " + this.snow[nlat + this.nLatitudes].getDailyValue( indexDay ) );
                    }
                }      
                
//              if ( year >= 98.9 ) System.out.println( (float)year + " " + indexDay + " " + currentGlobalMeanAirTemperature( indexDay ) );

                tick++;

                indexDay++;
                
            }
    
        }   

        // restore ap timestep 
        this.timeStepYears = initialTimestep;
        this.year = 0;
        
        System.out.println( "Annual global Mean Air Temperature " + findGlobalMeanAirTemperature() );
        
        printAirTemperatureTables( nviewLatitude );
    }

    // find global mean air temperature as area-weighted sum of air temperatures above ocean, land, and snow
    double currentGlobalMeanAirTemperature( int dayIndex ) {
        double tm = 0;
        double fractionSphereSurfaceArea;
        for ( int nlat=0; nlat<this.nLatitudes; nlat++ ) {
            fractionSphereSurfaceArea = map.pseudoMap[nlat+1][3];
            // NORTHER hemisphere
            tm += ocean[ nlat ].getDailyValue( dayIndex ) * map.pseudoMap[nlat+1][0] * fractionSphereSurfaceArea; // ocean
            tm += land[ nlat ].getDailyValue( dayIndex ) * map.pseudoMap[nlat+1][1] * fractionSphereSurfaceArea;  // land
            tm += snow[ nlat ].getDailyValue( dayIndex ) * map.pseudoMap[nlat+1][2] * fractionSphereSurfaceArea;  // snow
            // SOUTHERN hemisphere
            tm += ocean[ nlat + this.nLatitudes ].getDailyValue( dayIndex ) * map.pseudoMap[nlat+1 + this.nLatitudes][0] * fractionSphereSurfaceArea; // ocean
            tm += land[ nlat + this.nLatitudes ].getDailyValue( dayIndex ) * map.pseudoMap[nlat+1 + this.nLatitudes][1] * fractionSphereSurfaceArea; // ocean
            tm += snow[ nlat + this.nLatitudes ].getDailyValue( dayIndex ) * map.pseudoMap[nlat+1 + this.nLatitudes][2] * fractionSphereSurfaceArea; // ocean
        }
        return( tm );
    }   
    
    // using Preliminary run
    // get annual mean temperature from daily mean temperatures
    double findGlobalMeanAirTemperature() {
        double meanAirTemperature;
        double totalGlobalMeanTemperature = 0;
        for ( int dayIndex=1; dayIndex<=365; dayIndex++ ) {
            meanAirTemperature = currentGlobalMeanAirTemperature( dayIndex );
            totalGlobalMeanTemperature += meanAirTemperature;
            this.global.setDailyValue( meanAirTemperature, dayIndex );
//          System.out.println( "day " + dayIndex + " global mean air temperature " + meanAirTemperature );
        } 
        this.global.getMeanDailyValue();
        totalGlobalMeanTemperature = totalGlobalMeanTemperature / 365.0;
        this.annualGlobalMeanTemperature = totalGlobalMeanTemperature;
        System.out.println(  year + " new annualGlobalMeanTemperature " + this.annualGlobalMeanTemperature);
         
        return( totalGlobalMeanTemperature );
    }
    
    // update PseudoMap with new snow area, and find new combided air temperature
    double combinedAirTemperature( int nlat, double snowCoverageFraction, int dayIndex ) {
        double ta = 0;
        double landPlusSnow = 1.0 - map.pseudoMap[nlat+1][0];       
        double snowArea = snowCoverageFraction * landPlusSnow;
        double landArea = landPlusSnow - snowArea;
        
        // update map
        map.pseudoMap[nlat+1][2] = snowArea;
        map.pseudoMap[nlat+1][1] = landArea;
        
        // find combined ait temperature ta, and store in combined[] table
        ta += ocean[ nlat ].getDailyValue( dayIndex ) * map.pseudoMap[nlat+1][0];  // ocean
        ta += land[ nlat ].getDailyValue( dayIndex ) * map.pseudoMap[nlat+1][1] ;  // land
        ta += snow[ nlat ].getDailyValue( dayIndex ) * map.pseudoMap[nlat+1][2] ;  // snow
        this.combined[ nlat ].setDailyValue( ta, dayIndex );
        
        if ( dayIndex == 365 ) this.combined[ nlat ].getMeanDailyValue();
        
//      if ( year > 11999.5 && year < 12000.5 ) System.out.println( (float)snowArea + " " + (float)landArea + " : " + (float)ta  );
    
        return( ta ); 
    }
    
/**  end   preliminaries *******************************************************/    
    
    
}



class GeologicalColumn {

    Glaciation ap;
    boolean columnActiveCalculation;
    double columnLatitude;
    int nLatitude;
    double areaAtLatitude;              // area of circular strip at column latitude
//  double fractionAreaAtLatitude;      // fraction of asteroid surface area of strip at column latitude
//  double airTemperatureAtLatitude;
//  double fractionLandAreaAtLatitude = 0.6;    // 60% land in N hemisphere 0% in S hemisphere
    SolarRadiation sr;
    double radius;
    double nominalRadius;               // radius used in graphic display
    int nlayers;
    LinkedList<ConductiveLayer> llayer;
    double initialTemperatureK;
    int nIterations = 0;
    double referenceRadius = 0.0;                       // earth radius
    int surfaceBand;                                    // surface band for solar irradiation
    int thinSurfaceBand;                                // thin radiative surface band number
//  DailyValues glaciated = new DailyValues();          // pre-calculated glaciated daily air temperatures at colum latitude
//  DailyValues unglaciated = new DailyValues();        // ..     ..     unglaciated  ..   ..     ..       ..   ..     ..
    double stefanBoltzmannConstant = 5.6699E-8;
    double thinSurfaceBandTemperature;                  // thin radiative surface band temperature
    double thinSurfaceRadius = 0;
    int airLowestBand;                                  // bottom layer of air in atmospher
    double airLowestBandTemperature;                    // temperature of bottom layer of air
    double airLowestBandTemperatureK;                 // relative ..  .. ..  ..  
    double surfaceBandTemperature;
    double lastSurfaceTemperature;
    double surfaceRockHeatFlowRate;
    double maxSurfaceRockHeatFlowRate = 0;
    double surfaceRockTemperature = 0;
    double atmosphereTemperature;
    int surfaceRockBand;
    double surfaceHeatFlowRate = 0;
    double year;
    double graphicLayer[][] = new double[2000][10];     // date for graphics display
    int graphicLayerCount = 0;                          // data for grapjic display
    int graphicSurfaceRockBand = 0;                     // data for graphic display
    boolean resynchronise = true;
    double annualSolarInsolation = 1362 * 3.6E6;         // annual solar radiation kwh-> J UK
    double solarGain;                                   // solar gain Watts
    double albedo = 0.3;                                // mean Earth albedo
    double previousAlbedo = albedo;                     // albedo on previous iteration
    boolean albedoChange =  true;                       // set true at initialisation, and when albedo changes
    double secondsPerYear = 3.154e+7;
    double secondsPerDay = 86400;
    double iceDepth = 0;
    double iceWaterDepth = 0;
    double previousIceWaterDepth = 0;
    boolean iceMeltingAtBase = false;
    boolean printFlag = false;
    double surfaceArea;                                 // surface area of asteroid
    double mass;                                        // mass of asteroid (earth 5.972 × 10^24 kg)
    double atmosphereMass;                              // (earth 5.1480×10^18 kg)
    double topOfAtmosphere;
    int topLayerOfAtmosphere;
    double surfaceGravity = 9.81;                       // surface gravitaional acceleration (should be calculated)
    double asteroidHeatContent;
    double meanTimestepSolarGain;                       // J / timestep
    boolean varyIceAlbedo =  true;
    boolean varyIceAlbedoReset = true;
    double phaseChangeDate[] = new double[200];
    int uniqueNumber = 1;
    int removeLayerFlag = -1;                           // number of layer to remove
    boolean flagPrintColumn = false;
    boolean flagSetAirEmissivity = false;
    double yearSetAirEmissivity = 22000;                // year that emissivity is to be set
    double setAirEmissivity = 0.909;                    // high emissivity/absorptivity to emulate global warming
    double startOfGlaciation;
    double endOfGlaciation;
    boolean setTSLtempEqualToSRtemp = false;            // set thin surface layer temperature equal to top surface rock temperature (zero surface heat loss)
    boolean fixedIceHeatflow = false;                   // flag fixed heat flow rate into ice/snow from surface rock below
    double fixedIceHeatflowRate = 0.060;                //  fixed heat flow rate into ice/snow W
    double forestHeight =   0.0;
    double forestAlbedo = 0.09;
    double thinSurfaceLayerAlbedo = 0.09;
    int test;
    double degreesToRadians = Math.PI / 180;
    int timer = 0;
    double numAnnualMeanValues = 0;
//  double annualMeanAirTemperature;
    double annualMeanHeatflow;
//  double annualMeanAirTemperatureSum = 0;
    double annualMeanHeatflowSum = 0;
    ConductiveLayer layerTable[] = new ConductiveLayer[400];
    double airMixingFactor = 0.001;                        // air mixing factot in range 0 ( no mixing) to 1 (complete mixing)
    double activeAirTemperature;
    double horizontalProportionOfSnow;
    double snowLayerThickness = 25;                        // column snow deposition
    double snowDepositionInterval = 275;
    double snowMaximumDepth = 3000;
    double snowType = 3.05;
    double snowTemperature;
    long snowStartCZYN = 0;
    long snowStopCZYN = 66043000 * 2;
    double precipitstionCZYN ;
    double terrainHeight = 300;
    double terrainWidth = 600;
    double terrainUnderlyingAlbedo = 0.4;
    double terrainSnowAlbedo = 0.8;
    
    GeologicalColumn() { }

    GeologicalColumn( Glaciation a, String filename, double lat, int test ) {
        this.ap = a;
        this.test = test;
        
        System.out.println();
        System.out.println( "column latitude " + lat );
        this.columnLatitude = lat;
        
        referenceRadius = 6371500;                  // Earth-specific
        nominalRadius = referenceRadius;
        topOfAtmosphere = referenceRadius;
        System.out.println( "topOfAtmosphere0 " + topOfAtmosphere );
        
        areaAtLatitude = AreaAtLatitude( this.columnLatitude, 2.5 );
        System.out.println( "area At " + (float)this.columnLatitude + " Latitude " + areaAtLatitude );
        
        this.surfaceArea = this.sphereSurfaceArea( referenceRadius );
//      this.fractionAreaAtLatitude = this.areaAtLatitude / this.surfaceArea;
//      System.out.println( "this.fractionAreaAtLatitude " + this.fractionAreaAtLatitude );

        if ( filename.contains( ".csv" ) ) csvReader( filename );
        else if ( filename.contains( ".hcm" ) ) hcmReader( filename );
        else System.out.println( "no file" );

        reSynchronise();
        
        System.out.println( "topOfAtmosphere1 " + topOfAtmosphere );

//      adjustLayerTemperatures( 270.0/281.0 );
//      adjustLayerTemperatures( 0.5 );
        
//      setLayerPhases();
        
//      fixBandTemperature( true, 64 );

//      if ( this.columnLatitude == this.ap.viewLatitude ) printColumn( 0 );

//      if ( this.columnLatitude == this.ap.viewLatitude ) printColumnRC( 0 );

        recalculateRadii( 0 );
//      if ( this.columnLatitude == this.ap.viewLatitude ) printColumn( 0 );
        
//      if ( this.airMixingFactor > 0 ) setActiveCalculationLayers( this.thinSurfaceBand, this.thinSurfaceBand - 20  );
//      precipitstionCZYN = (long)ap.eemianCZYN + 12000;
        this.precipitstionCZYN = (long)ap.startCZYN + ap.delayedStart;
//      ap.eventHandler.insertEvent( ap.eventHandler.precipitstionCZYN );
        
//      setLayerTicks( 65, 100 );
/*
        System.out.println( "addLayer");
        addLayer( 72, 270.0, 200.0, 3.05);
        recalculateRadii( 0 );
        printcsvColumn( 0 );
*/
        double SRtimestep =  1.0 / 365.25;
        sr = new SolarRadiation( this.ap, this, 1367.0, lat, SRtimestep, 365.25, 0.016702, 0.409093, 1.796257 );
        System.out.println( "mean annual solar power: " + this.sr.meanSolarPower );
        
        this.areaAtLatitude = areaAtLatitude();
        
//      if ( this.columnLatitude == this.ap.viewLatitude ) setGeothermalGradient( 0.060 );
        System.out.println( "topOfAtmosphere2 " + topOfAtmosphere );

    }

    // read .csv (comma separated variables) file containing body locations, velocities.
    // (need to set working directory and applet.policy )
    double csvReader( String file ) {
        double m, x, y, z, xx, yy, zz, v, r, jd, startJulianDate;
        double nband, mt, rLayer, wLayer, hLayer, layerTemperature;
        double alayer[][] = new double[150][10];
        ConductiveLayer layer;
        llayer = new LinkedList<ConductiveLayer>();
        int b = 0;
        int rowcount, fieldcount, clr;
        boolean bodyok, filefound = false;
        String full_name, nm, dataRow;
        BufferedReader CSVFile;
        String rs[] = new String[400];
        startJulianDate = 0.0;
        rowcount = 0;
        boolean atmosphereFound = false;
        this.nlayers = 0;

        try {
            CSVFile = new BufferedReader(new FileReader(file));
            rowcount = 0;
            try {
                dataRow = CSVFile.readLine(); // Read first line.
            } catch (IOException e) {
                dataRow = new String("");
            }

            while (dataRow != null) {
                bodyok = false;
                filefound = true;
                StringTokenizer st = new StringTokenizer(dataRow, ",");
                fieldcount = 0;
                while (st.hasMoreTokens()) {
                    String s = st.nextToken();
                    rs[ fieldcount] = s;
                    fieldcount++;
                }

                if (rowcount > 0) {
                    nband = Double.valueOf(rs[0]).doubleValue();
                    if ( nband >= 0 ) {
                        // read layer data
                        alayer[nlayers][0] = Double.valueOf(rs[0]).doubleValue();
                        alayer[nlayers][1] = Double.valueOf(rs[1]).doubleValue();
                        alayer[nlayers][2] = Double.valueOf(rs[2]).doubleValue();
                        alayer[nlayers][3] = Double.valueOf(rs[3]).doubleValue();
                        alayer[nlayers][4] = Double.valueOf(rs[4]).doubleValue();
                        alayer[nlayers][5] = Double.valueOf(rs[5]).doubleValue();
                        alayer[nlayers][6] = Double.valueOf(rs[6]).doubleValue();

                        // single layer atmosphere is lowest air layer
                        if ( alayer[nlayers][1] == 5  ) {
                            topOfAtmosphere += alayer[nlayers][2];
                            topLayerOfAtmosphere = (int)nband;
                            // single layer atmosphere is first air mass found
                            if ( !atmosphereFound ) {
                                atmosphereFound = true;
                            }
//                          System.out.println( "air " + nband );
                        }
                        this.nlayers++;

                    } else if ( nband == -2 ) {
                        // read asteroid data
//                      this.referenceRadius = Double.valueOf(rs[1]).doubleValue();
                    }


                }
                rowcount++;
                try {
                    dataRow = CSVFile.readLine(); // Read next line.
                } catch (IOException e) {
                    dataRow = new String("");
                }
            }

            // Close the file once all data has been read.
            try {
                CSVFile.close();
            } catch (IOException e) {
            }

        } catch (IOException e) {
            filefound = false;
            System.out.println( file + " CSV file not found");
        }

        if ( filefound ) {
            // construct asteroid column from centre outward to surface
            int nl = nlayers;
            for ( int i=nl-1; i>=0; i-- ) {
                if ( alayer[i][0] >= 0 ) {
//                  alayer[i][6] = 5000;
                    //                                 radius,       width,        height,       temperature,   material,   tickDuration, W/m^3
                    layer = new ConductiveLayer( this, alayer[i][2], alayer[i][4], alayer[i][3], alayer[i][6], alayer[i][1], alayer[i][5], 0 );
                    layer.currentLayerPressure = layer.currentLayerPressure * 0.50;
                    llayer.addFirst( layer );
                    if ( (int)alayer[i][1] == 0 ) {
                        this.thinSurfaceRadius = alayer[i][2];
                        this.referenceRadius = this.thinSurfaceRadius;
                        System.out.println( "thinSurfaceRadius " + thinSurfaceRadius );
                    }
                }
            }
            // End the printout with a blank line.
            System.out.println("CSV File complete + " + rowcount + " rows " );
        }

        return( startJulianDate );
    }


    // read .hcm (comma separated variables) file containing layer heat contents and masses
    // (need to set working directory and applet.policy )
    double hcmReader( String file ) {
        double nband, startJulianDate;
//      double nband, mt, rLayer, wLayer, hLayer, layerTemperature;
        double alayer[][] = new double[150][10];
        ConductiveLayer layer;
        llayer = new LinkedList<ConductiveLayer>();
        int b = 0;
        int rowcount, fieldcount, clr;
        boolean bodyok, filefound = false;
        String full_name, nm, dataRow;
        BufferedReader CSVFile;
        String rs[] = new String[400];
        startJulianDate = 0.0;
        rowcount = 0;
        boolean atmosphereFound = false;
        this.nlayers = 0;
        
        System.out.println( file + " hcm file reader");

        try {
            CSVFile = new BufferedReader(new FileReader(file));
            rowcount = 0;
            try {
                dataRow = CSVFile.readLine(); // Read first line.
            } catch (IOException e) {
                dataRow = new String("");
            }

            while (dataRow != null) {
                bodyok = false;
                filefound = true;
                StringTokenizer st = new StringTokenizer(dataRow, ",");
                fieldcount = 0;
                while (st.hasMoreTokens()) {
                    String s = st.nextToken();
                    rs[ fieldcount] = s;
                    fieldcount++;
                }

                if (rowcount > 0) {
                    nband = Double.valueOf(rs[0]).doubleValue();
                    if ( nband >= 0 ) {
                        // read layer data
 
                        alayer[nlayers][0] = Double.valueOf(rs[0]).doubleValue();   // layer nBand
                        alayer[nlayers][1] = Double.valueOf(rs[1]).doubleValue();   // layer materialType
                        alayer[nlayers][2] = Double.valueOf(rs[2]).doubleValue();   // layer radius
                        alayer[nlayers][3] = Double.valueOf(rs[3]).doubleValue();   // layer height
                        alayer[nlayers][4] = Double.valueOf(rs[4]).doubleValue();   // layer mass
                        alayer[nlayers][5] = Double.valueOf(rs[5]).doubleValue();   // layer tick duration (secs)
                        alayer[nlayers][6] = Double.valueOf(rs[6]).doubleValue();   // layer heat content per Kg
                        alayer[nlayers][7] = Double.valueOf(rs[7]).doubleValue();   // layer temperature

                        // single layer atmosphere is lowest air layer
                        if ( alayer[nlayers][1] == 5  ) {
                            topOfAtmosphere += alayer[nlayers][2];
                            topLayerOfAtmosphere = (int)nband;
                            // single layer atmosphere is first air mass found
                            if ( !atmosphereFound ) {
                                atmosphereFound = true;
                            }
//                          System.out.println( "air " + nband );
                        }
                        this.nlayers++;

                    } else if ( nband == -2 ) {
                        // read asteroid data
//                      this.referenceRadius = Double.valueOf(rs[1]).doubleValue();
                    }


                }
                rowcount++;
                try {
                    dataRow = CSVFile.readLine(); // Read next line.
                } catch (IOException e) {
                    dataRow = new String("");
                }
            }

            // Close the file once all data has been read.
            try {
                CSVFile.close();
            } catch (IOException e) {
            }

        } catch (IOException e) {
            filefound = false;
            System.out.println( file + " hcm file not found");
        }

        if ( filefound ) {
            // construct asteroid column from centre outward to surface
            int nl = nlayers;
            double r = 0;
            for ( int i=nl-1; i>=0; i-- ) {
                if ( alayer[i][0] >= 0 ) {
                    //                                       nBand          material        radius      height          mass    tickduration  heat content   temperature
                    layer = new ConductiveLayer( this, (int)alayer[i][0], alayer[i][1],          r, alayer[i][3], alayer[i][4], alayer[i][5], alayer[i][6], alayer[i][7] ); 
                    layer.currentLayerPressure = layer.currentLayerPressure * 0.50;
                    r = layer.radius;  // use current layer radius to calculate next layer radius
                    llayer.addFirst( layer );
                    if ( (int)alayer[i][1] == 0 ) {
                        this.thinSurfaceRadius = alayer[i][2];                        
                        this.referenceRadius = this.thinSurfaceRadius;
//                      System.out.println( "thinSurfaceRadius " + thinSurfaceRadius );
                    }
  
                }
            }
            // End the printout with a blank line.
            System.out.println("hcm File complete + " + rowcount + " rows " );
        }

        return( startJulianDate );
    }


   // find all condutctive heat flows
    // using link list iterator
    void getNewLayerTemperatures( double timeStepSeconds, int timeStepLongDays, int nlat, int dayIndex ) {
        int removeLayer = -1;
        int n, bmax, bmin, i;
        boolean flag = false;
        boolean continuation = true;
        double heatflowrate, heatgain, dtemp, tmax, tmin, hfr, effectiveTemperature;
        ConductiveLayer lyr[] = new ConductiveLayer[3];
        int counter = 0;
        ListIterator<ConductiveLayer> itr = null;
        boolean timedOut = false;
        
        
        this.nLatitude = nlat;   // nLatitude should be set at initialisation
//      if ( ap.year == 109.82067077339573 ) System.out.println( nlatitude + " lat in yr 109.82067077339573" );
        
        // get the top 3 layers
        // layer 0 is a dummy floating layer
        heatflowrate = 0.0;
        itr = llayer.listIterator();
        if ( itr.hasNext() ) {
            lyr[0] = itr.next();
            counter++;
        }

        // layer 1 is a layer of air
        if ( itr.hasNext() ) {
            lyr[1] = itr.next();
            counter++;
        }

        // layer 2 is either a layer of water or ice or rock
        // lyr[] contains layer[n-1], layer[n], layer[n+1] for all layers
        // While loop continues down from top of atmosphere until continuation is false,
        // which happens when the temperature of the layer is fixed.
        while(itr.hasNext() && continuation ){
            lyr[2] = itr.next();
            counter++;

            // here come the heat flow calculations
            heatflowrate = 0.0;
            lyr[0].layerResistance();
            lyr[1].layerResistance();
            lyr[2].layerResistance();

            lyr[1].updated = false;
            
//          if ( lyr[1].nBand == this.thinSurfaceBand ) System.out.println( lyr[1].nBand + " thinSurfaceBand " + lyr[1].currentTemperatureK + " albedo " + lyr[1].albedo  );
//          if ( lyr[1].nBand == this.surfaceRockBand ) System.out.println( lyr[1].nBand + " thinSurfaceBand " + lyr[1].currentTemperatureK );

            if ( lyr[1].genericMaterialType() == 0  && lyr[1].activeCalculation ) {
                // thin surface layer
                
                if ( ap.solar == 3 ) {
                    // Milankovitch daily solar power W/m^2
                    solarGain = ( sr.getTotalSolarGain( dayIndex-timeStepLongDays, dayIndex ) ) / timeStepSeconds;   // assumes daily solar gain
                } else if ( ap.solar == 2 ) {
                    solarGain = ( sr.getTotalSolarGain( dayIndex-timeStepLongDays, dayIndex ) ) / timeStepSeconds;   // assumes daily solar gain
//                  if ( ap.year > 0 && ap.year < 2 ) System.out.println( ap.year + " dayIndex " + dayIndex + " timeStepLongDays " + timeStepLongDays + " solarGain " + solarGain ); 
                } else if ( ap.solar == 1 ) {
                    solarGain = sr.meanSolarPower;                       // mean solar power (Watts) over year
                } else {
                    solarGain = 0;
                }
                solarGain = (1.0 - ap.cloudCover) * solarGain;                  // solar gain is reduced by cloud cover
                
                // if thin surface layer albedo < 0, use albedo of layer below
                if ( lyr[1].albedo < 0 ) {
                    this.albedo = Math.abs( lyr[2].albedo );
                    //                                height width  albedo    snow depth   snow albedo
                    this.albedo = this.terrainAlbedo(  this.terrainHeight, this.terrainWidth, this.terrainUnderlyingAlbedo, this.iceWaterDepth, this.terrainSnowAlbedo, dayIndex );
                } else {
                    this.albedo = Math.abs( lyr[1].albedo );
                }
                
                // test terrain albedo
                //                                height width  albedo    snow depth   snow albedo
//              this.albedo = this.terrainAlbedo(    0,   600,   0.4, this.iceWaterDepth, 0.8, dayIndex );

                this.thinSurfaceLayerAlbedo = this.albedo;
                double ts = surfaceTemperature( lyr[0], lyr[1], lyr[2], dayIndex, timeStepLongDays );       // fourth root solution

                if ( !lyr[1].fixTemperature ) {
                    lyr[1].layerHeatContentPerKg = ts * lyr[1].layerSpecificHeat;
                    lyr[1].layerHeatContent = lyr[1].layerHeatContentPerKg * lyr[1].mass;
                    this.lastSurfaceTemperature = ts;
                    lyr[1].nextTemperatureK = ts;
//                  System.out.println( "thin surface layer phase " + lyr[1].phase );
                } else {
                    continuation = false;
                }
                
//              if ( ap.year >= 109.82067077339573 && ap.year < 109.99 ) System.out.println( ts + " ts " + this.columnLatitude + " columnLatitude " + ap.viewLatitude + " viewLatitude in yr " + ap.year );

            } else if ( lyr[1].genericMaterialType() == 5  && lyr[1].activeCalculation ) {
                // multi-layer atmosphere
                // radiative heat exchanges from atmospheric air
                heatflowrate = 0;
                // heat radiated from surface above is all absorbed
                heatflowrate += wattsRadiated( lyr[0].currentTemperatureK, lyr[0].layerEmissivity );
                // heat radiated from surface below is not all absorbed. Some is radiated into space
                heatflowrate += lyr[1].layerEmissivity * wattsRadiated( lyr[2].currentTemperatureK, lyr[2].layerEmissivity );
                // heat radiated to surface below
                heatflowrate -= wattsRadiated( lyr[1].currentTemperatureK, lyr[1].layerEmissivity );
                // heat radiated to surface above
                heatflowrate -= wattsRadiated( lyr[1].currentTemperatureK, lyr[1].layerEmissivity );
                // find heat gain over timestep
//              heatgain = heatflowrate * interval;
                heatgain = heatflowrate * timeStepSeconds;

                // find change in temperature due to heat gain.
                // * 5.0 is a fix to make current 5 atmosphere layers less responsive and stable
                dtemp = heatgain / ( lyr[1].mass * lyr[1].layerSpecificHeat * 1.0 );

                if ( !lyr[1].fixTemperature ) {
                    lyr[1].nextTemperatureK = lyr[1].currentTemperatureK + dtemp;
                } else {
                    continuation = false;
                }
//              System.out.println( "nband " + lyr[1].nBand + ": heatgain " + heatgain + " dtemp " + dtemp + " lyr[1].nextTemperatureK " + lyr[1].nextTemperatureK );
/*
                timer++;
                if ( timer >= 10000 ) {
                    System.out.println( ap.year + " this.nBand " + lyr[1].nBand );
                    timer = 0;
                }
*/
            } else if ( lyr[1].genericMaterialType() > 0  && lyr[1].activeCalculation ) {
                
                // find if any snow above surface rocks is melthing
                if ( lyr[1].nBand == this.surfaceRockBand ) {
/*
                    if ( lyr[0].genericMaterialType() == 3 && lyr[0].currentTemperatureK >= 273.0 ) {
                        this.iceMeltingAtBase = true;
                    } else {
                        this.iceMeltingAtBase = false;
                    }
*/
                    if ( lyr[1].currentTemperatureK > 273.0 ) {
                        this.iceMeltingAtBase = true;
                    } else {
                        this.iceMeltingAtBase = false;
                    }
                }    
                
                
                // while layer phase change is in process, currentTemperatureK is fixed, and
                // phaseTemperature is used instead until phase change is complete
                effectiveTemperature = lyr[1].currentTemperatureK;

                // conductive heat transfer
                // heat flow rate rate from higher layer 0 to layer 1
                if ( fixedIceHeatflow && ( lyr[0].genericMaterialType() == 3 ) && ( lyr[1].nBand == this.surfaceRockBand ) ) {
                    // here if layer is surface rock band and layer above is snow/ice 
                    heatflowrate -= fixedIceHeatflowRate;   // assumes upward heat flow
                } else {
                    // here for normal heat flow 
                    heatflowrate += conductiveHeatFlow( lyr[1], lyr[0].layerResistance, lyr[1].layerResistance, lyr[0].currentTemperatureK, effectiveTemperature );
                }
                
//              if ( lyr[1].nBand == 28 ) System.out.print( (float)lyr[0].currentTemperatureK + " 0->1 " + (float)heatflowrate );
                lyr[1].upwardHeatFlowRate = heatflowrate;
                if ( Math.abs( lyr[1].upwardHeatFlowRate ) > Math.abs( lyr[1].maxUpwardHeatFlowRate ) ) lyr[1].maxUpwardHeatFlowRate = lyr[1].upwardHeatFlowRate;
                if ( lyr[1].nBand == this.surfaceRockBand ) {
                    this.surfaceRockHeatFlowRate = heatflowrate;
                    this.maxSurfaceRockHeatFlowRate = lyr[1].maxUpwardHeatFlowRate;       
                }

                // conductive heat flow rate from lower layer layer 2 to layer 1
                if ( fixedIceHeatflow && ( lyr[1].genericMaterialType() == 3 ) && ( lyr[2].nBand == this.surfaceRockBand ) ) {
                    // here if fixed heat flow rate into ice/snow from surface rock beneth 
                    heatflowrate += fixedIceHeatflowRate;   // assumes upward heat flow
                } else {   
                    // here for normal heat flow 
                    heatflowrate += conductiveHeatFlow( lyr[1], lyr[2].layerResistance, lyr[1].layerResistance, lyr[2].currentTemperatureK, effectiveTemperature );
                }
                heatflowrate += lyr[1].heatProduced;      // radioactive heat production in layer

                
                // find heat gain over timestep
                heatgain = heatflowrate * timeStepSeconds;
                
// heat a layer if ( lyr[1].nBand == this.surfaceRockBand-4 ) heatgain = Math.abs( heatgain  );
                                
                lyr[1].layerHeatAdded += heatgain;
                lyr[1].layerHeatContentPerKg += heatgain / lyr[1].mass;
                lyr[1].layerHeatContent += heatgain;

                // find change in temperature due to heat gain. 

                // set the next layer temperature that will replace current layer tmperature               
                lyr[1].nextTemperatureK = lyr[1].pt.temperature( lyr[1].layerHeatContentPerKg );

            }
  
            lyr[1].updated = true;


            // roll layers up
            lyr[0] = lyr[1];
            lyr[1] = lyr[2];
        }


        
        // now copy next temperatures to current temperatures
        // and perform other housekeeping tasks
        int surfaceLayerIndex;
        double asteroidRadius = 0;
        asteroidHeatContent = 0;
        counter = 0;
        itr=llayer.listIterator();
        while(itr.hasNext()){
            lyr[0] = itr.next();

            if ( lyr[0].updated ) {
                
//              if ( lyr[0].nBand == 71 ) System.out.println( "l71 T " + lyr[0].currentTemperatureK  );

                // set current temperature of layer
                lyr[0].previousTemperaureK = lyr[0].currentTemperatureK;
                lyr[0].currentTemperatureK = lyr[0].nextTemperatureK;
                lyr[0].averageTemperatureK.addNewValue( lyr[0].currentTemperatureK );
//              if ( lyr[0].nBand == this.surfaceRockBand ) System.out.println( "this.surfaceRockBand " + lyr[0].averageTemperatureK.currentAverage );
/*                
                // store average surface rock temperatures ( topmost = 0)
                surfaceLayerIndex = this.surfaceRockBand - lyr[0].nBand;
                if ( surfaceLayerIndex >= 0 && surfaceLayerIndex < 10 ) {
                    this.surfaceTemperature[ surfaceLayerIndex ] = lyr[0].averageTemperatureK.currentAverage;
                }
*/
                // if phase has changed
                if ( lyr[0].phase != (int)lyr[0].nextPhase ) {
                    lyr[0].phase = (int)lyr[0].nextPhase; 
                    double mtrl = lyr[0].materialType;
                    mtrl = (int)mtrl + ( lyr[0].phase / 10.0 );  // 3.0 is ice, 3.1 is water, 3.2 is steam
                    lyr[0].materialType = mtrl;
                    lyr[0].material.setMaterialCharacteristics( mtrl, 1 );
//                  System.out.println( (float)ap.year + " bnnd " + lyr[0].nBand + " materialType becomes " + lyr[0].materialType );
                    // flag liquid or vapour water layer to be removed
                    if ( (int)mtrl == 3 ) {
                        if ( ap.waterRunoff && lyr[0].materialType >- 3.1 ) {
                            removeLayerFlag = lyr[0].nBand;
                        }
                    }
                }    

                // find layer heat content, and total asteroid heat content
                // asteroidHeatContent += lyr[0].heatContent();

                // ice thermal characteristics vary with temperature (not during phase changes)
                if ( ap.varyIceCharacter && lyr[0].material.isIce( lyr[0].currentTemperatureK) && !lyr[0].phaseChange ) {
                    i = lyr[0].material.indexIceTemperature( lyr[0].currentTemperatureK );
                    lyr[0].layerDensity = lyr[0].material.iceCharacteristics[i][1];
                    lyr[0].layerConductivity = lyr[0].material.iceCharacteristics[i][2];
                    lyr[0].layerSpecificHeat = lyr[0].material.iceCharacteristics[i][3] * 1000.0;
                    lyr[0].layerResistance = lyr[0].layerResistance();
                }

                // find temperature of lowest atmosphere layer (last air temperature found)
                if  ( lyr[0].material.isAir( lyr[0].currentTemperatureK ) ) {
                    this.atmosphereTemperature = lyr[0].currentTemperatureK;
//                  if ( ap.relativeTemperatures ) this.atmosphereTemperature = lyr[0].currentTemperatureK - lyr[0].initialTemperature ;
                }

                // set surface rock temperature.
                if ( lyr[0].nBand == this.surfaceRockBand ) {
                    surfaceRockTemperature = lyr[0].currentTemperatureK;
//                  if ( ap.relativeTemperatures ) surfaceRockTemperature = lyr[0].currentTemperatureK - lyr[0].initialTemperature ;
                }

                // set radiative surface layer temperature
                if ( lyr[0].nBand == this.thinSurfaceBand ) {
                    thinSurfaceBandTemperature = lyr[0].currentTemperatureK;
//                  if ( ap.relativeTemperatures ) thinSurfaceBandTemperature = lyr[0].currentTemperatureK - lyr[0].initialTemperature ;
//                  System.out.println( lyr[0].nBand + ": " + lyr[0].currentTemperatureK );
//                  if ( ap.year > 10000 ) lyr[0].currentTemperatureK = 322.85799999999506; // 20 deg temp jump 
                }

                // set lowest atnospheric layer remperature
                if ( lyr[0].nBand == this.airLowestBand ) {
                    this.airLowestBandTemperature = lyr[0].currentTemperatureK;
                    this.airLowestBandTemperatureK = lyr[0].currentTemperatureK;
//                  if ( ap.relativeTemperatures ) this.airLowestBandTemperature = lyr[0].currentTemperatureK - lyr[0].initialTemperature ;
    //              System.out.println( lyr[0].nBand + ": " + lyr[0].currentTemperatureK );
                }
                
//              if ( ap.icedropCounter > 0 && lyr[0].nBand == ap.icedropLayer ) {
                if ( ap.icedropCounter > 0 && lyr[0].materialType == 0 ) {
//                  System.out.println( "Layer[" + lyr[0].nBand + "] T = " + lyr[0].currentTemperatureK );
                    System.out.println( "Layer[" + lyr[0].nBand + "] albedo = " + this.albedo );
                    ap.icedropCounter--;
                }
/*               
                // start CO2 atmospheric warming after year 30000
                if ( ap.varyAirEmissivity ) {
                    if ( lyr[0].genericMaterialType() == 5  && ap.year > 290000 ) {
                        if ( lyr[0].layerEmissivity !=  setAirEmissivity ) {
                            lyr[0].layerEmissivity = setAirEmissivity;
                            lyr[0].layerAbsorptivity = setAirEmissivity;
                            System.out.println( ap.year + ":  lyr[0].layerEmissivity " + lyr[0].layerEmissivity  ); 
                        }
                    }
                }
*/                
//              if ( lyr[0].genericMaterialType() == 5 && ap.year < 10 ) {
//                     System.out.println( ap.year + ":  lyr[0].layerEmissivity " + lyr[0].layerEmissivity  ); 
//              }
  
                counter++;
            }
        }
        nIterations = counter;

        // new asteroid radius (if new layers have been added to it )
        this.radius = asteroidRadius();
        
        // report start and end of glaciation
        if ( this.airMixingFactor > 0 ) {
            if ( this.iceWaterDepth > 0 && this.previousIceWaterDepth == 0 ) {
//              System.out.println( ap.year + " lat " + this.nLatitude + " albedo " + this.albedo  + " previous albedo " + this.previousAlbedo );
                this.albedoChange = true;
                // change the pseudoMap latitude land to be either glaciated or unglaciated
                // so that new global air temperature can be found
//              System.out.println( ap.year + " glaciate" );
                ap.map.glaciateLand( this.nLatitude );
            } else if ( this.iceWaterDepth == 0 && this.previousIceWaterDepth > 0 )  {          
                this.albedoChange = true;
//              System.out.println( ap.year + " deglaciate" );
                ap.map.deglaciateLand( this.nLatitude );                    
            } else {
                this.albedoChange = false;
            }   
        } else {
            this.albedoChange = false;
        }
        this.previousIceWaterDepth = this.iceWaterDepth;

        // contiguous water layers are assumed to have same temperature (due to good convective mixing)
        if ( flagSetAirEmissivity && ap.year >= this.yearSetAirEmissivity ) {
            setAirEmissivities( setAirEmissivity );
            flagSetAirEmissivity = false;
        }

        if ( ap.waterRunoff ) {
//          removeAllMaterialTypeLayersAboveT( 3, 273.000000001 );       // melted ice or snow
        }
//      removeAllMaterialTypeLayersAboveT( 3, 373.5 );  // remove steam after slight pause to rise
        
        // remove any layer that's been flagged      
        if ( removeLayerFlag >= 0 ) removeLayer( removeLayerFlag );

        if ( setTSLtempEqualToSRtemp ) {
            setTSLtempEqualToSRtemp();
        }
        
        if ( resynchronise ) reSynchronise();
        
        if ( flagPrintColumn ) {
            printcsvColumn(0);
            flagPrintColumn = false;
        }
        
//      printhcmColumn(0);
    }

    double surfaceTemperature( ConductiveLayer layer0, ConductiveLayer layer1, ConductiveLayer layer2, int dayIndex, int timeStepLongDays ) {
        double heatflowrate = 0;
        double mixedTemperature;

        //  radiated heat from air (and only air) in layer above
        if ( layer0.genericMaterialType() == 5 ) {
            // use mixedTemperature rather than layer[0].currentTemperatureK 
            // when airMixingFactot > 0 for the purpose of changing only heat flow from below
            if ( this.airMixingFactor == 0.0 ) {
                // no air mixing
                heatflowrate += wattsRadiated( layer0.currentTemperatureK, layer0.layerEmissivity );      
                this.activeAirTemperature = layer0.currentTemperatureK;
//              if ( ap.year >  9500 && ap.year <  9501 ) System.out.println( ap.year + ": " + dayIndex + " Ta " + (float)this.activeAirTemperature );
            } else if ( this.airMixingFactor == 1.0 ) {
                // complete air mixing
                heatflowrate += wattsRadiated( ap.global.getDailyValue( dayIndex ), layer0.layerEmissivity );                            
                this.activeAirTemperature = ap.annualGlobalMeanTemperature;
                layer0.nextTemperatureK = this.activeAirTemperature;            // report temperature
            } else {
                // partial air mixing
                this.activeAirTemperature = ap.combined[ this.nLatitude  ].getDailyValue( dayIndex  );
                if ( this.iceWaterDepth > 0 ) {
                   mixedTemperature = ( 1.0 - this.airMixingFactor ) * ap.combined[ this.nLatitude  ].getDailyValue( dayIndex  ) + ( this.airMixingFactor ) * ap.global.getDailyValue( dayIndex ) ;
//                 mixedTemperature = ( 1.0 - this.airMixingFactor ) * ap.snow[ this.nLatitude  ].getMeanDailyValue( dayIndex-timeStepLongDays, dayIndex  ) + ( this.airMixingFactor ) * ap.global.getMeanDailyValue( dayIndex-timeStepLongDays, dayIndex ) ;
                   if ( ap.year > 12505 && ap.year < 12506 ) System.out.println( ap.year + ": " + (float)ap.snow[ this.nLatitude  ].getDailyValue( dayIndex  ) + " " + (float)ap.snow[ this.nLatitude  ].getMeanDailyValue( dayIndex-timeStepLongDays, dayIndex) + " ~ " + (int)this.iceWaterDepth + " " + (float)horizontalProportionOfSnow + " " + (float)ap.combined[ this.nLatitude  ].getDailyValue( dayIndex  ));
//                  if ( ap.year > 12005 && ap.year < 12006 ) System.out.println( ap.year + ": " + dayIndex + " - " + ap.global.getDailyValue( dayIndex ) + " " + ap.global.getMeanDailyValue( dayIndex-timeStepLongDays, dayIndex ) );
                } else {
                    mixedTemperature = ( 1.0 - this.airMixingFactor ) * ap.combined[ this.nLatitude ].getDailyValue( dayIndex  ) + ( this.airMixingFactor ) * ap.global.getDailyValue( dayIndex ) ;
//                  mixedTemperature = ( 1.0 - this.airMixingFactor ) * ap.land[ this.nLatitude ].getMeanDailyValue( dayIndex-timeStepLongDays, dayIndex  ) + ( this.airMixingFactor ) * ap.global.getMeanDailyValue( dayIndex-timeStepLongDays, dayIndex ) ;
                    if ( ap.year > 100 && ap.year < 101 ) System.out.println( ap.year + ": " + dayIndex + " - " + ap.global.getDailyValue( dayIndex ) + " " + ap.global.getMeanDailyValue( dayIndex-timeStepLongDays, dayIndex ) );
                }
                heatflowrate += wattsRadiated( mixedTemperature, layer0.layerEmissivity );                            
                this.activeAirTemperature = mixedTemperature;
                layer0.nextTemperatureK = this.activeAirTemperature;            // report temperature
            }
            // store the active air temperature used in surface temperature calculation
            ap.active[ this.nLatitude ].setDailyValue( this.activeAirTemperature, dayIndex );
        }

        // conductive heat flow from layer below
        heatflowrate += wattsConducted( layer2.currentTemperatureK, layer1.currentTemperatureK, layer2.layerConductivity, layer2.height    , layer2.area );

        // solar irradiation
        heatflowrate += this.solarGain * ( 1.0 - this.albedo );       //

        // find temperature of surface assuming emissivity of 1.0
        double t = Math.pow(( heatflowrate / this.stefanBoltzmannConstant ), 0.25 );    
//      System.out.println( ap.year + " surface hfr3= " + (float)heatflowrate + " t " + (float)t );

        return( t );
    }
    
    // air temperature over chequerboard of snow and land  ranges between the two
    double terrainModifiedAirTemperature( int dayIndex ) {
        double ta = this.horizontalProportionOfSnow * ap.snow[ this.nLatitude  ].getDailyValue( dayIndex  )
                 +  ( 1.0 - this.horizontalProportionOfSnow ) * ap.land[ this.nLatitude  ].getDailyValue( dayIndex  );  
        return( ta );
    }


    // conductive heat flow
    double wattsConducted( double t1, double t2, double k, double d, double a ) {
        double watts =  ( t1 - t2 ) * k * a / d;
        return( watts );
    }

    // Boltzmann
    double wattsRadiated( double t, double e ) {
        double s = 5.6699E-8;                                     // Stefan's constant
        double watts = e * s * Math.pow( t, 4.0 );
        return( watts );
    }


    // calculate conduction heat flow rate (Watts)
    // k = conductivity, a = area, d = depth, t1-t2 temp difference
    double conductiveHeatFlow( ConductiveLayer l1, double k, double a, double d, double t1, double t2 ) {
        double heatflowrate = (t1 - t2) * k * a / d;
//      if ( l1.nBand == 30 && l1.materialType == 3 ) System.out.println( "k a d R t1 t2 hfr " + (float)k + ", " + (float)a + ", " + (float)d + ", " + (float)(d/(k*a)) + ", " + (float)t1 + ", " + (float)t2 + ", " + (float)heatflowrate );
        return ( heatflowrate );
    }

    // calculate conduction heat flow rate (Watts) between layer centres
    // thermal resistance between layers = r1/2 + r2/2
    double conductiveHeatFlow( ConductiveLayer l1, double r1, double r2, double t1, double t2 ) {
        double rm = ( r1 + r2 ) / 2.0;
//      System.out.println( "rm " + rm + " r1 " + r1 + " r2 " + r2 );
        double heatflowrate = (t1 - t2) / rm;
 //     if ( l1.nBand == 30 && l1.materialType == 3 ) System.out.println( "k r1 rm t1 t2 hfr " + (float)l1.layerConductivity + ", " + (float)r1 + ", " + (float)rm + ", " + (float)t1 + ", " + (float)t2 + ", " + (float)heatflowrate  );
//      System.out.println( "rm " + rm + " hfr " + heatflowrate  );
        return ( heatflowrate );
    }

    // from https://en.wikipedia.org/wiki/Planck%27s_law
    // freq = frequency, 1/s
    double planckBlackBodyFunction( double tK, double freq ) {
        double B = 0;                       // spectral radiance
        double h = 6.626070040E-34;         // Planck constant Js
        double c = 299792458;               // velocity of light in vacuum or air m/s
        double kB = 1.38064852E-23;         // Boltzmann constant J/K
        double f1 = ( 2.0 * h * freq * freq * freq ) / ( c * c );
        double f2 = ( h * freq ) / ( kB * tK );
        B = f1 / ( Math.pow( Math.E, f2 ) - 1.0 );
        return( B );
    }

    // source https://www.acs.org/content/acs/en/climatescience/atmosphericwarming/singlelayermodel.html
    // (incoming) (1 – a)Save = (1 – e)sTp4 + esTa4 (outgoing) . . . . . . . . .(1)
    // (absorbed) esTp4 = 2 esTa4 (emitted) . . . . . . . . . . . . . . . . . . (2)
    // Ta4 = (1/2) Tp4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . .(3)
    // We can substitute for Ta4 in the planetary balance equation (1) and solve for Tp
    // Tp = {[2 (1 – a)Save]/ [s(2 – e)]}1/4
    // tested: results are same as in ACS
    void ACSalgebra() {
        double save = 342.0;                    // W/m2 mean solar radiation
        double albedo = 0.3;                    // earth mean albedo
        double sigma = 5.67E-8;                 // Stefan-Boltzmann constant, 5.67·10–8 W·m–2·K–4
        double emissivity, ta;
        System.out.println( "ACS single layer atmosphere" );
        for ( emissivity=0; emissivity<=1.0; emissivity += 0.1 ) {
            double tp = Math.pow ( ( 2.0 * (1.0 - albedo) * save ) / ( sigma * ( 2.0 - emissivity ) ), 0.25 );
            ta = tp * Math.pow( 0.5, 0.25 );
            System.out.println( "air emissivyty " + (float)emissivity + " surface temperature " + (float)tp + " air temperature " + (float)ta );
        }
    }
    
    // set thin surface layer temperature equal to surface rock temperature in layer below
    // this stops heat flow from surface rock to thin surface layer
    void setTSLtempEqualToSRtemp() {
        double t;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        // layer, and set fixTemperature flag
        while( itr.hasNext() ){
            layer = itr.next();
            if ( layer.nBand == surfaceRockBand ) {
                t = layer.currentTemperatureK;
//              System.out.print( "layer " + layer.nBand  + " temperature " + (float)t );
                layer = itr.previous();  // need to go back twice to get previous layer
                layer = itr.previous();
                layer.currentTemperatureK = t;
//              System.out.println( " becomes layer " + layer.nBand  + " temperature "  );
                break;
            }
            
        }                
    }
    
    void setInitialTemperatures() {
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        // set initial Temperature to current temperature
        while( itr.hasNext() ){
            layer = itr.next();
            layer.initialTemperature = layer.currentTemperatureK;
        }                
    }
    
    void reduceLayerHeight( ConductiveLayer layer, double heatGain, double latentHeat, double layerDensity ) {
        double meltMass = heatGain / latentHeat;
        double meltVolume = meltMass / layerDensity;
        double meltHeight = meltVolume;             // in a 1m x 1m block, melt height = melt volume
        layer.mass = layer.mass - meltMass;
        layer.volume = layer.volume - meltVolume;
        layer.height = layer.height - meltHeight;
        System.out.println( "band " + layer.nBand + " layer height " + layer.height + " layer temp " + layer.currentTemperatureK  + " layer init temp " + layer.initialTemperature  );
        if ( layer.height <=0 ) removeLayerFlag = layer.nBand;
    }

    // construct a growth/decay curve with time constant rc0 from ttarget temperature layer to tf final temperature
    void setExponentialTemperatures( double rc0, double tf, double ttarget ) {
        double ds = layerTemperatureDepth( ttarget );
        double d0 =  Math.log( tf - ttarget ) / tf;
        System.out.println( ds + " " + d0  );

        double tlayer, dl;
        boolean found = false;
        double depth = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        // find thin surface layer, and measure depth from there
        while( itr.hasNext() ){
            layer = itr.next();
            if ( layer.materialType == 0 ) {
                depth = 0;
            } else {
                depth += layer.height / 2.0;
                if ( depth == ds ) {
                    found = true;
                    break;
                } else {
                    depth += layer.height / 2.0;
                }
            }
        }

        if ( found ) {
            while( itr.hasNext() ){
                layer = itr.next();
                depth += layer.height / 2.0;
                dl = depth - ds + d0;
                tlayer = exrponentialTemperature( tf, rc0, dl );
                System.out.println( "depth " + depth + " temperature " + tlayer );
                layer.currentTemperatureK = tlayer;
                layer.nextTemperatureK = tlayer;
                depth += layer.height / 2.0;
            }
        }


    }


    // function to generate a temperature approaching tmax at given depth (km)
    // suggested value for rc time constant = 1000. Smaller values raise temperatures, larger lower them.
    double exrponentialTemperature( double tmax, double rc, double depth  ) {
        return ( tmax - tmax * Math.pow ( Math.E, -depth/rc ) );
    }
    
    void fixBandTemperature( boolean fix, int nband ) {
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        // layer, and set fixTemperature flag
        while( itr.hasNext() ){
            layer = itr.next();
            if ( layer.nBand == nband ) {
                if ( fix ) {
                    layer.fixTemperature();
                } else {
                    layer.unfixTemperature();
                }
            }
        }        
    }

    // thin surface layer albedo is usually -1 to indicate the albedo is that of the material bdenath it.
    void setThinSurfaceBandAlbedo( double a ) {
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        // layer, and set fixTemperature flag
        while( itr.hasNext() ){
            layer = itr.next();
            if ( layer.genericMaterialType() == 0 ) {
                layer.albedo = a;
                break;
            }
        }        
    }

    // set active calculation band range true, remainder false
    void setActiveCalculationLayers( int startBand, int stopBand ) {
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        while( itr.hasNext() ){
            layer = itr.next();
            layer.setActiveCalculation( false );
            if ( layer.nBand <= startBand && layer.nBand >= stopBand ) {
                layer.setActiveCalculation( true );
            }
        }        
    }

    // set ActiveCalculation flag in layers <= start band and >= stop band to desired state
    // leaving other layers unchanged
    void setActiveCalculationLayers( int startBand, int stopBand, boolean state ) {
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        while( itr.hasNext() ){
            layer = itr.next();
            if ( layer.nBand <= startBand && layer.nBand >= stopBand ) {
                layer.setActiveCalculation( state );
            }
        }        
    }
    
    // find the depth below thin surface layer of layer at targetTemperature
    double layerTemperatureDepth( double targetTemperature ) {
        boolean found = false;
        double depth = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        // find thin surface layer, and measure depth from there
        while( itr.hasNext() ){
            layer = itr.next();
            if ( layer.materialType == 0 ) {
                depth = 0;
            } else {
                depth += layer.height / 2.0;
                if ( layer.currentTemperatureK >= targetTemperature ) {
                    break;
                } else {
                    depth += layer.height / 2.0;
                }
            }
        }

        return( depth );
    }

    
    double volumeAtmosphere( double massAtmosphere, double densityAtmosphere ) {
        double volume = 0;
        volume = massAtmosphere / densityAtmosphere;
        return( volume );
    }

    double heightAtmosphere( double volumeAtmosphere ) {
        double h = 0;
        double volumeColumn = this.sphereVolume( this.referenceRadius );
        return( h );
    }

    double volumeOneSquareMetre( ) {
        double volume = 0;
        return( volume );
    }
  
    // area of circular strip at column latitude
    double areaAtLatitude() {
        double stripWidthDegrees = 90.0 / ap.nLatitudes;
        double lat1 = this.columnLatitude + stripWidthDegrees / 2.0;
        double lat2 = this.columnLatitude - stripWidthDegrees / 2.0;
        double a =2.0 * Math.PI * this.referenceRadius * this.referenceRadius * ( Math.sin( lat1 * this.degreesToRadians ) - Math.sin( lat2 * this.degreesToRadians ) );
        return( a );
    }
    
    // set all air layer emissivities and absorptivities
    void setAirEmissivities( double e ) {
        double e0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        while( itr.hasNext() ){
            layer = itr.next();
            if ( layer.genericMaterialType() == 5 ) {
                e0 = layer.layerEmissivity;;
                layer.layerEmissivity = e;
                layer.layerAbsorptivity = e;
                System.out.println( ap.year + " layer " + layer.nBand + " air emissivity changed from " + e0 + " to " + e );
            }
        }
        
    }

    // set up a geothermal gradient from the thin surface layer down to a maximum depth
    void setGeothermalGradient( double tThinSurface, double degreesPerKm, double maxDepth ) {
        boolean found = false;
        double depth;
        double thinSurfaceRadius = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        // find thin surface layer
        while( itr.hasNext() ){
            layer = itr.next();
            thinSurfaceRadius = layer.radius;
            if ( layer.materialType == 0 ) {
                layer.currentTemperatureK = tThinSurface;
                layer.nextTemperatureK = tThinSurface;
                found = true;
                break;
            }
        }

        if ( found ) {
            while( itr.hasNext() ) {
                layer = itr.next();
                depth = thinSurfaceRadius - layer.radius;
                depth += layer.height / 2.0;                                        // depth of middle of layer'
                depth = depth / 1000.0;                                             // depth in km
                if ( depth <= maxDepth ) {
                    layer.currentTemperatureK = tThinSurface + depth * degreesPerKm;
                    layer.nextTemperatureK = layer.currentTemperatureK;
                } else {
                    break;
                }
            }
        } else {
            System.out.println( "thin surface layer not found " );
        }

    }
    
    double terrainAlbedo( double terrainHeight, double terrainWidth, double  underlyingTerrainAlbedo, double snowDepth, double snowAlbedo, int dayIndex ) {
        double terrainAlbedo = underlyingTerrainAlbedo;
        if ( snowDepth > 0 ) { 
            if ( this.iceMeltingAtBase ) { 
                if ( terrainHeight <= 0 ) terrainHeight = 0.001;
                double snowVolume = snowDepth * terrainWidth * terrainWidth;
                double valleySnowDepth = Math.sqrt( ( snowVolume * terrainHeight ) / ( terrainWidth * terrainWidth ) );
                double valleySnowWidth = valleySnowDepth * terrainWidth / terrainHeight;
                horizontalProportionOfSnow = valleySnowWidth / terrainWidth;
                if ( horizontalProportionOfSnow > 1.0 ) horizontalProportionOfSnow = 1.0;
                if ( horizontalProportionOfSnow < 0.0 ) horizontalProportionOfSnow = 0.0;
                terrainAlbedo = horizontalProportionOfSnow * snowAlbedo + (1 -horizontalProportionOfSnow) * underlyingTerrainAlbedo;
        //      System.out.println( "horizontalProportionOfSnow " + horizontalProportionOfSnow );
            } else {
                // thin snow has lower albedo
                if ( snowDepth > 50 ) { 
                    terrainAlbedo = snowAlbedo;
                } else {
                    terrainAlbedo = snowAlbedo / 2;
                }
            }
        }
        
        // use snow coverage to update pseudoMap and get new combined air temperature
        ap.combinedAirTemperature( this.nLatitude, this.horizontalProportionOfSnow, dayIndex );
        
        return( terrainAlbedo );
    }

    
    // s = snow depth, hf = forest height, as = albedo of snow or ice, af = albedo of forest trees
    double forestAlbedo( double s, double hf, double as, double af ) {
        double fAlbedo, at, ar;
        
        // albedo of forest is albedo of underlying rock if forest height = 0
        // and falls to albedo of dense forest when forest height > 5.0 m
        if ( hf < 5.0 ) {
            ar = 0.4;                                                           // albedo of underlying rock
            at = ( 1.0 - hf / 5.0 ) * ar + ( hf / 5.0 ) * af;
        } else {
            at = af;
        }
        
        // at = lbedo of forest when sno height = 0
        // as = albedo of snow
        if ( s < hf ) {
            fAlbedo = ( s / hf ) * as + ( 1.0 - s / hf ) * at;
        } else {
            fAlbedo = as;
        }
        
        return( fAlbedo );
    }
    
    
    // add up all the layer masses
    double massOfAllLayers() {
        double totalMass = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        while( itr.hasNext() ){
            layer = itr.next();
            totalMass += layer.mass;
        }
        return( totalMass );
    }

    double massOfEntireAsteroid() {
        double totalMass = 0;
        double massSlice = this.massOfAllLayers();
        double asteroidSurfaceArea = this.sphereSurfaceArea( this.referenceRadius );
        totalMass = massSlice * asteroidSurfaceArea;
        return( totalMass );
    }

    // assumes only one set of contiguous layers of water
    double equaliseContiguousWaterLayerTemperatures() {
        int n = 0;
        double tmean;
        double v = 0;
        double vt = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;

        itr=llayer.listIterator();
        while(itr.hasNext()){
            layer = itr.next();
            if ( layer.material.isWater( layer.currentTemperatureK) ) {
                v += layer.volume;
                vt += layer.currentTemperatureK * layer.volume;
                n++;
            }
        }
        tmean = vt / v;

        if ( n > 1 ) {
            itr=llayer.listIterator();
            while(itr.hasNext()){
                layer = itr.next();
                if ( layer.material.isWater( layer.currentTemperatureK) ) {
                    layer.currentTemperatureK = tmean;
                }
            }
        }

        return( tmean );
    }



    // Measures ice presence above rock layers
    // Called with control = 0, each period during which ice (mt 3 ) is present is added to total
    // Called with ccontrol = 1, total/period is printed out, and total reset
    // assumes constnnt period
    double measureDegreeOfGlaciation( double gtotal, double period, double timestep, int control ) {
        double degree = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        this.iceDepth = 0;
        while( itr.hasNext() ){
            layer = itr.next();
            // record glaciations
            if ( layer.material.isIce( layer.currentTemperatureK) ) {
                degree = timestep;
                iceDepth += layer.height;
            }
            // quit when rock or iron reached
            if ( layer.materialType == 2 || layer.materialType == 1 ) {
                break;
            }
        }
//      System.out.println( degree + " " + gtotal );
        gtotal += degree;
        if ( control == 1 ) {
//          System.out.println( "% glaciation " + ( 100.0 * gtotal / period ) + " over period "+ period );
//          gtotal = 0;
        }
        return( gtotal );
    }


    // set radioactive heat produced in all layers of material type mt
    void setRadioactiveHeatGeneration( double mt, double wattsPerCubicMetre ) {
        double totalHeatProduced = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        while( itr.hasNext() ){
            layer = itr.next();
            if ( layer.materialType == mt  ) {
                layer.heatProduced = layer.volume * wattsPerCubicMetre;
                totalHeatProduced += layer.heatProduced;
            }
        }
        System.out.println( "total radioactive heat produced " + totalHeatProduced  + " W/m^3");
    }

    // using an array of temperatures, set layer temperatures from top down
    void setLayerTemperatures( double temps[], double multiplier ) {
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        int i = 0;
        while( itr.hasNext() ){
            layer = itr.next();
            layer.currentTemperatureK = temps[i++] * multiplier;
        }
    }
    
    
    void restoreInitialTemperatures() {
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        while( itr.hasNext() ){
            layer = itr.next();
            layer.restoreInitialTemperature();
        }
    }
    

    // using existing temperatures, adjust layer temperatures using multiplier
    void adjustLayerTemperatures( double multiplier ) {
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        int i = 0;
        while( itr.hasNext() ){
            layer = itr.next();
            layer.currentTemperatureK = layer.currentTemperatureK * multiplier;
        }
    }


    // recalculate all the radii of layers above startband,
    // using layer volume to find new heights and radii
    void recalculateRadii( int startBand ) {
        int count = 0;
        boolean started = false;
        double subsphereRadius, subsphereVolume, subsphereArea, subsphereSliceWidth, sliceFraction, layerVolume;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        subsphereRadius = this.referenceRadius;
        subsphereArea = this.sphereSurfaceArea( subsphereRadius );
        subsphereVolume = this.sphereVolume( subsphereRadius );
        sliceFraction = 1.0 / subsphereArea;

//      System.out.println( "recalculate radii " );

        // first go down the layers to the start layer
        itr = llayer.listIterator();
        while(itr.hasNext()){
            layer = itr.next();
            if ( layer.nBand == startBand ) {
                started = true;
                break;
            }
        }

        if ( itr.hasPrevious() ){
            layer = itr.previous();
            subsphereRadius = layer.radius;
            subsphereVolume = this.sphereVolume( subsphereRadius );
            subsphereArea = this.sphereSurfaceArea( subsphereRadius );
            subsphereSliceWidth = layer.width;
            sliceFraction = subsphereSliceWidth * subsphereSliceWidth / subsphereArea;
//          System.out.println( layer.nBand + "- " + layer.radius + " " + layer.width );
        }
        // go up the layers from the centre outwards to find the start band
        while( itr.hasPrevious() ){
            layer = itr.previous();
            if ( started ) {
                // layer mass is fixed, and layer density may be variab;e
                // layer volume is calculated from layermass and density
                layer.volume = layer.mass / layer.layerDensity;

                layerVolume = layer.volume / sliceFraction;  //
                layer.radius = this.layerRadius( layerVolume, subsphereVolume );
                layer.height = layer.radius - subsphereRadius;
                if (layer.height < 0 ) layer.height = 0;
                layer.width = layer.radius / this.referenceRadius;
                layer.length = layer.radius / this.referenceRadius;
//              System.out.println( layer.nBand + ": r " + layer.radius + " w " + layer.width + " v " + layer.volume);
            }
            subsphereRadius = layer.radius;
            subsphereVolume = this.sphereVolume( subsphereRadius );
            subsphereArea = this.sphereSurfaceArea( subsphereRadius );
            subsphereSliceWidth = layer.width;
            sliceFraction = subsphereSliceWidth * subsphereSliceWidth / subsphereArea;
        }
    }


    // remover a single contiguous set of layers of material type1 (e.g. all ice layers in ice sheet )
    // for single layer removal, set both types to be the same type.
    int removeAllMaterialTypeLayersAboveT( double type1, double t ) {
        boolean layerRemoved =  false;
        int count = 0;
        int nremoved = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        if ( ap.year > 12000 ) System.out.println( ap.year + " start removeAllMaterialTypeLayersAboveT" );

        double heightRemoved = 0;
        itr=llayer.listIterator();
        while(itr.hasNext()){
            layer = itr.next();
            layerRemoved = false;
            // remover material in 0.1 type range (e.g. 3.00 and 3.05, ice and snow)
            if ( layer.materialType >= (int)type1 && layer.materialType < ((int)type1+0.2) && layer.currentTemperatureK > t ) {
                itr.remove();
                heightRemoved += layer.height;
                nlayers--;
                nremoved++;
                layerRemoved = true;
                System.out.println( ap.year + " removed layer band " + (int)layer.nBand  + " at " + layer.currentTemperatureK + " K " );
  
                // start snowfall 10,000 yrs later
//              ap.eventHandler.precipitstionCZYN = (int)(ap.eemianCZYN + ap.year +  5000.0);
//              ap.eventHandler.precipitstionCZYN = 1000.0 * ( (int)( ap.eventHandler.precipitstionCZYN / 1000.0 ) );
                System.out.println( "ap.eventHandler.precipitstionCZYN " + ap.eventHandler.precipitstionCZYN );
                ap.eventHandler.insertEvent( ap.eventHandler.precipitstionCZYN );
                  
            }   //
            count++;
//          if ( !layerRemoved && nremoved > 0  ) break;
        }

        // now go back up to surface, lowering radii of layers above
        if ( nremoved > 0 ) {
            layer = itr.previous();
            while( itr.hasPrevious() ){
                layer = itr.previous();
//              System.out.println( "lowered layer type " + (int)layer.materialType + " by " + heightRemoved );
                layer.radius -= heightRemoved;
                count--;
            }
        }

        if ( nremoved > 0 ) {
            System.out.println( nremoved + " layers removed " );
//          System.out.println( type1 + " number of bands " + setBandNumbers() + " nremoved " + nremoved );
            reSynchronise();            // renumber layers
            
            if ( ap.constantAddLayerDepther ) {
                addLayer( ap.column[ ap.nviewLatitude ].thinSurfaceBand, 270,      ap.addLayerDepth, ap.addLayerMaterial );
            }
        }

        if ( ap.year > 12000 ) System.out.println( "end removeAllMaterialTypeLayersAboveT" );

        return( nremoved );
    }

    // add new layer at nBand nb, of material mt, temperature t.
    // adds UNDER layer nb
    void addLayer( int nb, double t, double h, double mt ) {
        double r, w, q;
        
        q = 0;
        ConductiveLayer layer, lyr;
        ListIterator<ConductiveLayer> itr = null;
//      System.out.println( "add layer" + nb );

        itr=llayer.listIterator();
        while(itr.hasNext()){
            layer = itr.next();
            r = layer.radius;
            w = layer.width;
            if ( layer.nBand == nb ) {
                // use 5 year tick period
                lyr =  new ConductiveLayer( this, r+h, w, h, t, mt, 1.0, 0 );
                itr.add( lyr );
                System.out.println( ap.year + " added layer of material " + mt + " at band" + nb );
                System.out.println( "layer temperature " + lyr.currentTemperatureK + " heat content per kg" + lyr.layerHeatContentPerKg );
//              q = lyr.pt.temperature( lyr.layerHeatContentPerKg );
//              System.out.println( "layer temperature from heat content " + q );
//              System.out.println( "layer heat content from temperature " + lyr.pt.heatContentPerKg( q ) );
            }
        } 

        reSynchronise();
//      printhcmColumn( 0 );
    }


    // Add volume v at temperature t into nband nb
    // All higher layers need their height recalculated (but havent yet)
    void addIntopLayer( double t, double v, int nb ) {
        double r, w, vnew, tnew;
        ConductiveLayer layer, lyr;
        ListIterator<ConductiveLayer> itr = null;
        itr = llayer.listIterator();
        if (itr.hasNext() ) {
            layer = itr.next();
            if ( layer.nBand == nb ) {
                vnew = layer.volume + v;
                tnew = meanTemperature( layer.volume, layer.currentTemperatureK, v, t );
                layer.volume = vnew;
                layer.currentTemperatureK = tnew;
                layer.height = layer.volume / ( layer.width * layer.width );  // this needs to be correctly calculated
            }
        }

        reSynchronise();
    }

    // look for H2O layer (materialType 3.xxx) 
    // and if found add more ice/snow/water
    // else create new layer
    void addIceLayer( double t, double h, double mt ) {
        double r, w, h1, h2, m1, m2;
        ConductiveLayer layer, lyr;
        ListIterator<ConductiveLayer> itr = null;
//      System.out.println( "start addIceLayer"  );

        itr=llayer.listIterator();
        while(itr.hasNext()){
            layer = itr.next();
            r = layer.radius;
            w = layer.width;
            if ( iceWaterDepth == 0 ) {
                // if no water layer, create one beneath thin surface layer
                if ( layer.materialType == 0 ) {
                    lyr =  new ConductiveLayer( this, r+h, w, h, t, mt, 1.0, 0 );
                    itr.add( lyr );
//                  System.out.println( "lyr.layerHeatContent  " + lyr.layerHeatContent  );
                    ap.addedIceMass = lyr.mass;                                 // mass to be added to grow layer
                    ap.addedIceHeatContentPerKg = lyr.layerHeatContentPerKg;    // heat content per Kg of added snow
                    layer.layerHeatAdded = lyr.layerHeatContent;    // added heat isn't just heat flow
//                  System.out.println( ap.year + " added brand new layer of snow/ice " + mt + " at band" + layer.nBand );   
//                  System.out.println( "brand new addedIceMass " + ap.addedIceMass + " addedIceHeatContent " + ap.addedIceHeatContent );
                }
            } else {
                // if water layer, add to it, finding new mean temperature
                // assumes adding H2O to H2O
                if ( layer.materialType >= 3.0 && layer.materialType < 3.2 ) {
                    // find new layer mass and temperature
 //                 layer.mass += ap.addedIceMass;
 //                 layer.layerHeatAdded += ap.addedIceHeatContent;                     // added heat isn't just heat flow
 //                 layer.layerHeatContentPerKg += ap.addedIceHeatContent / layer.mass;
 //                 layer.layerHeatContent = layer.layerHeatContentPerKg * layer.mass;
                    m1 = layer.mass;
                    m2 = ap.addedIceMass;
                    h1 = layer.layerHeatContentPerKg;
                    h2 = ap.addedIceHeatContentPerKg;
                    layer.layerHeatContentPerKg = ( h1 * m1 + h2 * m2 ) / ( m1 + m2 );
                    layer.mass = ( m1 + m2 );
                    layer.layerHeatContent = layer.layerHeatContentPerKg * layer.mass;
//                  System.out.println( "layer.layerHeatContent" + (float)layer.layerHeatContent + "  added mass " + (float)ap.addedIceMass + " heatcontent " + (float)ap.addedIceHeatContent );
                    layer.currentTemperatureK = layer.pt.temperature( layer.layerHeatContentPerKg ); 
                    // redimension layer and layer above
//                  this.recalculateRadii( layer.nBand );
                    //
//                  System.out.println( ap.year + " new snow/ice layer heat content " + (float)layer.layerHeatContent  + " temperature " + (float)layer.currentTemperatureK);
                    break;
                }
            }
        }

//      System.out.println( "resynchronise"  );
        reSynchronise();
//      printhcmColumn( 71 );
//      System.out.println( "end addIceLayer"  );
        
    }

    // remove nBand layer nb;
    void removeLayer( int nb ) {
        double snowMass = 0;
        double pe = 0;
        int uni = 0;
        int nremoved = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();
        while( itr.hasNext() ){
            layer = itr.next();
            if ( layer.genericMaterialType() == 3 ) snowMass += layer.mass;
            if ( layer.nBand == nb ) {
                if ( layer.genericMaterialType() == 3 ) {
                    // add potential energy of overlying snow to layer below removed snow layer
                    pe = snowMass * layer.height * 9.81;
                    if ( pe > 0 ) addHeatToLayer( nb-1, pe  );
//                  layerAbove.layerHeatContent += pe;
//                  layerAbove.layerHeatContentPerKg = layerAbove.layerHeatContent / layerAbove.mass;
//                  System.out.println( "P.E. added " + (float)pe + " layer heat content " + (float)layerAbove.layerHeatContent  );
                }
                itr.remove();
                nlayers--;
                nremoved++;
            }
//          layerAbove = layer;
        }
                
//      System.out.println( ap.year + " band " + nb + ": " + uni + " removed on request ");
        removeLayerFlag = -1;   // flag no layer to be removed
        resynchronise = true;
    }
    
    // add heat to layer nb
    void addHeatToLayer( int nb, double heat ) {
        ConductiveLayer layer, layerAbove;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();
        layerAbove = itr.next();
        while( itr.hasNext() ){
            layer = itr.next();
            if ( layer.nBand == nb ) {
                // add potential energy of overlying snow to layer bn
                layer.layerHeatContent += heat;
                layer.layerHeatContentPerKg = layer.layerHeatContent / layer.mass;
//              System.out.println( "P.E. added " + (float)heat + " layer heat content " + (float)layer.layerHeatContent  );
            }
        }
    }

    // find band number of layer of higher densiy than material mt
    int findHigherDensityNBand( double mt ) {
        int nb = -1;
        Material mtrl = new Material( this, mt );
        ConductiveLayer layer, lyr;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();
        while(itr.hasNext()){
            layer = itr.next();
            System.out.println( layer.nBand + ": " + layer.layerDensity + " > " + mtrl.density + " " + nb );
            if ( layer.layerDensity >= mtrl.density ) {
                nb = layer.nBand;
                break;
            }
        }
        return( nb );
    }

    double meanTemperature( double v1, double t1, double v2, double t2 ) {
        double tmean = ( v1 * t1 + v2 * t2 ) / ( v1 + v2 );
        return( tmean );
    }

    // find mean temperature of whole asteroid
    // this is the temperature that surface rocks would reach beneath a perfect insulator
    double meanAsteroidTemperature() {
        double vtot = 0;
        double vttot = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();
        while(itr.hasNext()){
            layer = itr.next();
            vttot += layer.volume * layer.currentTemperatureK;
            vtot += layer.volume;
        }
        vttot = vttot / vtot;
        return( vttot );
    }

    double radiusConstantVolumeLayer( int layer, double layerVolume ) {
        double lRadius, vol;
        double f = 4.0 * Math.PI / 3;
        double cubeRoot = 1.0 / 3.0;
        vol = (layer+1) * layerVolume;
        lRadius = Math.pow( ( vol / f ), cubeRoot );
        return( lRadius);
    }

    // return the radius of the innermost layer of a sphere of radius r with n layers in it.
    double equalLayerVolumeSphere( int numLayers, double sphereRadius ) {
        double r1;
        double r[] = new double[ numLayers ];
        // In a sphere with centre layer radius R1,
        // the radius of the nth layer, Rn, is given by Rn^3 = n * R1^3
        r1 = Math.pow( ( sphereRadius * sphereRadius * sphereRadius / numLayers ), 0.333333 );
        return( r1 );
    }


    double sphereVolume( double r ) {
        double volume = 1.3333333333 * Math.PI * r * r * r;
        return( volume );
    }

    double sphereSurfaceArea( double r ) {
        double a = 4.0 * Math.PI * r * r;
        return( a );
    }


    double sphereRadius( double vol ) {
        // sphere volume = 4/3 .pi. r^3
        double fn = ( vol * 3.0 ) / ( 4.0 * Math.PI );
        double r = Math.pow( fn, 0.33333333 );
        return( r );
    }

    // find outer radius of layer sitting above an inner subsphere
    double layerRadius( double layerVolume, double volumeSubsphere ) {
        double radius = 0;
        double fn = 3.0 * ( layerVolume + volumeSubsphere ) / ( 4.0 * Math.PI );
        radius = Math.pow( fn, 1.0/3.00 );
        return( radius );
    }

    // find highest band number of nmaterial layers
    int countMaterialBands( int nmaterial ) {
        int count = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();
        while(itr.hasNext()){
            layer = itr.next();
            if ( layer.materialType == nmaterial ) {
                count++;
            }
        }
        return( count );
    }

    double findIcePlusWaterDepth() {
        double depth = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();
        while(itr.hasNext()){
            layer = itr.next();
            if ( layer.material.isIce( layer.currentTemperatureK) || layer.material.isWater( layer.currentTemperatureK) ) {
//              System.out.println( layer.nBand + " " + layer.materialType );
                depth += layer.height;
            }
        }
//      System.out.println( "ice depth " + depth );
        return( depth );

    }

    // find highest band number of nmaterial layers
    int materialBandNumber( int nmaterial ) {
        int count = -1;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();
        while(itr.hasNext()){
            layer = itr.next();
            if ( layer.materialType == nmaterial ) {
                count = layer.nBand;
                break;
            }
        }
        return( count );
    }

    // layers are counted from top surface (layer 0) of asteroid downwards.
    // bands are counted from asteroid centre (band 0) upwards.
    // This needs to be called whenever layers are added to or subtracted from the asteroid
    // This should also be called fairly regularly to update graphic display temperatures and materials in layers
    int setBandNumbers() {
        boolean flag1 = false;
        boolean flag2 = false;
        boolean flag3 = false;
        int n = 0;
        int count = nlayers - 1;
        double totalMass = 0;
        
        this.topLayerOfAtmosphere = count;

        iceWaterDepth = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        // seems that list iterator can only go back to revious elemnts if it's been forrward to next elemnts.
        while( itr.hasNext() ){
            layer = itr.next();

            // store data for graphics display
            graphicLayer[n][0] = layer.nBand;
            graphicLayer[n][1] = layer.materialType;
            graphicLayer[n][2] = layer.radius;
            graphicLayer[n][3] = layer.height;
            graphicLayer[n][4] = layer.currentTemperatureK;
            graphicLayer[n][5] = layer.phase;
            graphicLayer[n][6] = layer.upwardHeatFlowRate;
            graphicLayer[n][7] = layer.uniqueNumber;
            graphicLayer[n][8] = layer.material.colourIndex[ layer.phase ];
            graphicLayer[n][9] = layer.initialTemperature;
            
            
            layer.nBand = count;
            totalMass += layer.mass;

            // find thin surface band for solar irradiation
            if ( layer.materialType == 0 && !flag1 ) {
                this.thinSurfaceBand = layer.nBand;
                this.surfaceBand = layer.nBand - 1;  // old surface band is band underneath thin surface band
//              System.out.println( "thin layer found " + layer.nBand );
                flag1 = true;
            }
            // find top rock layer
            if ( layer.materialType == 2 && !flag2 ) {
                this.surfaceRockBand = layer.nBand;
//              System.out.println( "surf layer found " + layer.nBand );
                flag2 = true;
            }

            // record temperature of surface band under thin surface band
            if ( layer.nBand == surfaceBand ) {
                this.surfaceBandTemperature = layer.currentTemperatureK;
            }

            // find ice + water depth
            if ( layer.materialType >= 3.0 && layer.materialType < 3.2 ) {
                iceWaterDepth += layer.height;
            }

            if ( layer.genericMaterialType() == 5 && !flag3 ) {
                this.airLowestBand = layer.nBand;
 //             System.out.println( "sair bottom band " + layer.nBand );
//              flag3 = true;
            }
            count--;
            n++;
        }
        // set topmost layer radius to pre-defined top of atmospher
 //     graphicLayer[n-1][2] = this.topOfAtmosphere;

        graphicLayerCount = n;
        graphicLayer[n][0] = -1;        // terminator
        layer = itr.previous();
        graphicSurfaceRockBand = this.surfaceRockBand;

//      System.out.println( "band thinSurf " + this.thinSurfaceBand + " surf " + this.surfaceBand + " rock " + this.surfaceRockBand );
//      System.out.println( "setBandNumbers totalMass " + totalMass );
        return( n );
    }


    // add up layer heights to get asteroid radius
    // and set nlayers variable
    double asteroidRadius() {
        int count = 0;
        double r = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();
        while(itr.hasNext()){
            layer = itr.next();
            r += layer.height;
            count++;
        }
        this.nlayers = count;
        return( r );
    }
    
    // adapted from GlobalGlaciation
    double AreaAtLatitude( double lat, double latRange ) {
        double a;
        double l1 = degreesToRadians * ( lat + latRange );
        double l2 = degreesToRadians * ( lat - latRange );
        if ( lat < 90 ) {
            a = 2.0 * Math.PI * referenceRadius * referenceRadius * ( Math.sin( l1 ) - Math.sin( l2 ) );
        } else {
            a = 2.0 * Math.PI * referenceRadius * referenceRadius * ( 1.0 - Math.sin( degreesToRadians * (90.0 - latRange/2.0)  ) );
//          System.out.println(  Math.sin( degreesToRadians * latRange/2.0  ) );
//          a = 0;
        }
//      System.out.println( lat + " " + a );
        return( a );
    }
    

    void setLayerTicks( int startBand, int count1 ) {
        int count = 0;
        double r = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();
        while(itr.hasNext()){
            layer = itr.next();
            if ( layer.nBand < startBand ) {
                count = layer.setupTick( count1 );
                System.out.println( layer.nBand + " tick count " + count );
        }
            count++;
        }

    }

    // find layer pressures, and use these and layer temperatures to find layer strain,
    // and then use layer strain to find layer density and volume
    // and use layer volume to find layer width, height, and radius from centre
    // ( used to only calculate rock pressures. It doesn't redimension layers.)
    void redimensionLayers() {
        double glocal;              // local gravitational acceleration
        double layerCentreRadius;
        double totalMass = 0;
        double totalForce = 0;
        double midLayerForce, deltaT, deltaP, depth;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();
        while ( itr.hasNext() ) {
            layer = itr.next();
            layerCentreRadius = layer.radius - layer.height / 2.0;
            depth = this.nominalRadius - layer.radius;
            if ( depth >= 0 ) {
                glocal = gravitationalAccelerationInsideAsteroid( layerCentreRadius, this.nominalRadius, this.surfaceGravity );
            } else {
                glocal = surfaceGravity;  // atmosphere g
            }
            midLayerForce = totalForce + glocal * layer.mass / 2.0;
//          System.out.println( layer.nBand + " midLayerForce " + midLayerForce + " totalForce " + totalForce + " glocal * layer.mass / 2.0 " + glocal * layer.mass / 2.0);
            layer.previousLayerPressure = layer.currentLayerPressure;
            layer.currentLayerPressure = midLayerForce / layer.area;
            deltaP = layer.currentLayerPressure - layer.previousLayerPressure;
            deltaT = layer.currentTemperatureK - layer.previousTemperaureK;
            totalForce += layer.mass * glocal;
            totalMass  += layer.mass;
            System.out.println( layer.nBand + " area " + layer.area + " km mass "+ (int)layer.mass + " kg pressure " + (float)(layer.currentLayerPressure/1000000000.0) + " gPascals" + " glocal " + (float)glocal );
        }
        System.out.println( "totalMass " + (float)totalMass * this.surfaceArea + " kg" );
    }

    void findNonTaperingLayerPressures( double a ) {
        double glocal;              // local gravitational acceleration
        double layerCentreRadius;
        double totalMass = 0;
        double totalForce = 0;
        double midLayerForce, deltaT, deltaP, depth, mass;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();
        while ( itr.hasNext() ) {
            layer = itr.next();
            layerCentreRadius = layer.radius - layer.height / 2.0;
            depth = this.nominalRadius - layer.radius;
            glocal = gravitationalAccelerationInsideAsteroid( layerCentreRadius, this.nominalRadius, this.surfaceGravity );
            mass = a * layer.height * layer.layerDensity;
            midLayerForce = totalForce + glocal * mass / 2.0;
//          System.out.println( layer.nBand + " midLayerForce " + midLayerForce + " totalForce " + totalForce + " glocal * layer.mass / 2.0 " + glocal * mass / 2.0);
            layer.previousLayerPressure = layer.currentLayerPressure;
            layer.currentLayerPressure = midLayerForce / a ;
            deltaP = layer.currentLayerPressure - layer.previousLayerPressure;
            deltaT = layer.currentTemperatureK - layer.previousTemperaureK;
            totalForce += mass * glocal;
            totalMass  += mass;
            System.out.println( layer.nBand + " km mass "+ mass + " kg pressure " + (layer.currentLayerPressure/1000000000.0) + " gPascals" + " glocal " + (float)glocal );
        }
        System.out.println( "totalMass " + (float)totalMass * this.surfaceArea + " kg" );
    }

    // layer strain (change of height) due to both pressure and temperature
    double layerStrain() {
        double deltaH = 0;
        return( deltaH );
    }

    // source http://physicsteacher.in/2017/10/18/acceleration-due-to-gravity-height-depth/
    // aradius = asteroid radius, r = required radius
    double gravitationalAccelerationInsideAsteroid( double r, double aradius, double surfaceacceleration  ) {
//      System.out.println( r + ", " + aradius + ", " + surfaceacceleration );
        double h = aradius - r;
        return( surfaceacceleration * ( 1.0 - h / aradius ) );
    }

    void findLayerPressures() {
        double glocal;              // local gravitational acceleration
        double layerCentreRadius;
        double totalMass = 0;
        double totalForce = 0;
        double midLayerForce, deltaT, deltaP, depth;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();
        while ( itr.hasNext() ) {
            layer = itr.next();
            layerCentreRadius = layer.radius - layer.height / 2.0;
            depth = this.nominalRadius - layer.radius;
            if ( depth >= 0 ) {
                glocal = gravitationalAccelerationInsideAsteroid( layerCentreRadius, this.nominalRadius, this.surfaceGravity );
            } else {
                glocal = surfaceGravity;  // atmosphere g
            }
            midLayerForce = totalForce + glocal * layer.mass / 2.0;
//          System.out.println( layer.nBand + " midLayerForce " + midLayerForce + " totalForce " + totalForce + " glocal * layer.mass / 2.0 " + glocal * layer.mass / 2.0);
            layer.previousLayerPressure = layer.currentLayerPressure;
            layer.currentLayerPressure = midLayerForce / layer.area;
            deltaP = layer.currentLayerPressure - layer.previousLayerPressure;
            deltaT = layer.currentTemperatureK - layer.previousTemperaureK;
            totalForce += layer.mass * glocal;
            totalMass  += layer.mass;
//          System.out.println( layer.nBand + " area " + layer.area + " km mass "+ (int)layer.mass + " kg pressure " + (float)(layer.currentLayerPressure/1000000000.0) + " gPascals" + " glocal " + (float)glocal );
        }
//      System.out.println( "totalMass " + (float)totalMass * this.surfaceArea + " kg" );
    }



    // when numbers of layers change, or height/thickness of layers changes,
    // all the layer radii and band numbers must be recalcu;ated
    void reSynchronise() {
//      System.out.println( "resynchronise "+ (this.ap.currentCZYN - this.ap.eemianCZYN) );
        this.radius = asteroidRadius();  // get complete radius of asteroid using all layers
        this.topOfAtmosphere =  this.radius;
        
        setBandNumbers();

        findLayerPressures();

        recalculateRadii( surfaceRockBand );

        // radius of each layer is radius of top of layer
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr = llayer.listIterator();
//      double heightOfLayersAbove = 0;
        while ( itr.hasNext() ){
            layer = itr.next();
//          layer.radius = this.radius - heightOfLayersAbove;
//          heightOfLayersAbove += layer.height;
            this.layerTable[ layer.nBand ] = layer;
//          if ( layer.nBand > 77 ) System.out.println( ap.year + " this.layerTable[" +layer.nBand+ "].radius " + this.layerTable[ layer.nBand ].radius );
        }


//      if ( this.materialBandNumber( 4 ) > this.surfaceBand ) this.surfaceBand = this.materialBandNumber( 4 );
//      if ( this.materialBandNumber( 3 ) > this.surfaceBand ) this.surfaceBand = this.materialBandNumber( 3 );

        resynchronise = false;      // unflag reSynchroise() call needed
    }

    void printWholeColumn() {
        double mass[] = new double[10];
        String name[] = new String[10];
        int index;
        double wLayer, r = 0;
        double totalMass = 0;
        double hfl = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        System.out.println( "Year " + ap.year);
        // seems that list iterator can only go back to revious elemnts if it's been forrward to next elemnts.
        while( itr.hasNext() ){
            layer = itr.next();
            index = (int)layer.materialType;
            name[ index ] = layer.material.name;
            mass[ index ] += layer.mass;
        }

        for( index=0; index<=9; index++ ) {
            System.out.println( index + " " + name[index] + " mass " + mass[index] * this.surfaceArea );
        }

    }

    void printColumn( int ref ) {
        double wLayer, r = 0;
        double totalMass = 0;
        double totalCapacity = 0;
        double airMass = 0;
        double hfl = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        System.out.println( "Year " + ap.year);
        // seems that list iterator can only go back to revious elemnts if it's been forrward to next elemnts.
        while( itr.hasNext() ){
            layer = itr.next();
            totalMass += layer.mass;
            totalCapacity += layer.mass * layer.layerSpecificHeat;
            wLayer = layer.radius / referenceRadius;
            if ( layer.genericMaterialType() == 5 ) airMass += layer.mass;
            if ( ref == 1 || ref == 2 ) layer.storedTemperature[ ref-1 ] = layer.currentTemperatureK;
            System.out.println( "band " + layer.nBand + " r " + (float)layer.radius + " ht " + (float)layer.height + " lvol " + (float)layer.volume + " mtl " + (float)layer.materialType + " mass " + (int)layer.mass + " lres " + (float)layer.layerResistance + " uhfr " + (float)layer.upwardHeatFlowRate + " degrees K " + (float)layer.currentTemperatureK  ) ;
//          System.out.println( "band " + layer.nBand + " radius " + (float)layer.radius + " height " + (float)layer.height + " lvolume " + (float)layer.layerVolume( layer.radius, layer.radius - layer.height, wLayer ) + " material " + (float)layer.materialType + " heat " + (float)layer.heatProduced  + " degrees K " + (float)layer.currentTemperatureK  ) ;
            r += layer.height;
            hfl += layer.upwardHeatFlowRate;
        }
        System.out.println( "asteroid radius " + r + " net upward heat flow rate " + hfl );
        System.out.println( "asteroid column mass  " + totalMass + " asteroid mass " + totalMass * this.surfaceArea );
        System.out.println( "True Earth mass = 5.972 × 10^24 kg");
        System.out.println( "Asteroid atmosphere mass = " + (float)(airMass * this.surfaceArea) + " true value 5.14E18 Kg");
        System.out.println( "asteroid heat content " + this.asteroidHeatContent + " J");
        System.out.println( "asteroid mean temperature " + this.asteroidHeatContent/totalCapacity  + " deg K ");
    }


    // print .csv layer data
    void printcsvColumn( int ref ) {
        double heatContent = 0;
        double v1 = 0;
        double v2 = 0; 
        double v3 = 0;
        double v4 = 0;
        double v5 = 0;
        double v6 = 0;
        double wLayer, r = 0;
        double totalMass = 0;
        double totalCapacity = 0;
        double hfl = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
        String str = sdf.format(date);

        System.out.println( "Year " + ap.year + " " + str + " latitude " + this.columnLatitude + " " + this.test );
        // seems that list iterator can only go back to revious elemnts if it's been forrward to next elemnts.
        while( itr.hasNext() ){
            layer = itr.next();
            if ( layer.materialType == 3.05 ) { 
                heatContent = layer.layerHeatContent;
                v1 = layer.layerConductivity;
                v2 = layer.layerResistance;
                v3 = layer.material.conductivity;
                v4 = layer.layerSpecificHeat;
                v5 = layer.material.specificHeat;
                v6 = layer.pt.FractionPhaseChange;
            }
            totalMass += layer.mass;
            totalCapacity += layer.mass * layer.layerSpecificHeat;
            wLayer = layer.radius / referenceRadius;
            if ( ref == 1 || ref == 2 ) layer.storedTemperature[ ref-1 ] = layer.currentTemperatureK;
            System.out.println( layer.nBand + ", " + layer.materialType + ", " + (float)layer.radius + ", " + (float)layer.height + ", " + (float)layer.width + ", " + (float)layer.upwardHeatFlowRate + ", " + (float)layer.currentTemperatureK + ", "  ) ;
//          System.out.println( layer.nBand + ", " + layer.uniqueNumber + ", " + layer.materialType + ", " + layer.radius + ", " + layer.height + ", " + layer.width + ", " + layer.pseudoTemperatureK + ", " + layer.currentTemperatureK + ", "  ) ;
            r += layer.height;
            hfl += layer.upwardHeatFlowRate;
            
        }
        System.out.println( "asteroid radius " + r + " net upward heat flow rate " + hfl );
        System.out.println( "asteroid column mass  " + totalMass + " asteroid mass " + totalMass * this.surfaceArea );
        System.out.println( "True Earth mass = 5.972 × 10^24 kg");
        System.out.println( "asteroid heat content " + this.asteroidHeatContent + " J");
        System.out.println( "asteroid mean temperature " + this.asteroidHeatContent/totalCapacity  + " deg K ");
        System.out.println( "Snow layer heat content " + heatContent + " " + + v1 + " " + v2 + " " + v3 + " " + v4 + " " + v5 + " " + v6);

        double TotalTime = System.currentTimeMillis() - ap.stime;
        System.out.println( "Elapsed milliseconds " + TotalTime );

        System.out.println(  (ap.currentCZYN - ap.eemianCZYN) + "  " + (ap.eventHandler.precipitstionCZYN - ap.eemianCZYN) );
        System.out.println( "H20LayerHeatContent " + ap.H20LayerHeatContent + " " + ap.H20LayerMassxSpht + " " + ap.H20LayerMassxLatht + " added " + ap.H20LayerHeatAdded );
     }
    
    // print .csv layer data (re-copied from Glaciation140)
    void printhcmColumn( int ref ) {
        double heatContent = 0;
        double wLayer, r = 0;
        double totalMass = 0;
        double totalCapacity = 0;
        double hfl = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
        String str = sdf.format(date);

        System.out.println( "Year " + ap.year + " " + str + " latitude " + this.columnLatitude + " " + " .hcm  " );
        System.out.println( "Yr " + " mtrl  " + " radius " + "   height " + "     mass  " + "   timestep " + "  heat/kg " + "  temp K "  );
        // seems that list iterator can only go back to revious elemnts if it's been forrward to next elemnts.
        while( itr.hasNext() ){
            layer = itr.next();
            totalMass += layer.mass;
            totalCapacity += layer.mass * layer.layerSpecificHeat;
            wLayer = layer.radius / referenceRadius;
            System.out.println( layer.nBand + ", " + layer.materialType + ", " + (float)layer.radius + ", " + (float)layer.height + ", " + (float)layer.mass  + ", " + (float)layer.tickDuration  + ", " + (float)layer.layerHeatContentPerKg + ", " + (float)layer.currentTemperatureK + ", "  ) ;
            r += layer.height;
    //      hfl += layer.upwardHeatFlowRate;
            if ( layer.nBand == ref ) break;            
        }
        System.out.println( "thinSurfaceBand " + this.thinSurfaceBand + " thinSurfaceLayerAlbedo " + thinSurfaceLayerAlbedo );
     }
    
    
    // print asteroid R, C, and RC time constants
    void printColumnRC( int ref ) {
        double wLayer, r = 0;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr=llayer.listIterator();

        System.out.println( "Year " + ap.year);
        // seems that list iterator can only go back to revious elemnts if it's been forrward to next elemnts.
        while( itr.hasNext() ){
            layer = itr.next();
            wLayer = layer.radius / referenceRadius;
            if ( ref == 1 || ref == 2 ) layer.storedTemperature[ ref-1 ] = layer.currentTemperatureK;
            System.out.println( "band " + layer.nBand + " v " + (float)layer.volume + " mtl " + (float)layer.materialType + " l_k " + (float)layer.layerConductivity + " lres " + (float)layer.layerResistance + " cap " + (float)layer.layerThermalCapacity + " time const (years) " + (float)(layer.layerTimeConstant / ap.secondsPerYear) ) ;
//          System.out.println( "band " + layer.nBand + " radius " + (float)layer.radius + " height " + (float)layer.height + " lvolume " + (float)layer.layerVolume( layer.radius, layer.radius - layer.height, wLayer ) + " material " + (float)layer.materialType + " heat " + (float)layer.heatProduced  + " degrees K " + (float)layer.currentTemperatureK  ) ;
            r += layer.height;
        }
        System.out.println( "asteroid radius " + r );
    }

    void printGraphicLayers() {
        System.out.println( "Year " + ap.year + " graphic layers");
        for ( int n=0; n<graphicLayerCount; n++ ) {
            System.out.println( "band " + (int)graphicLayer[n][0] + " type " + (int)graphicLayer[n][1] + " radius " + graphicLayer[n][2] );
        }
    }

}



class ConductiveLayer {
    GeologicalColumn astro;
    PhaseTemperature pt;
    double materialType;                // 1=iron, 2= rock, 3=ice.water, 5=air, 6.nnn=rock-ice where .nnn = fgarnite
    Material material;
    double radius;                      // radial distance of top of layer from asteroid centre
    double length;                      // longitudinal m
    double width;                       // latitudinal m
    double height;                      // radial thickness m
    double area;                        // length x widthe
    double volume;                      // length x width * height
    double mass;                        // layer mass kg
    double albedo;                      // layer albedo is set to material albedo, but can change
    int    phase;                       // 0 solid, 1 liqid. 2 vapour
    double volumetricFractionGranite;
    double heatProduced;                // watts heat generated by radioactive granite in layer
    double upwardHeatFlowRate;          // conductive heat flow rate to layer above
    double maxUpwardHeatFlowRate = 0;
    double netHeatFlowRate;             // layer heat flow from all sources
    double previousTemperaureK;         // temperature at previous iteration
    double currentTemperatureK;
    double initialTemperature;          // temperature at initiation
    double nextTemperatureK;
    double nextPhase;
    boolean fixTemperature = false;     // allow or disallow temperature change
    double pseudoTemperatureK;          // temperature used during phase change
    double phaseTemperatureK;           // temperature of current phase change
    boolean phaseChange;                // flagged true if currently changing phase
    boolean phaseHasChanged = false;    // flag set when material phase changes
    boolean layerPhase;                 // 0 solide, 1 liqid, 2 vapour, -1 phasechange
    double latentHeat;
    double layerDensity;
    double layerConductivity;
    double layerSpecificHeat;
    double layerLatentHeatOfFusion;
    double layerEmissivity;
    double layerAbsorptivity;
    double layerHeatContent;            // J
    double layerHeatContentPerKg;       // J / Kg
    double initialLayerHeatContent;
    double initialLayerHeatContentPerKg;
    double layerHeatAdded;              // summed heat gains
    double layerThermalCapacity;
    double layerResistance;
    double layerTimeConstant;
    double currentLayerPressure;        // kg / square metre
    double previousLayerPressure;       // kg / square metre
    double layerTimestep;               // layer timestep (seconds)
    double tickDuration;                // duration of ap tick (seconds)
    int tickCounter;                    // current layer tick countdown
    int tickResetCount;                 // reset value for tickCounter;
    int nBand;                          // band numbers have reverse sense of layers, Band 0 is at centre.
    double storedTemperature[] = new double[2];
    double phaseSum = 0;
    boolean updated;
    int uniqueNumber = 0;
    boolean activeCalculation =  true;
    Average averageTemperatureK;

    Material material0;
    Material material1;
    Material material2;
    double spHt0;
    double spHt1;
    double spHt2;
                
    // default constructor
    ConductiveLayer() { }


    // indexed material table driven constructor (called by csvreader()) Sep 2019
    ConductiveLayer( GeologicalColumn a, double rlyr, double w, double h, double t, double mt, double tickYears, double wattsPerCubicMetre ) {
        this.astro = a;
        getUniqueLayerNumber();
        
        this.materialType = mt;
        this.length = w;
        this.width = w;
        this.height = h;
        this.area = w * w;
        this.volume = this.layerVolume( rlyr, rlyr - this.height, this.width );
        this.radius = rlyr;         // radial distance of top of layer from asteroid centre

        this.currentTemperatureK = t;
        this.nextTemperatureK = t;
        this.initialTemperature = t;

        this.material = new Material( astro, mt );

//      this.heatProductionGranite = 0;    // watts / kg  (multiple of 0.0000000001 )
        this.albedo = material.albedo;
        this.layerDensity = material.density;
        this.layerConductivity = material.conductivity;
        this.layerSpecificHeat = material.specificHeat;
        this.layerLatentHeatOfFusion = material.latentHeatOfFusion;
        this.layerEmissivity = material.emissivity;
        this.layerAbsorptivity = material.absorptivity;
        this.mass = this.volume * this.layerDensity;
        this.layerResistance = this.layerResistance();
        this.layerThermalCapacity = this.layerThermalCapacity();
        this.layerTimeConstant = this.layerResistance * this.layerThermalCapacity;
        this.heatProduced = volume * wattsPerCubicMetre;

        this.setLayerPhase();
        this.setTickYears( tickYears );
//      System.out.println( "tickYears " + tickYears + " tick duration " +this.tickDuration + " tick count " + this.tickResetCount  );
        
        // System.out.println( "Layer constructor current temperature " + this.currentTemperatureK + " heat content " + this.layerHeatContent );
        // System.out.println( "Layer constructor tick duration " + this.tickDuration / astro.secondsPerYear + " count " + this.tickResetCount );

        // phase change data
        double mt0 = (int)this.materialType;     // solid (ice)
        double mt1 = mt0 + 0.1;                  // liquid (water)
        double mt2 = mt0 + 0.2;                  // gas (water vapour)
        
        this.material0 = new Material( astro, mt0 );
        this.material1 = new Material( astro, mt1 );
        this.material2 = new Material( astro, mt2 );
        
//      double spHt0 = this.layerSpecificHeat;  // use current specific heat
        this.spHt0 = material0.specificHeat;
        this.spHt1 = material1.specificHeat;
        this.spHt2 = material2.specificHeat;     

        pt = new PhaseTemperature( this );
        System.out.println( " initial temperature " + pt.temperature( layerHeatContentPerKg ) );

        this.layerHeatContentPerKg = pt.heatContentPerKg( this.currentTemperatureK );
        this.layerHeatContent = this.layerHeatContentPerKg * this.mass;
        this.initialLayerHeatContent = this.layerHeatContent;
        this.initialLayerHeatContentPerKg = this.layerHeatContentPerKg;
        
        this.layerHeatAdded = 0;
        averageTemperatureK = new Average( (int)( 1.0 / astro.ap.timeStepYears ) );


//      if ( mt == 0 ) System.out.println( " init m " + (float)this.mass + ", dy " + (float)this.layerDensity + ", v " + (float)this.volume + ", h " + (float)this.height  + ", w " + (float)this.width  + ", a " + (float)this.area   + ", r " + (float)this.radius );
        
    }

    // indexed material table driven constructor  Mar 2019 (called by hcmreader())
    //      System.out.println( layer.nBand + ", " + layer.materialType + ", " + (float)layer.radius + ", " + (float)layer.height + ", " + (float)layer.mass  + ", " + (float)layer.layerHeatContentPerKg + ", " + (float)layer.currentTemperatureK + ", "  ) ;    
    // band numbers, radii, height, and temperature are all recalculated from mass, density, and heat content (and so are simply included )
    //                                band number   material    radius    height      mass     heat content   temperature
    ConductiveLayer( GeologicalColumn a, int band, double mt, double r, double h, double m, double tick, double heatPerKg, double t ) {
        double tickYears = 0;   // time step one day
        this.astro = a;
        getUniqueLayerNumber();
        
        this.materialType = mt;

        // claculate layer dimenssions from layer mass and material density
        this.material = new Material( astro, mt );
        this.layerDensity = material.density;
        this.mass = m;
        this.volume = layerVolume( this.mass, this.layerDensity );
        this.radius = radiusTopOfLayer( this.volume, r, a.referenceRadius );
        setLayerDimensions( this.radius, r, a.referenceRadius );
//      if ( mt == 0 ) System.out.println( " init m " + (float)this.mass + ", dy " + (float)this.layerDensity + ", v " + (float)this.volume + ", h " + (float)this.height  + ", w " + (float)this.width  + ", a " + (float)this.area   + ", r " + (float)this.radius );
 
//      this.heatProductionGranite = 0;    // watts / kg  (multiple of 0.0000000001 )
        this.albedo = material.albedo;
        this.layerConductivity = material.conductivity;
        this.layerSpecificHeat = material.specificHeat;
        this.layerLatentHeatOfFusion = material.latentHeatOfFusion;
        this.layerEmissivity = material.emissivity;
        this.layerAbsorptivity = material.absorptivity;
        this.mass = this.volume * this.layerDensity;
        this.layerResistance = this.layerResistance();
        this.layerThermalCapacity = this.layerThermalCapacity();
        this.layerTimeConstant = this.layerResistance * this.layerThermalCapacity;
//      this.heatProduced = volume * wattsPerCubicMetre;
        this.layerHeatContentPerKg = heatPerKg;
        this.initialLayerHeatContent = this.layerHeatContent;
        this.layerHeatContent = heatPerKg * this.mass;
        this.initialLayerHeatContentPerKg = this.layerHeatContentPerKg;
        this.layerHeatAdded = 0;
        averageTemperatureK = new Average( (int)( 1.0 / astro.ap.timeStepYears ) );
  
//      this.setLayerPhase();
        this.setTickYears( tickYears );
//      System.out.println( "tickYears " + tickYears + " tick duration " +this.tickDuration + " tick count " + this.tickResetCount  );
        

        // System.out.println( "Layer constructor current temperature " + this.currentTemperatureK + " heat content " + this.layerHeatContent );
        // System.out.println( "Layer constructor tick duration " + this.tickDuration / astro.secondsPerYear + " count " + this.tickResetCount );

        // phase change data
        double mt0 = (int)this.materialType;     // solid (ice)
        double mt1 = mt0 + 0.1;                  // liquid (water)
        double mt2 = mt0 + 0.2;                  // gas (water vapour)
        
        this.material0 = new Material( astro, mt0 );
        this.material1 = new Material( astro, mt1 );
        this.material2 = new Material( astro, mt2 );
        
//      double spHt0 = this.layerSpecificHeat;  // use current specific heat
        this.spHt0 = material0.specificHeat;
        this.spHt1 = material1.specificHeat;
        this.spHt2 = material2.specificHeat;     

        pt = new PhaseTemperature( this );
        
        // set temperatures using heat content of layer
        this.currentTemperatureK = pt.temperature( layerHeatContentPerKg );
        this.nextTemperatureK = this.currentTemperatureK;
        this.initialTemperature = this.currentTemperatureK;
        
//      if ( (int)mt == 5 ) this.materialType = 5.0;  // fix for .hcm bug?
        
//      if ( mt == 0 ) System.out.println( "this.currentTemperatureK " + this.currentTemperatureK );
    }

    // copy the characteristics of another layer
    void copyLayer( ConductiveLayer layer ) {
        // this.radius = layer.radius;         not copied because it's a different layer
        // nBand not copied because it's a different layer
        // nBand is set by asteroid.reSynchronise();
        this.materialType = layer.materialType;
        this.material = layer.material;
        this.length = layer.length;
        this.width = layer.width;
        this.height = layer.height;
        this.area = layer.area;
        this.volume = layer.volume;
        this.mass = layer.mass;
        this.currentTemperatureK = layer.currentTemperatureK;
        this.nextTemperatureK = layer.nextTemperatureK;
        this.layerDensity = layer.layerDensity;
        this.layerConductivity = layer.layerConductivity;
        this.layerSpecificHeat = layer.layerSpecificHeat;
        this.layerLatentHeatOfFusion = layer.layerLatentHeatOfFusion;
        this.layerEmissivity = layer.layerEmissivity;
        this.layerAbsorptivity = layer.layerAbsorptivity;
        this.layerThermalCapacity = layer.layerThermalCapacity;
        this.heatProduced = layer.heatProduced;
        this.layerHeatContent = layer.layerHeatContent;
        this.albedo = layer.albedo;
    }
    
    void setActiveCalculation( boolean astate ) {
        this.activeCalculation = astate;
    }
    
    // get a unique number for the layer
    int getUniqueLayerNumber() {
        astro.uniqueNumber++;
        this.uniqueNumber = astro.uniqueNumber;
        return( astro.uniqueNumber );
    }
    
    
    void restoreInitialTemperature() {
        this.currentTemperatureK = this.initialTemperature;
        this.nextTemperatureK = this.initialTemperature;      
        this.layerHeatContentPerKg = this.initialLayerHeatContentPerKg;
        this.layerHeatContent = this.initialLayerHeatContent;        
    } 

    void fixTemperature() {
        this.fixTemperature = true;
    }
    
    void unfixTemperature() {
        this.fixTemperature = false;
    }
    
    // Layers are thin slices of a sphere, with this.width * this width cross-sectional area at top.
    double layerVolume( double radiusTopOfLayer, double radiusBottomOfLayer, double sliceWidth ) {
        double v = sphereVolume( radiusTopOfLayer ) - sphereVolume( radiusBottomOfLayer );
        double fraction = sliceWidth * sliceWidth / sphereSurfaceArea( radiusTopOfLayer );
        return( fraction * v );
    }
    
    // find layer volume from mass and density
    double layerVolume( double mass, double density ) {
        double v = mass / density;
        return( v );
    }
    
    // layer is part of a column with surface area of one square metre on asteroid of radius referenceRadius
    double radiusTopOfLayer( double layerVolume, double radiusBottomOfLayer, double referenceRadius ) {
        double asteroidSurfaceArea = sphereSurfaceArea( referenceRadius );
        double columnFractionOfSphere = 1.0 / asteroidSurfaceArea;
        double rCubed = Math.pow( radiusBottomOfLayer, 3.0 ) + ( 3.0 * layerVolume ) / ( 4.0 * columnFractionOfSphere * Math.PI  );
        double r = Math.pow( rCubed, ( 1.0/3.0 ) );
        return( r );
    }

    // set layer height, width, and top surface area
    void setLayerDimensions( double radiusTopOfLayer, double radiusBottomOfLayer, double referenceRadius ) {
        this.height = radiusTopOfLayer - radiusBottomOfLayer;
        this.width =  ( radiusTopOfLayer / referenceRadius );   // 1 m at asteroid surface
        this.area = this.width * this.width;                    // 1 square m at surface
    }
    
    double layerMass() {
        return( this.volume * this.material.density );
    }

    double layerThermalCapacity() {
        return( this.volume * this.material.density * this.material.specificHeat );
    }

    double heatGenerated() {
        return( this.heatProduced );
    }

    // fractional part of floating point number
    double fPart( double number ) {
        return( number - iPart( number ) );        
    }
    double layerResistance() {
        this.layerResistance = this.height / ( this.layerConductivity * this.area );
        return( this.layerResistance );
    }

    double sphereVolume( double r ) {
        double volume = 1.3333333333 * Math.PI * r * r * r;
        return( volume );
    }

    double sphereSurfaceArea( double r ) {
        double a = 4.0 * Math.PI * r * r;
        return( a );
    }

/*  
    // layer heat content including latent heat
    double heatContent( double temperature ) {
        double heatContent = 0;
        Material material0;
        Material material1;
        Material material2;
                
        double mt0 = (int)this.materialType;     // solid (ice)
        double mt1 = mt0 + 0.1;                  // liquid (water)
        double mt2 = mt0 + 0.2;                  // gas (water vapour)
        
        material0 = new Material( astro, mt0 );
        material1 = new Material( astro, mt1 );
        material2 = new Material( astro, mt2 );
        
//      double spHt0 = this.layerSpecificHeat;  // use current specific heat
        double spHt0 = material0.specificHeat;
        double spHt1 = material1.specificHeat;
        double spHt2 = material2.specificHeat;        
        
        if ( this.nBand == 72 ) System.out.println( "hc " + heatContent + " phasechange " + this.phaseChange );
        
        if ( this.phaseChange ) {
            // if phase change in process layer will be at either phasetemperature1 (fusion) or phasetemperature2 (vapourisation)
            if ( this.currentTemperatureK == material0.phaseTemperature1 ) {
                heatContent = this.mass * spHt0 * material0.phaseTemperature1;
                heatContent += this.mass * material0.latentHeatOfFusion * Math.abs( material0.phaseTemperature1 - this.pseudoTemperatureK );
//              System.out.println( "heatContent2 " + heatContent + " " + this.mass + " " +material0.latentHeatOfFusion + " " + Math.abs( material0.phaseTemperature1 - this.pseudoTemperatureK ));
            } else if ( this.currentTemperatureK == material0.phaseTemperature2 ) {
                heatContent = this.mass * spHt0 * material0.phaseTemperature1;
                heatContent += this.mass * material0.latentHeatOfFusion;
                heatContent += this.mass * spHt1 * (material1.phaseTemperature2 - material1.phaseTemperature1);
                heatContent += this.mass * Math.abs( material1.phaseTemperature2 - this.pseudoTemperatureK ) * material1.latentHeatOfEvaporation;                
            }
        } else {
            // if between phases
            if ( temperature > material0.phaseTemperature2 ) {
                heatContent = this.mass * spHt0 * material0.phaseTemperature1;
                heatContent += this.mass * material0.latentHeatOfFusion;
                heatContent += this.mass * spHt1 * ( material1.phaseTemperature2 - material1.phaseTemperature1 );
                heatContent += this.mass * material1.latentHeatOfEvaporation;
                heatContent += this.mass * spHt2 * ( temperature - material2.phaseTemperature2 );
            } else if ( temperature > material0.phaseTemperature1 ) {
                heatContent = this.mass * spHt0 * material0.phaseTemperature1;
                heatContent += this.mass * material0.latentHeatOfFusion;
                heatContent += this.mass * spHt1 * ( temperature - material1.phaseTemperature1 );                
            } else {
                heatContent = this.mass * spHt0 * temperature; 
            }   
        }
        
        if ( this.nBand == 72 ) System.out.println( "hc " + heatContent + " phasechange " + this.phaseChange );
        

        return( heatContent );
    }
*/  
    
    // early version prior to jan 2019
    int setLayerPhase() {
        if ( material.isSolid( this.currentTemperatureK ) ) {
            this.phase = 0;
        } else if ( material.isLiquid( this.currentTemperatureK ) ) {
            this.phase = 1;
        } else {
            this.phase = 2;
        }
                
        return( this.phase );
    }
    
    // jan 2019 set new phase and material type if necessary
    double phaseCheck( double t) {
        int newphase = getPhase( t );
        if ( newphase > -1 ) {
            if ( newphase != this.phase) {
                this.phase = newphase;
                this.materialType = getMaterialType( this.materialType, newphase );
                System.out.println( (float)astro.ap.year + " band " + this.nBand + " new phase " + newphase + " new material " + this.materialType );
            } else {
                // keep current materialType unchanged
            }           
        } else {
            // keep current materialType unchnanged during phase change
        }
        return( this.materialType );
    }
    
    // jan 2019
    int getPhase( double t ) {
        int lphase = -2;
        if ( t == this.material.phaseTemperature1 || t == this.material.phaseTemperature2 ) {
            lphase = -1;                // phase change in process 
        } else {
            if ( t < this.material.phaseTemperature1 ) {
                lphase = 0;             // solid
            } else if ( t > this.material.phaseTemperature2 ) {
                lphase = 2;             // vapour
            } else {
                lphase = 1;             // liquid
            }
        }
        
        return( lphase );
    }
    
    // generic material type is the integer number of the material
    // i.e. materialType 5.2 is generic material type 5.
    int genericMaterialType() {
        return( (int)this.materialType );
    }
    
    // jan 2019
    double getMaterialType( double mt, int mphase ) {
        double type = iPart( mt ) + (double)mphase / 10.0;
        return( type );
    }

    // interger part of floating point number
    int iPart( double number ) {
        return ( (int)number );
    }
    
    
    int setTickYears( double years ) {
        if ( years == 0 ) {
            // default setting 0 is to use same timestep as ap
            this.tickDuration = astro.ap.timeStepYears * astro.secondsPerYear;
            this.tickResetCount = 1;
            resetTickCounter();
        } else if ( years > 0 ) {
            if ( years > 0.1 * this.layerTimeConstant / astro.secondsPerYear ) {
                years = 0.1 * this.layerTimeConstant / astro.secondsPerYear;
            }
            this.tickResetCount = (int)( years / astro.ap.timeStepYears );
            if ( this.tickResetCount < 0 ) this.tickResetCount = 1;
            this.tickDuration = years * astro.secondsPerYear;
        }
        return( this.tickResetCount );
    }

    int setupTick( int c ) {
        if ( c == 0 ) {
            // default setting 0 is to use same timestep as ap
            this.tickDuration = astro.ap.timeStepYears * astro.secondsPerYear;
            this.tickResetCount = 1;
            resetTickCounter();
        } else if ( c > 0 ) {
            this.tickResetCount = (int)(this.layerTimeConstant / this.tickDuration);
            this.tickResetCount = this.tickResetCount / c;
            if ( this.tickResetCount < 0 ) this.tickResetCount = 1;
            this.tickDuration = astro.ap.timeStepYears * astro.secondsPerYear * this.tickResetCount;
        }
        return( this.tickResetCount );
    }

    void resetTickCounter() {
        this.tickCounter = this.tickResetCount;
    }

}


class Material{
    GeologicalColumn astro;
    String name;
    double index;
    double albedo;                          // negative albedo is transparent, use albedo of layer below
    double density;
    double conductivity;
    double specificHeat;
    double emissivity;
    double absorptivity;
    double latentHeatOfFusion;
    double latentHeatOfEvaporation;
    double volumetricFractionGranite = 0;
    double radioactiveHeatGenerated;
    double phaseTemperature1;               // melting point
    double phaseTemperature2;               // boiling point
    double expansivity;                     // coefficient of linear thermal expansion
    double modulusElasticity;
    int colourIndex[] = new int[3];         // 0 solid, 1 liquid, 2 vapaor

double[][] iceCharacteristics = {
    // copied from https://www.engineeringtoolbox.com/ice-thermal-properties-d_576.html
    // degC    density    k      sp ht (kJ/kg)
    { 0,	916.2,	2.22,	2.050, },
    { -5,	917.5,	2.25,	2.027, },
    { -10,	918.9,	2.30,	2.000, },
    { -15,	919.4,	2.34,	1.972, },
    { -20,	919.4,	2.39,	1.943, },
    { -25,	919.6,	2.45,	1.913, },
    { -30,	920.0,	2.50,	1.882, },
    { -35,	920.4,	2.57,	1.851, },
    { -40,	920.8,	2.63,	1.818, },
    { -50,	921.6,	2.76,	1.751, },
    { -60,	922.4,	2.90,	1.681, },
    { -70,	923.3,	3.05,	1.609, },
    { -80,	924.1,	3.19,	1.536, },
    { -90,	924.9,	3.34,	1.463, },
    {-100,	925.7,	3.48,	1.389, },
    {-150,	926.7,	3.52,	1.360, },   // extrapolated value (v approx)
    {-200,	927.7,	3.56,	1.340, },   // extrapolated value (v approx)
    {-250,	928.7,	3.60,	1.320, },   // extrapolated value (v approx)

};

    Material() { }

    Material( GeologicalColumn a, double i, String name, double d, double c, double s, double lhf, double lhv, double fgranite, double hgranite ) {
        astro = a;
        this.name = name;
        this.index = i;
        this.density = d;
        this.conductivity = c;
        this.specificHeat = s;
        this.latentHeatOfFusion = lhf;
        this.latentHeatOfEvaporation = lhv;
        this.volumetricFractionGranite = fgranite;
        this.radioactiveHeatGenerated = hgranite;
    }

    // select from existing materials
    Material( GeologicalColumn a, double i ) {
        astro = a;
        setMaterialCharacteristics(i, 0);
    }
    
    
    // select from existing materials
    Material( double i ) {
        setMaterialCharacteristics(i, 0);
    }
    

    void setMaterialCharacteristics( double i, int calledBy ) {
        int multiple = (int)( i * 100.01 );
        switch ( multiple ) {
            case 0:
            case 10:
            case 20:
                // thin surface layer has zero thickness and therefore zero mass volume etc
                this.name = "surface";
                this.index = i;
                this.albedo = -1;                       //-1 means albedo is of material in layer below
                this.density = 7870;  
                this.conductivity = 79.5;  // iron
                this.specificHeat = 460.5;  // iron
                this.phaseTemperature1 = 100000000;     // no phase
                this.phaseTemperature2 = 100000000;     // no phase change
                this.emissivity = 0.93;                 // ???
                this.absorptivity = 0.93;
                this.expansivity = 0;
                this.modulusElasticity = -0;
                this.colourIndex[0] = 0;                // black
                this.colourIndex[1] = 0;                //
                this.colourIndex[2] = 0;   
                break;

            case 100:
            case 110:
            case 120:
                // basalt rock
                this.name = "basalt asthenosphere";
                this.index = i;
                this.albedo = 0.3;
                this.density = 3000.0;                  // kg / m^3 basalt
                this.conductivity = 2.20;               // W/ m deg K basalt
                this.specificHeat = 840;                // J/ kg degK basalt
                this.phaseTemperature1 = 1300;
                this.latentHeatOfFusion = 418400;       // 100 calories / gm
                this.phaseTemperature2 =  7001;
                this.latentHeatOfEvaporation = 840;     // set same as specific heat
                this.volumetricFractionGranite = 0;
                this.radioactiveHeatGenerated = 1E-9;    // modern terrestrial granite
                this.emissivity = 0.95;                  // https://www.optotherm.com/emiss-table.htm
                this.absorptivity = 0.95;
                this.expansivity = 8.0e-6;               // 10^-6 same as granite
                this.modulusElasticity = 50E9;       // http://community.dur.ac.uk/~des0www4/cal/dams/geol/mod.htm
                this.colourIndex[0] = 0;                 // brown
                this.colourIndex[1] = 5;                 // red
                this.colourIndex[2] = 7;                 // yellow
                break;

            case 200:
            case 220:
                // granite rock
                this.name = "granite crust";
                this.index = i;
//              this.albedo = 0.4;
                this.albedo = 0.4;
                this.density = 2650.0;                  // kg / m^3
                this.conductivity = 2.12;               // dry high porosity W/ m deg K
    //          this.conductivity = 3.98;               // wet low porosity W/ m deg K (may be as high as 5.00)
    //          this.conductivity = 6.36;               // 3 x standard conductivity)
                this.specificHeat = 790;                // J/ kg degK engineering toolbox
                this.phaseTemperature1 = 1523;          // 1250 °C granite melts at about 1215° to 1260° C
                this.latentHeatOfFusion = 418000;       // conversion of 100 calories/gm
                this.phaseTemperature2 =  7001;         // large number so it won't happen
                this.latentHeatOfEvaporation = 790;     // set same as specific heat
                this.volumetricFractionGranite = 1;
                this.radioactiveHeatGenerated = 1E-9;       // modern terrestrial granite
                this.emissivity = 0.95;                 // https://www.optotherm.com/emiss-table.htm
                this.absorptivity = 0.95;
                this.expansivity = 8.0e-6;              // 10^-6
                this.modulusElasticity = 40E9;      //http://community.dur.ac.uk/~des0www4/cal/dams/geol/mod.htm
                this.colourIndex[0] = 9;                // brown
                this.colourIndex[1] = 5;                // red
                this.colourIndex[2] = 7;                // yellow
                break;

            case 205:
                // foamed granite rock (aka pumice) copied from material index 11
                // pumice (all the characteristics of granit except density and conductivity)
                this.name = "pumice, or bubbly granite";
                this.index = i;
                this.albedo = 0.4;
                this.density = 50.0;                    // slightly higher than polystyrene foam kg / m^3
                this.conductivity = 0.02;               // conductivity of polystyrene foam W/ m deg K
                this.specificHeat = 790;                // J/ kg degK engineering toolbox
                this.phaseTemperature1 = 1523;
                this.latentHeatOfFusion = 418000;       // conversion of 100 calories/gm
                this.phaseTemperature2 =  7001;         // large number so it won't happen
                this.latentHeatOfEvaporation = 460;     // set same as specific heat
                this.volumetricFractionGranite = 1;
                this.radioactiveHeatGenerated = 1E-9;       // modern terrestrial granite
                this.emissivity = 0.95;                 // https://www.optotherm.com/emiss-table.htm
                this.absorptivity = 0.95;
                this.expansivity = 8.0e-6;              // 10^-6
                this.modulusElasticity = 40E9;      //http://community.dur.ac.uk/~des0www4/cal/dams/geol/mod.htm
                this.colourIndex[0] = 6;                // brown
                this.colourIndex[1] = 6;                // red
                this.colourIndex[2] = 7;                // yellow
                break;

            case 210:
                // molten granite rock is supposed to have the same characteristic as solid rock
                this.name = "granite lava";
                this.index = i;
                this.albedo = 0.4;
    //          this.albedo = 0.50;
                this.density = 2650.0;                  // kg / m^3
                this.conductivity = 2.12;               // dry high porosity W/ m deg K
    //          this.conductivity = 3.98;               // wet low porosity W/ m deg K
                this.specificHeat = 790;                // J/ kg degK engineering toolbox
                this.phaseTemperature1 = 1523;          // 1250 °C granite melts at about 1215° to 1260° C
                this.latentHeatOfFusion = 418000;       // conversion of 100 calories/gm
                this.phaseTemperature2 =  7001;         // large number so it won't happen
                this.latentHeatOfEvaporation = 460;     // set same as specific heat
                this.volumetricFractionGranite = 1;
                this.radioactiveHeatGenerated = 1E-9;       // modern terrestrial granite
                this.emissivity = 0.95;                 // https://www.optotherm.com/emiss-table.htm
                this.absorptivity = 0.95;
                this.expansivity = 8.0e-6;              // 10^-6
                this.modulusElasticity = 40E9;      //http://community.dur.ac.uk/~des0www4/cal/dams/geol/mod.htm
                this.colourIndex[0] = 9;                // brown
                this.colourIndex[1] = 5;                // red
                this.colourIndex[2] = 7;                // yellow
                break;

            case 300:
                // water ice
                this.name = "ice";
                this.index = 3.0;
                this.albedo = 0.6;                      // less than snow
                this.density = 917;                     // density of ice kg/ m^3
                this.conductivity = 2.18;               // conductivity of Ice  W / m degK
                this.specificHeat = 1389;               // specific heat of ice, J / kg deg K
                this.phaseTemperature1 = 273;
                this.latentHeatOfFusion = 334000;       // J/kg
                this.phaseTemperature2 = 373;
                this.latentHeatOfEvaporation = 2264705; // J/kg
                this.volumetricFractionGranite = i - (int)i;
                this.radioactiveHeatGenerated = 0;
                this.emissivity = 0.85;                 // snow 0.8 - 0.9
                this.absorptivity = 0.85;
                this.expansivity = 51.0;
                this.modulusElasticity = 0;         // unknown
                this.colourIndex[0] = 3;                // mid gray (ice)
                this.colourIndex[1] = 12;               // cyan (water)
                this.colourIndex[2] = 7;               // yellow (steam)
                break;

            case 305:
                // snow, which is a form of ice, but less dense and less conductive
                this.name = "snow";
                this.index = 3.05;
    //          this.albedo = 0.8;                      // fresh snow albedo
    //          this.albedo = 0.80;                      // fresh snow albedo
                this.albedo = 0.80;
                this.density =  30;                     // density of dry light snow kg/ m^3
                this.conductivity = 0.02;               // conductivity of snow  W / m degK ( same as exp polystyrene)
    //          this.conductivity = 0.006;              // conductivity of PP snow 103 kg/m3 https://agupubs.onlinelibrary.wiley.com/doi/10.1029/2011GL049234
    //          this.conductivity = 0.08;               // conductivity of fresh snow  W / m degK
                this.specificHeat = 2025;               // specific heat of ice, J / kg deg K
                this.phaseTemperature1 = 273;
                this.latentHeatOfFusion = 334000;       // J/kg
                this.phaseTemperature2 = 373;
                this.latentHeatOfEvaporation = 2264705; // J/kg
                this.volumetricFractionGranite = i - (int)i;
                this.radioactiveHeatGenerated = 0;
                this.emissivity = 0.85;                 // snow 0.8 - 0.9
                this.absorptivity = 0.85;
                this.expansivity = 51.0;
                this.modulusElasticity = 0;         // unknown
                this.colourIndex[0] = 4;                // mid gray (ice)
                this.colourIndex[1] = 12;               // cyan (water)
                this.colourIndex[2] = 7;               // yellow (steam)
                break;

            case 310:
                // liquid water
                this.name = "water";
                this.index = 3.1;
                this.albedo = -0.3;                     // negative means transparent
                this.density = 999.8;                   // water density at 273 K
    //          this.conductivity = 0.59;               // water conductivity at 273 K
                this.conductivity = 0.56;               // temporary water conductivity to emulate convection
                this.specificHeat = 4217;    // water specific heat at 273 K
                this.phaseTemperature1 = 273;
                this.latentHeatOfFusion = 334000;              // J/kg
                this.phaseTemperature2 = 373;
                this.latentHeatOfEvaporation = 2264705;        // J/kg
                this.volumetricFractionGranite = i - (int)i;
                this.radioactiveHeatGenerated = 0;
                this.emissivity = 0.96;                  // water, snow 0.8 - 0.9
                this.absorptivity = 0.96;
                this.expansivity = 0;                    // water dees not expand much
                this.modulusElasticity = 2.2E9;      // 2.2 GN/m2,
                this.colourIndex[0] = 4;                 // mid gray
                this.colourIndex[1] = 12;                // blue
                this.colourIndex[2] = 7;                 // light gray
                break;

            case 320:
                // steam or water vapour
                // using characteristics f water for now
                this.name = "steam";
                this.index = 3.2;
                this.albedo = -0.3;                     // transparent
                this.density = 999.8;                   // water density at 273 K
                this.conductivity = 0.59;               // water conductivity at 273 K
                this.specificHeat = 1996;               // water specific heat at 273 K
                this.phaseTemperature1 = 273;
                this.latentHeatOfFusion = 334000;       // J/kg
                this.phaseTemperature2 = 373;
                this.latentHeatOfEvaporation = 2264705; // J/kg
                this.volumetricFractionGranite = i - (int)i;
                this.radioactiveHeatGenerated = 0;
                this.emissivity = 0.96;                 // ???
                this.absorptivity = 0.96;
                this.expansivity = 0;                   // no idea!
                this.modulusElasticity = -0;            // unknown
                this.colourIndex[0] = 4;                // mid gray
                this.colourIndex[1] = 12;                // cyan
                this.colourIndex[2] = 1;                // light gray
                break;

            case 500:
            case 510:
            case 520:
                // air
                this.name = "air";
                this.index = i;
                this.albedo = -0.3;
                this.density = 1.293;                   // density of air kg/ m^3
//              this.density = 2.586;                   // double density of air kg/ m^3
                this.conductivity = 0.0243;             // conductivity of air  W / m degK
                this.specificHeat = 1000;               // specific heat of air, J / kg deg K
                this.phaseTemperature1 = 58;
                this.latentHeatOfFusion = 1000;         // set same as specific heat
                this.phaseTemperature2 = 76.3;
                this.latentHeatOfEvaporation = 1000;    // set same as specific heat
                this.volumetricFractionGranite = i - (int)i;
                this.radioactiveHeatGenerated = 0;
//              this.emissivity = 0.958;                 // RESULTS IN 300 k AIR TEMPERATURES AT EQUATOR
//              this.absorptivity = 0.958;
    //          this.emissivity = 0.95;                 
    //          this.absorptivity = 0.95;
    //         this.emissivity = 0.999;                 // effect of greenhouse gases?
    //          this.absorptivity = 0.999;
    //          this.emissivity = 0.80;                 // ACS value
    //          this.absorptivity = 0.80;
                this.emissivity = 0.91;                 // prelininaries 0.90 -> 288.08522217136186 K using Solar=1, 0.91 -> 288.04 using Solar=2.
                this.absorptivity = 0.91;
   //           this.emissivity = 0.911; 
   //           this.absorptivity = 0.911;
                this.expansivity = 0.00369;             // 1/K coefficient at 0°C and 1 bar https://www.engineeringtoolbox.com/air-properties-d_156.html
                this.modulusElasticity = 1.01325E5;     // https://www.engineeringtoolbox.com/air-properties-d_156.html
                this.colourIndex[0] = 10;               // cyan
                this.colourIndex[1] = 10;               // cyan
                this.colourIndex[2] = 10;               // cyan    ay
                break;

            case 600:
            case 610:
            case 620:
                // planet inner core (iron?)
                this.name = "inner iron core";
                this.index = i;
                this.albedo = 0.3;
                this.density = 13000;                   // http://hyperphysics.phy-astr.gsu.edu/hbase/Geophys/earthstruct.html
                this.conductivity = 79.5;               // iron
                this.specificHeat = 800.0;              // file:///C:/Users/Me/Downloads/m00024-dat-914-core-properties.pdf
                this.phaseTemperature1 = 6501;          // ?
                this.latentHeatOfFusion = 460.5;        // set same as specific heat
                this.phaseTemperature2 = 7001;          // value to ensure doesn't vaporize
                this.latentHeatOfEvaporation = 460.5;   // set same as specific heat
                this.volumetricFractionGranite = i - (int)i;
                this.radioactiveHeatGenerated = 0;
                this.emissivity = 0.87;
                this.absorptivity = 0.87;
                this.expansivity = 10.5E-6;
                this.modulusElasticity = -0;
                this.colourIndex[0] = 0;                 // black
                this.colourIndex[1] = 6;                 // orange
                this.colourIndex[2] = 7;                 // yellow
                break;

            case 700:
            case 710:
            case 720:
                // planet outer core (iron?)
                this.name = "outer iron core";
                this.index = i;
                this.albedo = 0.3;
                this.density = 12200;                   // http://hyperphysics.phy-astr.gsu.edu/hbase/Geophys/earthstruct.html
                this.conductivity = 79.5;               // iron
                this.specificHeat = 800.0;              // file:///C:/Users/Me/Downloads/m00024-dat-914-core-properties.pdf
                this.phaseTemperature1 = 6001;          // ?
                this.latentHeatOfFusion = 460.5;        // set same as specific heat
                this.phaseTemperature2 = 7001;          // value to ensure doesn't vaporize
                this.latentHeatOfEvaporation = 460.5;   // set same as specific heat
                this.volumetricFractionGranite = i - (int)i;
                this.radioactiveHeatGenerated = 0;
                this.emissivity = 0.87;
                this.absorptivity = 0.87;
                this.expansivity = 10.5E-6;             // same as inner core
                this.modulusElasticity = -0;        // unknown
                this.colourIndex[0] = 0;                // black
                this.colourIndex[1] = 6;                // orange
                this.colourIndex[2] = 7;                // yellow
                break;

            case 800:
            case 810:
            case 820:
                // planet lower mantle)
                this.name = "lower mantle";
                this.index = i;
                this.albedo = 0.3;
                this.density =  4600;                   // http://hyperphysics.phy-astr.gsu.edu/hbase/Geophys/earthstruct.html
                this.conductivity =  6.0;               // https://escholarship.org/uc/item/6ns1k73c
                this.specificHeat = 1000.0;             // https://www.colorado.edu/geolsci/courses/GEOL5700-PCE/convection.pdf
                this.phaseTemperature1 = 5001;          // ?
                this.latentHeatOfFusion = 460.5;        // set same as specific heat
                this.phaseTemperature2 = 7001;          // value to ensure doesn't vaporize
                this.latentHeatOfEvaporation = 460.5;   // set same as specific heat
                this.volumetricFractionGranite = i - (int)i;
                this.radioactiveHeatGenerated = 0;
                this.emissivity = 0.87;
                this.absorptivity = 0.87;
                this.expansivity = 15.0E-6;             // MgO
                this.modulusElasticity = -0;        // unknown
                this.colourIndex[0] = 0;                // black
                this.colourIndex[1] = 6;                // orange
                this.colourIndex[2] = 7;                // yellow
                break;

            case 900:
            case 910:
            case 920:
                // planet upper mantle)
                this.name = "upper mantle";
                this.index = i;
                this.albedo = 0.3;
                this.density =  3600;                   // http://hyperphysics.phy-astr.gsu.edu/hbase/Geophys/earthstruct.html
                this.conductivity =  4.5;               // guessed
                this.specificHeat = 1000.0;             // https://www.colorado.edu/geolsci/courses/GEOL5700-PCE/convection.pdf
                this.phaseTemperature1 =  5001;          // ?
                this.latentHeatOfFusion = 460.5;        // set same as specific heat
                this.phaseTemperature2 = 7001;          // value to ensure doesn't vaporize
                this.latentHeatOfEvaporation = 460.5;   // set same as specific heat
                this.volumetricFractionGranite = i - (int)i;
                this.radioactiveHeatGenerated = 0;
                this.emissivity = 0.87;
                this.absorptivity = 0.87;
                this.expansivity = 30.0E-6;             // MgO
                this.modulusElasticity = -0;        // unknown
                this.colourIndex[0] = 0;                // black
                this.colourIndex[1] = 6;                // orange
                this.colourIndex[2] = 7;                // yellow
                break;

            case 1000:
            case 1010:
            case 1020:
                // outer space (using characteristics of air)
                this.name = "space";
                this.index = i;
                this.albedo = -0.3;
                this.density = 0.00001;                 // density of air kg/ m^3
                this.conductivity = 0.00001;            // conductivity of air  W / m degK
                this.specificHeat = 0.0001;             // specific heat of air, J / kg deg K
                this.phaseTemperature1 = 58;
                this.latentHeatOfFusion = 1000;         // set same as specific heat
                this.phaseTemperature2 = 76.3;
                this.latentHeatOfEvaporation = 1000;    // set same as specific heat
                this.volumetricFractionGranite = i - (int)i;
                this.radioactiveHeatGenerated = 0;
                this.emissivity = 0.95;                 // ???
                this.absorptivity = 0.95;
                this.expansivity = 0.00369;             // 1/K coefficient at 0°C and 1 bar https://www.engineeringtoolbox.com/air-properties-d_156.html
                this.modulusElasticity = 1.01325E5;     // https://www.engineeringtoolbox.com/air-properties-d_156.html
                this.colourIndex[0] = 10;               // mid gray
                this.colourIndex[1] = 10;               // cyan
                this.colourIndex[2] = 10;               // light gray
                break;

            case 1100:
            case 1110:
            case 1120:
                // pumice (all the characteristics of granit except density and conductivity)
                this.name = "pumice, or bubbly granite";
                this.index = i;
                this.albedo = 0.3;
                this.density = 50.0;                    // slightly higher than polystyrene foam kg / m^3
                this.conductivity = 0.02;               // conductivity of polystyrene foam W/ m deg K
                this.specificHeat = 790;                // J/ kg degK engineering toolbox
                this.phaseTemperature1 = 1250;
                this.latentHeatOfFusion = 418000;       // conversion of 100 calories/gm
                this.phaseTemperature2 = 10000;         // large number so it won't happen
                this.latentHeatOfEvaporation = 460;     // set same as specific heat
                this.volumetricFractionGranite = 1;
                this.radioactiveHeatGenerated = 1E-9;       // modern terrestrial granite
                this.emissivity = 0.95;                 // https://www.optotherm.com/emiss-table.htm
                this.absorptivity = 0.95;
                this.expansivity = 8.0e-6;              // 10^-6
                this.modulusElasticity = 40E9;      //http://community.dur.ac.uk/~des0www4/cal/dams/geol/mod.htm
                this.colourIndex[0] = 6;                // brown
                this.colourIndex[1] = 6;                // red
                this.colourIndex[2] = 7;                // yellow
                break;

            case 1200:
            case 1210:
            case 1220:
                // granite rock  
                this.name = "chalk";
                this.index = i;
                this.albedo = 0.7;
                this.density = 1790.0;                  // kg / m^3 nora.nerc.ac.uk/7752/
//              this.conductivity = 1.78;               // W/ m deg K https://qjegh.lyellcollection.org/content/early/2018/05/28/qjegh2017-127
                this.conductivity = 0.09;               // wet low porosity W/ m deg K (may be as high as 5.00)
                this.specificHeat = 750;                // J/ kg degK https://www.engineeringtoolbox.com/specific-heat-capacity-d_391.html
                this.phaseTemperature1 = 1612;          // https://en.wikipedia.org/wiki/Calcium_carbonate
                this.phaseTemperature1 = 7000;          // guessed
                this.latentHeatOfFusion = 418000;       // guessed same as granite
                this.phaseTemperature2 =  7001;         // guessed large number so it won't happen
                this.latentHeatOfEvaporation = 720;     // guessed set same as specific heat
                this.volumetricFractionGranite = 0;
                this.radioactiveHeatGenerated = 0;      
                this.emissivity = 0.95;                 // https://www.optotherm.com/emiss-table.htm
                this.absorptivity = 0.95;
                this.expansivity = 8.0e-6;              // 10^-6 1/K same as granite
                this.modulusElasticity = 40E9;          // same as granite
                this.colourIndex[0] = 4;                // white
                this.colourIndex[1] = 12;               // cyan (water)
                this.colourIndex[2] = 7;               // yellow (steam)
                break;

            default: 
                System.out.println( "Can't find material " + i );
                break;
        } 
    }    
  
//

    // for use when ice (index 3.0) is combined with water (index 3.2)
    boolean isWater( double t ) {
        boolean isWater = false;
        if ( this.index == 3 ) {
            // water phase change over 1 degree K
            if ( t > this.phaseTemperature1+0.01  && t < this.phaseTemperature2 ) {
                isWater = true;
            }
        }
        return ( isWater );
    }

    // for use when ice (material index 3) is combined with water (material index 4)
    boolean isIce( double t ) {
        boolean isIce = false;
        if ( this.index == 3 ) {
            // water phase change over 1 degree K
            if ( t < this.phaseTemperature1+0.01  && t < this.phaseTemperature2 ) {
                isIce = true;
            }
        }
        return ( isIce );
    }

    boolean isLiquid( double t ) {
        boolean isLiquid = false;
        if ( t > this.phaseTemperature1+0.01  && t < this.phaseTemperature2 ) {
            isLiquid = true;
        }
        return ( isLiquid );
    }

    boolean isSolid( double t ) {
        boolean isSolid = false;
        if ( t < this.phaseTemperature1+0.01  && t < this.phaseTemperature2 ) {
            isSolid = true;
        }
        return ( isSolid );
    }

    boolean isAir( double t ) {
        boolean isAir = false;
        if ( this.index == 5 ) {
            isAir = true;
        }
        return ( isAir );
    }

    int indexIceTemperature( double degK ) {
        int index = 0;
        for ( int n=0; n<18; n++ ) {
            if ( celsiusToKelvin( iceCharacteristics[n][0] ) < degK ) {
                index = n;
                break;
            }
        }
        return( index);
    }

    double celsiusToKelvin( double degC ) {
        return( degC + 273 );
    }

}


class PhaseTemperature{
    
    ConductiveLayer layer;

    double t;
    double phase;
     
    double spHt0;           // solid specific heat
    double latHt1;          // latenet heat of fusion
    double spHt1;           // liquid specific heat
    double latHt2;          // latenet heat of evaporation
    double spHt2;           // vapour specific heat
    double phaseT1;         // solid-liquid phase change temperature
    double phaseT2;         // liquid-vapour phase change temperature
    double phaseT3;         // non-existent high temperature
    double phaseStep[][] = new double[6][5];
    double FractionPhaseChange;
    
    // default constructor
    PhaseTemperature() {        
    }
    
    // find heat content at each phase step
    PhaseTemperature( ConductiveLayer layer ) {
        this.layer = layer;
        double mt0 = layer.materialType;
        double mt1 = (double)(int)mt0 + 0.1;   
        Material material1 = new Material( mt1 );
        double mt2 = (double)(int)mt0 + 0.2;
        Material material2 = new Material( mt2 );
        
        this.spHt0 = this.layer.material.specificHeat;              // layer solid phase specific heat
        this.spHt1 = material1.specificHeat;                        // liquid phase specific heat
        this.spHt2 = material2.specificHeat;                        // vapour phase sepcific heat
        this.latHt1 =  this.layer.material.latentHeatOfFusion;
        this.latHt2 =  this.layer.material.latentHeatOfEvaporation;
        this.phaseT1 = this.layer.material.phaseTemperature1;
        this.phaseT2 = this.layer.material.phaseTemperature2;
//      System.out.println( "material type " + mt0 + " spht0 " + this.spHt0  );
//      System.out.println( "material type " + mt1 + " spht1 " + this.spHt1  );
//      System.out.println( "material type " + mt2 + " spht2 " + this.spHt2  );
        
        this.phaseT3 = this.phaseT2 + this.spHt2;               // imaginary temperature
        
        // absolute zero
        phaseStep[0][0] = 0;                                    // temperature change K
        phaseStep[0][1] = 0;                                    // temperature K
        phaseStep[0][2] = 0;                                    // specific or latent heat
        phaseStep[0][3] = 0;                                    // heat content
        // solid phase
        phaseStep[1][0] = this.phaseT1;                         // temperature change K
        phaseStep[1][1] = this.phaseT1;                         // temperature K
        phaseStep[1][2] = this.spHt0 * this.phaseT1;            // specific or latent heat
        phaseStep[1][3] = phaseStep[1][2];                      // heat content
        // phase change solid to liquid
        phaseStep[2][0] = 0;                                    // temperature change K
        phaseStep[2][1] = this.phaseT1;                         // temperature K
        phaseStep[2][2] = this.latHt1;                          // specific or latent heat
        phaseStep[2][3] = phaseStep[1][3] + this.latHt1;        // heat content
        // liquid phase
        phaseStep[3][0] = this.phaseT2 - this.phaseT1;          // temperature change K
        phaseStep[3][1] = this.phaseT2;                         // temperature K
        phaseStep[3][2] = phaseStep[3][0] * this.spHt1;         // specific or latent heat
        phaseStep[3][3] = phaseStep[2][3] + phaseStep[3][2];    // heat content
        // phase change liquid to vapour
        phaseStep[4][0] = 0;                                    // temperature change K
        phaseStep[4][1] = this.phaseT2;                         // temperature K
        phaseStep[4][2] = this.latHt2;                          // specific or latent heat
        phaseStep[4][3] = phaseStep[3][3] + this.latHt2;        // heat content
        // vapour phase
        phaseStep[5][0] = this.phaseT3 - this.phaseT2;          // temperature change K
        phaseStep[5][1] = this.phaseT3;                         // temperature K
        phaseStep[5][2] = phaseStep[5][0] * this.spHt1;         // specific or latent heat
        phaseStep[5][3] = phaseStep[4][3] + phaseStep[5][2];    // heat content
                
    }
    
    // find temperature and phase from layer heat content q
    double temperature( double layerHeatContentPerKg ) {
        int n =0;
        this.t = 0;
        this.phase = 0;
       
        double q = layerHeatContentPerKg;
        
        for ( n=0; n<=5; n++ ) {
            if ( q < phaseStep[n][3] ) {
                this.phase = (double)(n - 1) / 2.0;
                // where t = temperature K and q = heat J
                // t =   t[n]      +  (   q - q[n-1]   )       * T step change    / q step change     
                this.t = phaseStep[n-1][1] + ( q - phaseStep[n-1][3] ) * phaseStep[n][0] / phaseStep[n][2] ;
                
                this.FractionPhaseChange = ( q - phaseStep[n-1][3] ) / phaseStep[n][2] ;
                break;
            }
        }

        layer.nextTemperatureK =  this.t;
        if ( layer.materialType == 3.05 && phase != layer.nextPhase ) {
//          System.out.println( layer.astro.ap.year + " snow phase change to " +this.phase );
            layer.astro.ap.snowHeatAdded = 0;
        }
        layer.nextPhase = this.phase;

        return( this.t );
    }
 
    // find layer heat content from temperature (added sep 2019)
    double heatContentPerKg( double layerTemperature ) {
        double q = 0;
        
        for ( int n=0; n<=5; n++ ) {
            if ( layerTemperature < phaseStep[n][1] ) {
                q = phaseStep[n][3] + ( layerTemperature - phaseStep[n][1] ) * phaseStep[n][2] / phaseStep[n][0];                
                break;
            }
        }
        return( q );
    }
    
}



/*********** moving graph canvas *****************************************/
class ItemGraphCanvas extends Canvas {
    Glaciation ap;
    Dimension fielddimension;
//  Image fieldImage;
//  Graphics field2Graphics;
    int day;
    int maxstreams = 6;
    int nstreams;
    double lasty[] = new double[maxstreams];
    double nexty[] = new double[maxstreams];
    String streamLabel[] = new String[maxstreams];
    double streamMaxValue[] = new double[maxstreams];
    double streamMinValue[] = new double[maxstreams];
    boolean plot[] = new boolean[maxstreams];
    boolean overlappingStreams = true;
//  int graph_height = 100;
    int graphymax = 100;
    boolean started;
    Font font;
    FontMetrics fm;
    int redLetterDay;
    String ylabel = new String();
    Dimension d;
    int sideband = 25;
    int lowband = 15;
    long calendar = 0;                  // count keeping elapsed time
    double calendarStep;                // actual duration of each pixel calendar step (e.g. years)
    long calendarDisplayInterval;
    String calendarLabel;               // label to attach to calanddar dates
    boolean autoCalendar = true;        // if true, created dates automatically
    Color colorpalette[] = new Color[15];
    Image historicalRecord;             // rolling image of historical graphs
    Graphics historicalGraphics;
    Dimension hr;                       // width and height of historical record
    Image currentWindow;                // current window onto historical record
    Graphics currentGraphics;
    Dimension cw;                       // width and height of historical record



    // Constructor allows access to Field objects by this class
    public ItemGraphCanvas( Glaciation app, Graphics g, int rollingGraphWidth, int rollingGraphHeight, int ns, boolean overlap ) {
       super();
        ap = app;
        d  = new Dimension( rollingGraphWidth, rollingGraphHeight );
//    graph_height = d.height;
        nstreams = ns;
        overlappingStreams = overlap;


        for ( int n = 0; n < nstreams; n++ ) {
          lasty[n] = -100.0; nexty[n] = -100.0; streamLabel[n] = "    ";
          plot[n] = true;
        }
        ylabel = "500";
        day = 0;
        started = false;
        redLetterDay = -1;
        font = new Font( "Helvetica", Font.PLAIN, 12 );

        calendarDisplayInterval =  100000;
        calendarLabel = "e5";

        colorpalette[0] = new Color(000, 000, 000);     // black
        colorpalette[1] = new Color( 64,  64,  64);     // light grey
        colorpalette[2] = new Color(128, 128, 128);     // mid gray
        colorpalette[3] = new Color(192, 192, 192);     // darke gray
        colorpalette[4] = new Color(255, 255, 255);     // white
        colorpalette[5] = Color.RED;
        colorpalette[6] = Color.ORANGE;
        colorpalette[7] = Color.YELLOW;
        colorpalette[8] = Color.CYAN;
        colorpalette[9] = new Color( 85,  52,  52);     // brown?
        colorpalette[10] = new Color(135,206, 250);     // sky blue
        colorpalette[11] = new Color(240,240, 240);     // steam
        colorpalette[12] = new Color( 0,  0,  255);     // blue
        colorpalette[13] = new Color( 0,  0,  255);     // blue
        colorpalette[14] = new Color( 0,  0,  255);     // blue


    }

    void setNumberOfStreams( int n ) {
        nstreams = n;
    }

    public void addStream() {
      nstreams++;
    }

    public void removeStream() {
      nstreams--;
    }

    public void setStreamLabel( int stream, String s ) {
      streamLabel[stream] = s;
    }

    // set max and min values that are to be visible
    public void setStreamMaxMin( int stream, double vmax, double vmin ) {
      streamMaxValue[ stream ] =  vmax;
      streamMinValue[ stream ] =  vmin;
    }

    public void setYscale( int ymax ) {
      graphymax = ymax;
      ylabel = Integer.toString( ymax );
    }

    public void stop() {

//      field2Graphics = null;
//      fieldImage = null;
    }

//  public void redraw() { repaint(); }

    // update calls paint(), otherwise clears the screen.
    public void update(Graphics g) {
      paint(g);
    }

    public void paint(Graphics fieldGraphics ) {
      int n, h;
      String s;

      calendar += calendarStep;
//    System.out.println( "year " + (float)ap.year + " calendar " + (float)calendar + " calendarDisplayInterval " + (float)calendarDisplayInterval);

      // Create the offscreen graphics context, if no good one exists.
      if ( (fieldGraphics == null) ) {
//      fieldImage = createImage(d.width, d.height + lowband);
//      fieldGraphics = fieldImage.getGraphics();
        fieldGraphics.setColor(Color.WHITE);
        fieldGraphics.fillRect(0, 0, d.width, d.height + lowband);
        fieldGraphics.setFont( font );
//      g.drawImage(fieldImage, 0, 0, this);
      }

      fm = fieldGraphics.getFontMetrics( font );

      if ( started ) {

        // move image 1 pixel leftward
        fieldGraphics.setPaintMode();
        fieldGraphics.copyArea(1,0, d.width-sideband, d.height+lowband-1, -1, 0 );

        // rub out unmoved vertical line
        fieldGraphics.setColor(Color.WHITE);
        fieldGraphics.fillRect(d.width-sideband-1, 0, d.width-1, d.height+lowband);

        // add righthand text
        fieldGraphics.setColor(Color.darkGray);
//      fieldGraphics.drawString( ylabel, d.width-sideband+1, 10 );
//      fieldGraphics.drawString( "0", d.width-sideband/3-1, d.height-lowband );


        // draw horizontal gridlines
        if ( this.overlappingStreams ) {
            h = 0;
            for ( n=0; n<=nstreams; n++ ) {
              h = h + (d.height - lowband)/5;
                fieldGraphics.setColor(Color.darkGray);
                fieldGraphics.drawLine( d.width-sideband-2, h, d.width-sideband-1, h );
                s = Integer.toString( (int)( streamMaxValue[0] ) );
                fieldGraphics.drawString( s, d.width-sideband, rescale( 0, streamMaxValue[0] ) +9 );
                s = Integer.toString( (int)( streamMinValue[0] ) );
                fieldGraphics.drawString( s, d.width-sideband, rescale( 0, streamMinValue[0] ) -2 );
            }
        } else {
            for ( n=0; n<nstreams; n++ ) {
                fieldGraphics.setColor(Color.darkGray);
                fieldGraphics.drawLine( d.width-sideband-2, rescale(n, 0), d.width-sideband-1, rescale(n, 0) );
                s = Integer.toString( (int)( streamMaxValue[n] ) );
                fieldGraphics.drawString( s, d.width-sideband, rescale( n, streamMaxValue[n] ) +9 );
                s = Integer.toString( (int)( streamMinValue[n] ) );
                fieldGraphics.drawString( s, d.width-sideband, rescale( n, streamMinValue[n] ) -2 );
            }
        }
        // draw vertical gridlines and day numbers
        if ( autoCalendar ) {
          if ( calendar % calendarDisplayInterval == 0) {
//            System.out.println( calendar + " calendar " + calendarDisplayInterval );
              fieldGraphics.setColor(Color.darkGray);
              fieldGraphics.drawLine( d.width-sideband-1, 0, d.width-sideband-1, d.height-lowband );
              s = Integer.toString( (int)( calendar / calendarDisplayInterval ) ) + calendarLabel;
              fieldGraphics.drawString( s, d.width-sideband-fm.stringWidth(s)-1, d.height-2 );
          }
        } else if ( (day % calendarDisplayInterval ) == 0 ) {
          fieldGraphics.setColor(Color.darkGray);
          fieldGraphics.drawLine( d.width-sideband-1, 0, d.width-sideband-1, d.height );
          s = Integer.toString(day);
          fieldGraphics.setColor(Color.darkGray);
          fieldGraphics.drawString( s, d.width-sideband-fm.stringWidth(s)-1, d.height+lowband-1 );
        }

        // draw red vertical line to show event
        if ( redLetterDay >= 0 ) {
          fieldGraphics.setColor(Color.red);
          fieldGraphics.drawLine( d.width-sideband-1, 0, d.width-sideband-1, d.height );
          s = Integer.toString(redLetterDay);
          fieldGraphics.drawString( s, d.width-sideband-fm.stringWidth(s)-3, fm.getAscent() + 1 );
        }

//        System.out.println( " overlap " + nstreams );

        // draw graph lines and labels at right edge of graph
        for ( n = nstreams-1; n >= 0; n-- ) {
          if ( plot[n] ) {
            fieldGraphics.setColor(Color.orange);
            if ( n==0 ) fieldGraphics.setColor(Color.BLACK);
            if ( n==1 ) fieldGraphics.setColor(Color.red);
            if ( n==2 ) fieldGraphics.setColor(Color.green);
            if ( n==3 ) fieldGraphics.setColor(Color.cyan);
            if ( n==4 ) fieldGraphics.setColor(Color.orange);
            if ( n==5 ) fieldGraphics.setColor(Color.darkGray);
            fieldGraphics.drawLine( d.width-sideband-2, rescale( n, lasty[n] ), d.width-sideband-1, rescale( n, nexty[n] ) );
            fieldGraphics.drawString( streamLabel[n], d.width-sideband+2, rescale( n, nexty[n] ) );
          }
        }

//      fieldGraphics.setColor(Color.red);
//      fieldGraphics.fillRect(  20, 20, 20, 20);


        //Paint the image onto the screen.
//      g.drawImage(fieldImage, 0, 0, this);

      } else {
        started = true;
        fieldGraphics.setColor(Color.WHITE);
        fieldGraphics.fillRect(0, 0, d.width, d.height);
      }

      for ( n = 0; n < nstreams; n++ ) lasty[n] = nexty[n];
    }


    // rescale y value
    int rescale( double yvalue ) {
      int rsv;
      double gph = (double)d.height;
      double yv = (double)yvalue;
      double gymax = (double)graphymax;
      double yl = gph - yv * ( gph / gymax );
      rsv = (int)yl;
      return( rsv );
    }

    // new 2018 rescale y value
    // new code can handle +/- yvalue range
    int rescale( int s, double yvalue ) {
        int rsv;
        double height = d.height - lowband;
        if ( this.overlappingStreams ) {
            double vscale = height / (streamMaxValue[s] - streamMinValue[s]);
            double xaxis = vscale * streamMaxValue[s];
            rsv = (int)( xaxis - yvalue * vscale );
//          if ( s==0 ) System.out.println( "yvalue " + (float)yvalue + " vscale " + (float)vscale + " xaxis " + (float)xaxis + " rsv " + rsv);
        } else {
            // each stream height = ( d.height / (double)nstreams )
            double vscale = height / ( (streamMaxValue[s] - streamMinValue[s]) * (double)nstreams );
            double xaxis = ( (double)s * height / (double)nstreams ) + ( vscale * streamMaxValue[s] );
            rsv = (int)( xaxis - yvalue * vscale );
        }
        return( rsv );
    }
}



class LayerDisplay {
    Glaciation ap;
    int xcentre;
    int ycentre;
    int xtopleft;
    int ytopleft;
    int hr;
    double jscale;
    int xmax;
    int ymax;
    Color colorpalette[] = new Color[15];
    boolean showLayerBoundaries =  true;
    double topLayerDepth = 40000;

    int NBAND = 0;
    int MATERIALTYPE = 1;
    int RADIUS = 2;
    int HEIGHT = 3;
    int TEMPERATURE = 4;
    int PHASE = 5;
    int UHFLOWRATE = 6;

    public LayerDisplay( Glaciation a ) {
        this.ap = a;

        colorpalette[0] = new Color(000, 000, 000);     // black
        colorpalette[1] = new Color( 64,  64,  64);     // light grey
        colorpalette[2] = new Color(128, 128, 128);     // mid gray
        colorpalette[3] = new Color(192, 192, 192);     // darke gray
        colorpalette[4] = new Color(255, 255, 255);     // white
        colorpalette[5] = Color.RED;
        colorpalette[6] = Color.ORANGE;
        colorpalette[7] = Color.YELLOW;
        colorpalette[8] = Color.CYAN;
        colorpalette[9] = new Color( 85,  52,  52);     // brown?
        colorpalette[10] = new Color(135,206, 250);     // sky blue
        colorpalette[11] = new Color(240,240, 240);     // steam
        colorpalette[12] = new Color( 0,  0,  255);     // blue
        colorpalette[13] = new Color( 0,  0,  255);     // blue
        colorpalette[14] = new Color( 0,  0,  255);     // blue
    }

    void setDisplaySize( int x, int y ) {
        xmax = x;
        ymax = y;
//      System.out.println( "layer xmax " + xmax + " ymax " + ymax );
    }

    void paint( Graphics g, GeologicalColumn asteroid, double topRockRadius ) {
        double r;

        // show 200 km of top layers of body
//      double topLayerDepth =  200000.0;
        double jscale = xmax / topLayerDepth;        // screen to show top 40 km
        jscale = 20.0 * jscale;
        double tscale = xmax / 5200.0;              // temperature scale

        // clear window
        g.setColor( Color.white );
        g.fillRect( 0, 0, xmax, ymax );

        // circle centre (pixels)
        xcentre = xmax / 2;
        ycentre = (int)( topRockRadius * jscale ) + ( ymax / 2 );

        // paint atmosphere filled circle
        // surface rock radius
        int sr = (int)( ap.column[ ap.nviewLatitude ].graphicLayer[0][RADIUS] * jscale );

        xtopleft = xcentre - sr;
        ytopleft = ycentre - sr;
        hr = 2 * sr;
//      g.setColor( Color.darkGray );
//      g.drawOval( ytopleft, xtopleft, hr, hr );
        int xtoa = xtopleft;
        int ytoa = ytopleft;

        Color colour = Color.white;
        Color lastColour = Color.white;
        for ( int n=0; n< ap.column[ ap.nviewLatitude ].graphicLayerCount; n++ ) {
            colour = colorpalette[ (int)ap.column[ ap.nviewLatitude ].graphicLayer[n][8] ];
//          colour = setColour( (int)column[ nviewLatitude ].graphicLayer[n][MATERIALTYPE], column[ nviewLatitude ].graphicLayer[n][PHASE] );
            if ( colour != lastColour ) {
                r = ap.column[ ap.nviewLatitude ].graphicLayer[n][RADIUS];
                if ( r > ap.column[ ap.nviewLatitude ].topOfAtmosphere ) r = ap.column[ ap.nviewLatitude ].topOfAtmosphere;
                sr = (int)( r * jscale );
                xtopleft = xcentre - sr;
                ytopleft = ycentre - sr;
                hr = 2 * sr;
                g.setColor( colour );
                g.fillOval( xtopleft, ytopleft, hr, hr );
                lastColour = colour;
            }
        }

        if ( showLayerBoundaries ) {
           for ( int n=0; n<ap.column[ ap.nviewLatitude ].graphicLayerCount; n++ ) {
                sr = (int)( ap.column[ ap.nviewLatitude ].graphicLayer[n][RADIUS] * jscale );
                xtopleft = xcentre - sr;
                ytopleft = ycentre - sr;
                hr = 2 * sr;
                g.setColor( Color.white );
                g.drawOval( xtopleft, ytopleft, hr, hr );
            }
        }


//      g.setColor( Color.darkGray );
//      g.fillRect( 100, 120, 30, 30 );

        paintTemperatureGraph( g, asteroid, ytoa, jscale );

    }

    void paintTemperatureGraph( Graphics g, GeologicalColumn asteroid, int xtoa, double jscale ) {
        int count = 0;
        int x1, x2, y1, y2, m;
        double t, r, d;
        ConductiveLayer layer;
        ListIterator<ConductiveLayer> itr = null;
        itr = ap.column[ ap.nviewLatitude ].llayer.listIterator();

        x1 = 0;
        y1 = 0;
        t = 0;
        g.setColor( Color.black );
//      g.drawLine( centre-bradius, centre, centre, centre );
        g.setColor( Color.yellow );
        for ( int n=0; n<ap.column[ ap.nviewLatitude ].nlayers; n++ ) {
            t = ap.column[ ap.nviewLatitude ].graphicLayer[n][4];
            y1 = xtoa + (int)(( ap.column[ ap.nviewLatitude ].graphicLayer[0][2] - ap.column[ ap.nviewLatitude ].graphicLayer[n][2] ) * jscale );
            x1 = (int)( ap.column[ ap.nviewLatitude ].graphicLayer[n][4] / 50.0 );
            m = n+1;
            if ( m == ap.column[ ap.nviewLatitude ].nlayers ) m = n;
            y2 = xtoa + (int)(( ap.column[ ap.nviewLatitude ].graphicLayer[0][2] - ap.column[ ap.nviewLatitude ].graphicLayer[m][2] ) * jscale );
            x2 = (int)( ap.column[ ap.nviewLatitude ].graphicLayer[m][4] / 50.0 );
            g.drawLine( x1, y1, x2, y2 );
        }
 //     g.drawLine( x1, y1, centre, y1 );
        g.drawString( "  " + t + " degrees K", x1, y1 );
        g.drawString( "  0 degrees K", x1, (ymax/2) );

    }

    // paint 18 columbs
    void paint( Graphics g, double topRockRadius ) {
        double r;
        int x, phase, ci, xview, xloc;
        int xstep = (int)( (double)xmax / 18.0 );       // column pixel width
        int xspace = xmax - 18 * xstep - 7;             // left margin pixel width
        boolean drawColSeparators =  true;
        boolean widenViewLatitude = false;
        
        // show top layers 
        double topLayerDepth = 50000.0;
        double jscale = xmax / topLayerDepth;        // screen to show top 40 km
        jscale = 20.0 * jscale;

        // clear window
        g.setColor( Color.white );
        g.fillRect( 0, 0, xmax, ymax );
        x = xmax-1;

        // circle centre (pixels)
        xcentre = xmax / 2;
        ycentre = (int)( topRockRadius * jscale ) + ( ymax / 2 );

        // paint atmosphere filled circle
        // surface rock radius
        int sr = (int)( ap.column[ 0 ].graphicLayer[0][RADIUS] * jscale );
        

        for ( int nlat=0; nlat<ap.nLatitudes; nlat++ ) {
            Color colour = Color.white;
            Color lastColour = Color.white;

            // draw vertical coloumn separator
            if ( (nlat % 3) == 0 && drawColSeparators ) {
                g.setColor( Color.lightGray );
                g.drawLine( x, 0, x, ymax );
//              if ( ap.year < 200 ) System.out.println( "nlat " + nlat + " x " + x  + " xspace " + xspace );
                x--;
                colour = Color.lightGray;
            }

            x -= xstep;
            
            for ( int n=ap.column[ nlat ].graphicLayerCount-1; n>=0; n-- ) {
                if ( ap.column[ nlat ].layerTable[n].genericMaterialType() == 2 ) {
                    colour = setRockHeatColour( ap.column[ nlat ].layerTable[n].currentTemperatureK, ap.column[ nlat ].layerTable[n].initialTemperature, (int)20 );
                } else {
                    phase = ap.column[ nlat ].layerTable[n].phase;
                    ci = (int)ap.column[ nlat ].layerTable[n].material.colourIndex[ phase ];
                    colour = colorpalette[ ci ];
                }
//     layer.material.colourIndex[ layer.phase ];

                if ( colour != lastColour ) {
                    r = ap.column[ nlat ].layerTable[n].radius ;
                    if ( r > ap.column[ nlat ].topOfAtmosphere ) r = ap.column[ nlat ].topOfAtmosphere;
                    sr = (int)( r * jscale );
                    xtopleft = xcentre - sr;
                    ytopleft = ycentre - sr;
                    hr = 2 * sr;
//                  if ( ap.year < 100 && nlat == 9 )System.out.println( ap.year + " nBand " + ap.column[ nlat ].layerTable[n].nBand  + " mtl " + ap.column[ nlat ].layerTable[n].material.index + " r " + (float)r + " ytop " + ytopleft + " sr " + sr );
//                  if ( ap.year < 100 && nlat == 9 )System.out.println( ap.year + " nBand " + ap.column[ nlat ].layerTable[n].nBand  + " mtl " + ap.column[ nlat ].layerTable[n].material.index + " r " + (float)r + " radius " + ap.column[ nlat ].layerTable[n].radius + " ci " + ci );
//                  colour = colorpalette[ (int)( Math.random() * 5 ) ];
                    g.setColor( colour );
//                  if ( ap.column[ nlat ].layerTable[n].currentTemperatureK >  280 ) g.setColor( Color.red );
//                  g.fillOval( xtopleft, ytopleft, hr, hr );
                    xview = 0;
                    if ( ( nlat == ap.nviewLatitude ) && widenViewLatitude ) {
                        xview = xspace;             // widen viewLatititude
                        x -= xview;
                    }     
                    g.fillRect( x, ytopleft, xstep+xview, hr );
                    lastColour = colour;
                }

            }
            
        }    
            
    }    
    
    // rock heat colour rises from brown to red and then yellow
    // or it falls from brown to purple and then bluw
    Color setRockHeatColour( double rockTemperatureK, double rockInitialTemperatureK, double scale ) {
        Color heatColour = new Color( 90, 0, 0 );
        double v = ( rockTemperatureK - rockInitialTemperatureK  ) * scale;
        int i;
        int plus1 = 255 - 90;       // red limit
        int plus2 = plus1 + 255;    // yellow limit
        int minus1 = -90;           // purple limit
        int minus2 = minus1 - 90;   // blue limit
        
        if ( v > 0 ) {
            if ( v < plus1 ) {
                heatColour = new Color( 90 + (int)v, 0, 0 );
            } else if ( v < plus2 ) {
                i = (int)v - plus1;
                if ( i >=0 && i <=255 ) {
                    heatColour = new Color( 255, i, 0 );
                } else {
                    heatColour = new Color( 255, 127, 0 );
                }
            } else {
                heatColour = new Color( 255, 255, 0 );                
            }
        } else {
            if ( v > minus1 ) {
                heatColour = new Color( 90, 0, -(int)v );
            } else if ( v > minus2 ) {
                i = 90 + (int)v;
                if ( i >=0 && i <= 255 ) { 
                    heatColour = new Color( i, 0, 90 );
                } else {
                    heatColour = new Color( 45, 0, 90 );
                }
            } else {
                heatColour = new Color( 0, 0, 90 );                
            }
        }
        return( heatColour );
    }

    // rock heat colour rises from grey to red 
    // or it falls from grey to pale bluw
    Color setRockHeatColour( double rockTemperatureK, double rockInitialTemperatureK, int range ) {
        int gray = 90;
        int r, g, b;
        Color heatColour = new Color( 90, 0, 0 );
        int t = (int)( rockTemperatureK - rockInitialTemperatureK );
        
        if ( t > 0 ) {
            if ( t > range ) t = range;
            r = gray + ( ( 255 - gray ) * t ) / range;
            g = gray - ( gray * t ) / range;
            b = g;
        } else {
            if ( t < -range ) t = -range;
            r = gray + ( gray * t ) / range;
            g = gray - ( ( 255 - gray ) * t ) / range;
            b = g;
        }
        if ( ap.year < 100 && t != 0 ) System.out.println( "T " + t + " RGB " + r + ", " + g + ", " + b );
        heatColour = new Color( r, g, b );
        return( heatColour );
    }   
    
    // set colour using material index
    Color setColour( int index, double phase ) {
        Color colour = Color.white;
        Material mt = new Material( ap.column[ ap.nviewLatitude ], index );
        int cindex = 0;
        if ( phase == 1 ) cindex = 1;
        if ( phase == 2 ) cindex = 2;
        colour = colorpalette[ mt.colourIndex[ cindex ] ];
        return( colour );
    }

}


// handle annual scheduled events (adapted from Orbit3D.java )
class EventHandler {

    Glaciation ap;
    long eventQueue[] = new long[500];
    int nextEvent = 0;
    int eventCount = 0;

    int repeatEventType = 0;
    long repeatEventCZYN;
    long repeatEventTotal[] = new long[10];
    int repeatEventCount;
    long repeatEventStartCZYN;
    long repeatEventStopCZYN;
    int nrepeatEvent = 0;
    long repeatEventMeasure[][] = new long[4][1000];
    int repeatEventBody = 11;
    long repeatEventInterval = 0;  // quarter hour intervals of day

    double timestepRemainder;
    double secsperday;

    long printAsteroidCZYN;
    long setBandNumCZYN;
    long rollingGraphCZYN;
    long printEventQueueCZYN;
    long layerDisplayCZYN;
    long precipitstionCZYN;
    long pauseEventCZYN;
    long milankovitchCZYN;

    // default constructor
    public EventHandler() {}

    public EventHandler( Glaciation a ) {
        ap = a;

        printAsteroidCZYN = (long)ap.startCZYN;
        this.insertEvent( printAsteroidCZYN );
        System.out.println( "printAsteroidCZYN " + (long)printAsteroidCZYN);

        setBandNumCZYN = (long)ap.startCZYN + 9;
        this.insertEvent( setBandNumCZYN );
        System.out.println( "setBandNumCZYN " + (long)setBandNumCZYN);

        layerDisplayCZYN = (long)ap.startCZYN + 10;
        this.insertEvent( layerDisplayCZYN );

        rollingGraphCZYN = (long)ap.startCZYN + 10;
        this.insertEvent( rollingGraphCZYN );
          
        precipitstionCZYN = (long)ap.startCZYN + ap.delayedStart;      // delayed start of 
//      precipitstionCZYN = ap.eemianCZYN;              // immediate start of precipitation
        this.insertEvent( precipitstionCZYN );
        System.out.println( ap.year + ": precipitationnCZYN " + precipitstionCZYN );
  
        if ( ap.pauseDate > 0 ) {
            this.pauseEventCZYN = (long)ap.startCZYN + (long)ap.pauseDate;
            this.insertEvent( pauseEventCZYN );
        }
        
        if ( ap.solar == 3 ) {
            this.milankovitchCZYN = (long)ap.startCZYN;
            this.insertEvent( this.milankovitchCZYN );
        }
        
        System.out.println( "printAsteroidCZYN " + printAsteroidCZYN );
        System.out.println( "setBandNumCZYN " + setBandNumCZYN );
        System.out.println( "layerDisplayCZYN " + layerDisplayCZYN );
        System.out.println( "rollingGraphCZYN " + rollingGraphCZYN );
        System.out.println( "pauseEventCZYN " + pauseEventCZYN );
//      System.out.println( "precipitstionCZYN " + precipitstionCZYN );
        
    }

    // a variety of events can all happen at the same time,
    // so when an event occurs, a whole bunch of things need to be checked.
    void checkEventQueue() {
        double dt = 1.0;
        double ta;              // air temperature
        double hl;              // added layer height
        double ri;              // repeat interval
        double hmax;            // max snow/ice sheet depth
        double lastDate;
        boolean repaint = false;

        if ( nextEvent < 500 ) {
//          if ( ap.currentCZYN > 131885997 && ap.currentCZYN < 131886003 ) {
//              System.out.println( ap.currentCZYN + " eventQueue[nextEvent] " + eventQueue[nextEvent] + " " + nextEvent + " :" +  ap.year );
//              printEventQueue();
//          }
            
            if (  (long)ap.currentCZYN == eventQueue[nextEvent] ) {
//              if ( ap.printManager ) System.out.println( ap.currentCZYN + " EVENT at " + ap.currentCZYN  );
                if ( ap.printManager ) System.out.println( ap.currentCZYN + " EVENT at " + ap.currentCZYN  );

                if ( (long)ap.currentCZYN == pauseEventCZYN ) {
                    ap.go = false;   
                    System.out.println( "paused at year "+ pauseEventCZYN );
                    

                    // update display
                    ap.column[ ap.nviewLatitude ].setBandNumbers();   // get data for graphic display
                    ap.rollingGraphOutput( ap.century );
                    ap.rollingGraphUpdated = true;
                    repaint = true;
                    
                    pauseEventCZYN += ap.pauseDate;
                    this.insertEvent( pauseEventCZYN );
                }   
                
                if ( ap.currentCZYN == printAsteroidCZYN ) {
                    ap.column[ ap.nviewLatitude ].printhcmColumn( 0 );
                    if ( ap.year >= 11999 && ap.year <= 11998 ) {
                        printAsteroidCZYN +=  1;      
                    } else {
                        printAsteroidCZYN +=  6000;
                    }
                    this.insertEvent( printAsteroidCZYN );
//                  if ( ap.printManager ) System.out.println( "print asteroid " );
//                  this.printEventQueue();
                    System.out.println( "next print asteroid date " + printAsteroidCZYN );
                }

                if ( ap.currentCZYN == setBandNumCZYN ) {
                    ap.column[ ap.nviewLatitude ].setBandNumbers();
                    setBandNumCZYN += 1000;
                    this.insertEvent( setBandNumCZYN );
                    if ( ap.printManager ) System.out.println( "set band numbers " );
                }
/*
//              if ( ( ap.currentCZYN == (ap.eemianCZYN) ) || ( ap.currentCZYN == (ap.eemianCZYN + 15018) ) ) {
                if ( ap.currentCZYN == precipitstionCZYN ) {
//                  ap.column[ nviewLatitude ].addLayer( ap.column[ nviewLatitude ].thinSurfaceBand, ap.column[ nviewLatitude ].surfaceBandTemperature,  1000, 3 );
                    // end precipitation  after 10000 years
                    
                    // snow temperature is air temperature.
                    // if air temperature > 273K, snow temperature = 
                    ta = ap.column[ ap.nviewLatitude ].airLowestBandTemperatureK;
                    if ( ta > 273 ) ta = 273;
                    hl =   25.0;     // layer height
                    ri =  300.0;    // deposition interval (years)
                    hmax = 500.0;   // maximum height of layer
                    
//                  if ( ap.currentCZYN < ap.eemianCZYN + 990010 ) {
//                      System.out.println( "CZYN " + (int)ap.currentCZYN + " " + (int)ap.eemianCZYN );
//                      ap.asteroid.addLayer( ap.asteroid.thinSurfaceBand, ap.asteroid.atmosphereTemperature,    200.0, 3 );
//                      ap.asteroid.addLayer( ap.asteroid.thinSurfaceBand, 260,      50, 3.1 );
//                      ap.asteroid.addLayer( ap.asteroid.thinSurfaceBand, 270,     500, 3.0 );
//                      ap.asteroid.addLayer( ap.asteroid.thinSurfaceBand,  270.00, 3000.0, 3.00 );
//                      ap.column[ ap.nviewLatitude ].addLayer( ap.column[ ap.nviewLatitude ].thinSurfaceBand,  270.00,  260.00, 3.05 );
//                      if ( ( ap.year <  50000 || ap.year > 100000 ) && ap.asteroid.iceWaterDepth <  250 ) {
  
//                      if (  ap.column[ ap.nviewLatitude ].iceWaterDepth <  250 && ap.year < 100000 ) {
                        if (  ap.column[ ap.nviewLatitude ].iceWaterDepth < hmax ) {
//                          System.out.println( "start adding glaciation in year " + ap.year  );
//                          ap.column[ ap.nviewLatitude ].addIceLayer(  ta,  hl, 3.05 );
                            ap.column[ ap.nviewLatitude ].addLayer( ap.column[ ap.nviewLatitude ].thinSurfaceBand, ta,  hl, 3.05 );
//                          ap.column[ ap.nviewLatitude ].addLayer( ap.column[ ap.nviewLatitude ].thinSurfaceBand, ta,  hl, 12.0 );
//                          ap.column[ ap.nviewLatitude ].addIceLayer( 270.0, 10.00, 3.05 );
//                          ap.column[ ap.nviewLatitude ].addIceLayer( 270.0, 10.00, 3.05 );
//                          ap.column[ ap.nviewLatitude ].addIceLayer( 270.0, 10.00, 3.05 );
//                          ap.column[ ap.nviewLatitude ].addIceLayer( 270.0, 10.00, 3.05 );
//                          ap.column[ ap.nviewLatitude ].addIceLayer( 270.0, 10.00, 3.05 );
//                          System.out.println( ap.year + " layer height " + hl + " layer T " + ta );   
//                          System.out.println();
                        }
                        ap.column[ ap.nviewLatitude ].startOfGlaciation = ap.year;
                          
//                      ap.asteroid.setAirEmissivity = 0.999;
//                      ap.asteroid.flagSetAirEmissivity = true;

                        
  
//                      ap.icedropCounter = 100;
//                      ap.solar = 0;

//                      precipitstionCZYN += 5000;
                        if ( ap.column[ ap.nviewLatitude ].varyIceAlbedo ) {
                            ap.column[ ap.nviewLatitude ].varyIceAlbedoReset = true;
                        }
                        ap.column[ ap.nviewLatitude ].resynchronise = true;  // need to renumber layers
//                  } else {
//                      ap.column[ nviewLatitude ].addLayer( ap.column[ nviewLatitude ].thinSurfaceBand, ap.column[ nviewLatitude ].atmosphereTemperature,   25.0, 3 );
                        precipitstionCZYN +=     ri;
//                  }

                    this.insertEvent( precipitstionCZYN );
                }
*/                
                // add snow to active columns
                for ( int ncol=0; ncol<=17; ncol++ ) {
                    if ( ap.column[ ncol ].columnActiveCalculation ) {
                        if ( ap.column[ ncol ].snowStartCZYN <= ap.currentCZYN && ap.column[ ncol ].snowStopCZYN > ap.currentCZYN ) {
                            if ( ap.currentCZYN == ap.column[ ncol ].precipitstionCZYN ) {
                                System.out.println( ap.year + ": CZYN " + ap.currentCZYN );
                                ta = ap.column[ ncol ].airLowestBandTemperatureK;  // air temperature
//                              if ( ta > 273 ) ta = 273;
                                hl = ap.column[ ncol ].snowLayerThickness;         // layer height
                                ri =  ap.column[ ncol ].snowDepositionInterval;    // deposition interval (years)
                                hmax = ap.column[ ncol ].snowMaximumDepth;         // maximum height of layer
                                if (  ap.column[ ncol ].iceWaterDepth < hmax ) {
                                    ap.column[ ncol ].addLayer( ap.column[ ncol ].thinSurfaceBand, ta,  hl, ap.column[ ncol ].snowType );                                
                                    ap.column[ ncol ].resynchronise = true;  // need to renumber layers
                                    ap.column[ ncol ].precipitstionCZYN += ri;
                                    this.insertEvent( (long)ap.column[ ncol ].precipitstionCZYN );
//                                  if ( !ap.snowing ) ap.setInitialTemperatures();
                                    ap.snowing = true;
                                } else {
                                    ap.column[ ncol ].precipitstionCZYN += ri;
                                    this.insertEvent( (long)ap.column[ ncol ].precipitstionCZYN );                                    
                                }
                            }   
                        }    
                    }
                }
                  

                if ( ap.currentCZYN == layerDisplayCZYN ) {
//                  if ( ap.currentCZYN - (long)ap.eemianCZYN > 65000000 ) System.out.println( ap.currentCZYN + " layerDisplay layerDisplayCZYN " + layerDisplayCZYN );
                    repaint = true;
                    layerDisplayCZYN += 100000;
                    this.insertEvent( layerDisplayCZYN );
                }

                if ( ap.currentCZYN == rollingGraphCZYN ) {

//                  if ( ap.albedoTest ) ap.column[ ap.nviewLatitude ].incrementThinSurefaceLayerAlbedo( );

                    ap.column[ ap.nviewLatitude ].setBandNumbers();   // get data for graphic display
                    ap.rollingGraphOutput( ap.century );
                    ap.rollingGraphUpdated = true;
                     repaint = true;
                   rollingGraphCZYN += ap.canvas1.calendarStep;
                    this.insertEvent( rollingGraphCZYN );
                    if ( ap.printManager ) System.out.println( (float)(ap.currentCZYN - ap.eemianCZYN) + " rollingGraph numbers " );
//                  if ( ap.currentCZYN > 131885700 && ap.currentCZYN < 131886003 ) System.out.println( ap.currentCZYN + " new rollingGraphCZYN " + rollingGraphCZYN  );
                }

                if ( ap.currentCZYN == milankovitchCZYN ) {
                    //Milankovitch ebentManager returns next date 
                    lastDate = ap.currentCZYN;
                    milankovitchCZYN = ap.mil.eventManager( milankovitchCZYN );
                    System.out.println( "Milankovitvh current " + ap.currentCZYN + " next " + milankovitchCZYN );
                    if ( lastDate !=milankovitchCZYN ) this.insertEvent( milankovitchCZYN );   // don't add event if same date 
                }    
                
                removeEvent( (long)ap.currentCZYN );

                if ( repaint == true ) ap.repaint();
            }
        } else {
            nextEvent = 0; // prevent overflow
        }
    
    }

    void setupRepeatEvent( int type, double startCZYN, int bn, double duration, double interval ) {
        repeatEventType = type;
        repeatEventBody = bn;
        if ( type == 1 ) {
            repeatEventCZYN = (long)ap.currentCZYN;
            repeatEventTotal[0] = 0;
            repeatEventTotal[1] = 0;
            repeatEventTotal[2] = 0;
            repeatEventCount = 0;
            repeatEventStopCZYN = repeatEventCZYN + (long)duration;         // record for some period of time
            repeatEventInterval = (long)interval;
            repeatEventCZYN = ap.currentCZYN + (long)( interval );    // add quarter hour
            insertEvent( repeatEventCZYN );
            System.out.println( "currentCZYN " + ap.currentCZYN + " repeatEventCZYN scheduled for " + repeatEventCZYN );
            printEventQueue();
        } else if ( type == 2 || type == 3 || type == 4 || type == 5 ) {
            repeatEventStartCZYN = (long)startCZYN;
            repeatEventStopCZYN = (long)startCZYN + (long)duration;
            repeatEventCount = 0;
            repeatEventInterval = (long)interval;
            repeatEventCZYN = (long)startCZYN;
            insertEvent( (long)startCZYN );
            System.out.println(  "repeat event scheduled for " + startCZYN );
        }

    }


    // A variety of dates need to be accurately hit. These include:
    // 1. Historical dates on which known planetary body positions are corrected.
    // 2. Stop dates when state vectors are printed.
    // 3. Body state changes defined by b[n].initialCZYN and finalCZYN
    // 4. Rockcloud halo launch dates
    // The timestep manager keeps track of the nearest upcoming date from a variety of sources,
    // and, if necessary, alters dt in order to hit these dates.
    double timestepManager( double currentTimestep ) {
        double nextjd, timestepToEvent;
        double newTimestep = currentTimestep;

//      if ( pool.currentCZYN > 2454877.2 && pool.currentCZYN < 2454877.4) System.out.println( "***MANAGER " + pool.currentCZYN + " nextCZYN : " + eventQueue[nextEvent] + " dt " + currentTimestep );
//      System.out.println( "***MANAGER " + pool.currentCZYN + " nextCZYN : " + eventQueue[nextEvent] + " dt " + currentTimestep );

        // if there is a remaining timestep to complete, use it as the next timestep
        // so as to not break the timestep sequence.
        if ( timestepRemainder != 0.0 ) newTimestep = timestepRemainder;

        nextjd = eventQueue[nextEvent];
//      if ( nextjd <= pool.currentCZYN ) nextEvent++;  // if next event is before current date, try next event along.

        // if timestep length < current dt, return shortest.
        timestepToEvent = ( nextjd -   ap.currentCZYN ) * secsperday;
        if ( Math.abs(timestepToEvent) < Math.abs(newTimestep) ) {
            timestepRemainder = newTimestep - timestepToEvent;
            newTimestep = timestepToEvent;
//          if ( printManager )  {
//              if ( newTimestep != dt ) System.out.println( "MANAGER " + pool.currentCZYN + " new timestep : " + newTimestep + " current timestep : " + currentTimestep + " rem " + timestepRemainder );
//          }
        } else {
            timestepRemainder = 0;
        }
//      if ( pool.currentCZYN > 2454877.2 && pool.currentCZYN < 2454877.4) System.out.println( "MANAGER " + pool.currentCZYN + " nextjd " + nextjd + " timestep : " + newTimestep + " rem " + timestepRemainder );

        return( newTimestep );
    }


    double getNearestDate( double currentDate, double currentNearestDate, double offeredDate, double timestep ) {
        double newDate = currentNearestDate;
        if ( timestep > 0 ) {
            if ( offeredDate > currentDate ) {
                if ( offeredDate < currentNearestDate ) newDate = offeredDate;
            }
        } else {
            if ( offeredDate < currentDate ) {
               if ( offeredDate > currentNearestDate ) newDate = offeredDate;
            }
        }
        return( newDate );
    }


    // add event to event queue if it doesn't already exist
    void addEvent( long jd ) {
        int n;
        boolean found = false;
        for ( n=0; n<eventCount; n++ ) {
            if ( eventQueue[n] == jd ) {
                found = true;
                break;
            }
        }
        if ( !found ) {
            eventQueue[ eventCount ] = jd;
            eventCount++;
        }
    }

    // Insert Julian date jd into date-ordered event queue
    void insertEvent( long jd ) {
        int n, i;
        boolean found = false;
        if ( eventCount == 0 ) {
            eventQueue[0] = jd;                   // if no events, add new event
            eventCount++;
//          eventQueue[1] = jd * 2;               // dummy terminal event
//          eventCount++;
        } else {
            for ( n=0; n<eventCount; n++ ) {
                if ( eventQueue[n] == jd ) {
                    found = true;                           // event already exists
                    break;
                } else if ( eventQueue[n] > jd ) {
                    eventCount++;
                    for ( i=eventCount; i>n; i-- ) {
                        eventQueue[i] = eventQueue[i-1];    // move subsequent events down queue
                    }
                    eventQueue[n] = jd;                     // add new event
                    break;
                } else if ( n == eventCount-1 ) {
                    eventCount++;                           // add new event to end of the queue
                    eventQueue[n+1] = jd;
                }
            }
        }
//      if ( ap.currentCZYN > 131885000 && ap.currentCZYN < 131886003 ) { 
//          System.out.println( ap.currentCZYN + " new event inserted at " + jd );
//          printEventQueue();
//      }
    }

    // Insert Julian date jd into date-ordered event queue before NextEvent pointer
    void insertNowEvent( long jd ) {
        for ( int i=eventCount; i>nextEvent; i-- ) {
            eventQueue[i] = eventQueue[i-1];    // move subsequent events down queue
        }
        eventQueue[nextEvent] = (long)jd;                     // add new event
        eventCount++;
        nextEvent++;                                    // point to next event after it
    }

    // Insert Julian date jd into date-ordered event queue
    void insertIntermediateEvent( long jd ) {
        int n, i;
        boolean found = false;
        if ( jd > eventQueue[0] && jd < eventQueue[eventCount-1]) {
            for ( n=0; n<eventCount; n++ ) {
                if ( eventQueue[n] == jd ) {
                    found = true;                           // event already exists
                    break;
                } else if ( eventQueue[n] > jd ) {
                    eventCount++;
                    for ( i=eventCount; i>n; i-- ) {
                        eventQueue[i] = eventQueue[i-1];    // move subsequent events down queue
                    }
                    eventQueue[n] = (long)jd;                     // add new event
                    break;
                }
            }
        }
    }


    // remove Julian date jd event from event queue
    void removeEvent( long jd ) {
        int n, i;
        boolean found = false;
        for ( n=0; n<eventCount; n++ ) {
            if ( eventQueue[n] == jd ) {
                for ( i=n; i<eventCount; i++ ) {
                    eventQueue[i] = eventQueue[i+1];    // move subsequent events up queue
                    eventQueue[i+1] = 0;
                }
                eventCount--;
                break;
            }
        }
    }

    void printEventQueue() {
        System.out.println( "Event Queue count = " + eventCount  );
        for ( int n=0; n<eventCount; n++ ) {
            System.out.println( n + " " + eventQueue[n] );
        }
        System.out.println( "nextEvent " + nextEvent );
    }


}


class SolarRadiation {
    // source: Solar Radiation Calculation Dr. Mohamad Kharseh
    // source: https://www.researchgate.net solarRadiationCalc.pdf by Dr. Mohamad Kharseh
    // mean terrestrial solar radiation of 342 W/m^2 = 2.95E7 W / m^2 day
    // Earth Mean Orbital Elements (J2000) source: https://nssdc.gsfc.nasa.gov/planetary/factsheet/earthfact.html
    //  Semimajor axis (AU)                  1.00000011  
    //  Orbital eccentricity                 0.01671022   
    //  Orbital inclination (deg)            0.00005  
    //  Longitude of ascending node (deg)  -11.26064  
    //  Longitude of perihelion (deg)      102.94719      
    //  Mean Longitude (deg)               100.46435
    double timeStepTable[][] = new double[2][370];      // J
    int nTimeSteps;
    double timeStep;                                    // days
    double meanSolarPower;
    double latitude;                                    // +/-degrees
    double solarConstant;                               // Watts / square metre
    int currentTimeStep;
    double eccentricity;
    double obliquity;                                   // radians
    double perihelionLongitude;                         // radians
    double secondsPerYear = 3.154e+7;
    

    public SolarRadiation() { }

    // test code
    public SolarRadiation( double solarConstant ) {
        timeStepTerrestrialRadiation( solarConstant, 183.75, 185.25, 65 );
    }
    

    // create table of solar heat gains during each time step at given latiude
    // try to set integer number of timesteps per year (e.g. 1000 per year)
    public SolarRadiation( Glaciation a, GeologicalColumn gc, double solarConstant, double latitude, double timestepYears, double daysPerYear, double e, double ob, double pl ) {
        Glaciation ap = a;
        double ho;
        double timestepDays = daysPerYear * timestepYears;
        int nTimestepsPerYear = (int)( daysPerYear / timestepDays );
        this.solarConstant = solarConstant;
        this.latitude = latitude;
        this.timeStep = timestepDays;
        this.nTimeSteps = nTimestepsPerYear;
        this.eccentricity = e;
        this.obliquity = ob;
        this.perihelionLongitude = pl;
        this.currentTimeStep = 0;
        double daynum = 0;
        double totho = 0;
        int step = 1;
        timeStepTable[0][ 0 ] = 0;
        for ( int s=1; s<=nTimestepsPerYear; s++ ) {
            ho = timeStepTerrestrialRadiation( solarConstant, daynum, daynum+timestepDays, latitude );
            totho += ho;
            timeStepTable[1][ step ] = totho;
            daynum += timestepDays;
            timeStepTable[0][ step ] = ho;
            step++;
        }
        this.meanSolarPower = totho / ap.secondsPerYear;            // watts
//      System.out.println( "total solar gain " + (long)totho + " mean power (watts) " + this.meanSolarPower  );
        System.out.println( "nTimestepsPerYear " + nTimestepsPerYear );
        timeStepTable[0][ step ] = -1;  // terminator character if needed
//      printTimeStepTable();

        gc.meanTimestepSolarGain =  totho / (double)nTimestepsPerYear;
//      System.out.println(" totho " + totho + " steps " + nTimestepsPerYear + " meanTimestepSolarGain " + (int)gc.meanTimestepSolarGain );
        this.meanSolarPower = totho / this.secondsPerYear;   // assuming time step one day
//      System.out.println(" mean annual solar power " + this.meanSolarPower );
    }

    void printTimeStepTable() {
        int n = 0;
        System.out.println( "timeStepTable at latitude " + this.latitude );
        while ( this.timeStepTable[0][n] >= 0 ) {
            System.out.printf("% 9d", n );
            System.out.printf("% 9d", (int)this.timeStepTable[0][n] );
            System.out.println();
            n++;
        }
    }

    // return the solar gain (J) for the current time step, and update the current time step number
    double getNextSolarGain() {
        double solargain = this.timeStepTable[0][ this.currentTimeStep ];
        this.currentTimeStep++;
        if ( this.timeStepTable[0][ this.currentTimeStep ] < 0 ) this.currentTimeStep = 0;
        return( solargain );
    }
    
    // return total solar gain received between startDay and endDay
    // endDay > startDay in range 0 to 365
    double getTotalSolarGain( int startDay, int endDay ) {
  //    System.out.println( "startDay" + startDay + " endDay " + endDay );
        double solargain = this.timeStepTable[1][endDay] - this.timeStepTable[1][startDay];
        return( solargain );
    }

    
    // tested, working for non-integer timesteps of n days.
    // use to create a timestepTable of nsteps of terrestrial solar heat gains at different latitudes
    double timeStepTerrestrialRadiation(  double solarConstant, double dn1, double dn2, double latitude ) {
        double toth = 0;
        double h = 0;

        // parse start and stop day numbers
        if ( dn2 > dn1 ) {

            // start day
            boolean s1i = false;
            int sd1 = (int)dn1;
            double f11 = dn1 - (int)dn1;                            // remaining fraction of first day
            if ( f11 == 0 ) s1i = true;                             // integer start day
            f11 = fractionAngle( f11 );
            double f12 = 180;
            // stop day
            boolean s2i = false;
            int sd2 = (int)dn2;
            double f22 = dn2 - (int)dn2;
            if ( f22 == 0 ) s2i = true;                             // integer stop day
            f22 = fractionAngle( f22 );                                 // initial fraction of last day
            double f21 = -180;

//          System.out.println( dn1 + " to " + dn2 + ": " + sd1 + " " + s1i + " + " + sd2  + " " + s2i  );
            if ( sd1 == sd2 ) {
                // period is fraction of one day, sd1
                f12 = f22;
//              System.out.println( sd1 + ": " + f11 + " to " + f12 + " same day" );
                h = hourlyExtraTerrestrialRadiation( solarConstant, sd1, f11, f12, latitude  );
//              System.out.println( "h = " + (int)h );
                toth += h;
            } else {
                int wd1 = sd1 + 1;
                int wd2 = sd2 - 1;

                if ( !s1i ) {
                    // start day fraction
//                  System.out.println( sd1 + ": " + f11 + " to " + f12 + " start" );
                    h = hourlyExtraTerrestrialRadiation( solarConstant, sd1, f11, f12, latitude  );
//                  System.out.println( "h = " + (int)h );
                    toth += h;
                } else {
                    wd1 = sd1;
                }
                // whole days
                for ( int n=wd1; n<=wd2; n++ ) {
//                  System.out.println( n + " whole" );
                    h = dailyExtraTerrestrialRadiation( solarConstant, wd1, latitude );
//                  System.out.println( "h = " + (int)h );
                    toth += h;
                }
                // stop day fraction
                if ( !s2i ) {
//                 System.out.println( sd2 + ": " + f21 + " to " + f22 + " stop" );
                    h = hourlyExtraTerrestrialRadiation( solarConstant, sd2, f21, f22, latitude  );
//                  System.out.println( "h = " + (int)h );
                    toth += h;
                } else {
                    wd1 = sd1;
                }

            }
//          System.out.println( "toth = " + (int)toth );

        } else {
            System.out.println( "invalid day numbers");
        }

        return( toth );
    }

    // when fraction is 0.5, this corresponds to noon, hour angle 0
    // when fraction is 0 or 1, hour angle = 180
    double fractionAngle( double fraction ) {
        double angle = ( fraction - 0.5 ) * 360.0;
//      System.out.println( fraction + " a " + angle );
        return( angle );
    }

    // degrees are coverted to radians
    // tested working (produces slighly smaller values over a day than dailETradiation))
    double hourlyExtraTerrestrialRadiation( double solarConstant, double dayNumber, double ha1, double ha2, double latitude  ) {
        double hourlyRadiation = 0;
        double hourAngle1 = degreesToRadians( ha1 );
        double hourAngle2 = degreesToRadians( ha2 );
        double lat = degreesToRadians( latitude );                              // radians
        double declination = declination( dayNumber );                          // radians
        double sunsetHourAngle = hourAngle( lat, declination );                 // radians
        double hoursSunlight = 2.0 * radiansToDegrees( sunsetHourAngle ) / 15.0;
        double f1 = 12.0 * 3600.0 * solarConstant / Math.PI;
        // 0,033 is twice the eccentricity of the Earth's orbit
        double f2 = 1.0 + (2.0 * this.eccentricity) * Math.cos ( degreesToRadians( ( 360.0 * dayNumber ) / 365.0 ) );
        double f3 = Math.cos(lat) * Math.cos(declination)* ( Math.sin(hourAngle2) - Math.sin(hourAngle1 ) );
        double f4 = ( hourAngle2 - hourAngle1 ) * Math.sin(lat) * Math.sin(declination);
        hourlyRadiation = f1 * f2 * ( f3 + f4 );
        if ( hourlyRadiation < 0 ) hourlyRadiation = 0;
        return( hourlyRadiation );
    }

    // degrees are coverted to radians
    // tested working
    double dailyExtraTerrestrialRadiation( double solarConstant, double dayNumber, double latitude  ) {
        double dailyRadiation;
        double lat = degreesToRadians( latitude );                              // radians
        double declination = declination( dayNumber );                          // radians
        double sunsetHourAngle = hourAngle( lat, declination );                 // radians
        double f1 = 24.0 * 3600.0 * solarConstant / Math.PI;
        // 0,033 is twice the eccentricity of the Earth's orbit
        double f2 = 1.0 + (2.0 * this.eccentricity) * Math.cos ( degreesToRadians( ( 360.0 * dayNumber ) / 365.0 ) );
        double f3 = Math.cos(lat) * Math.cos(declination)* Math.sin(sunsetHourAngle );
        double f4 = sunsetHourAngle * Math.sin(lat) * Math.sin(declination);
        // was f4 = Math.PI * sunsetHourAngle(degrees) * Math.sin(lat) * Math.sin(declination ) / 180.0;
        dailyRadiation = f1 * f2 * ( f3 + f4 );
        if ( dailyRadiation <= 0 ) {
            dailyRadiation = 0;
//          System.out.println( latitude + " sunsetHourAngle " + sunsetHourAngle + " declination " + declination);
        }
        return( dailyRadiation );
    }

    double instantaneousExtraTerrestrialRadiation( double solarConstant, double dayNumber, double hourAngle, double latitude ) {
        double instantaneousRadiation;
        double hourAngleR = degreesToRadians( hourAngle );
        double lat = degreesToRadians( latitude );                              // radians
        double declination = declination( dayNumber );                          // radians
        double f1 = solarConstant;
        // 0,033 is twice the eccentricity of the Earth's orbit
        double f2 = 1.0 + (2.0 * this.eccentricity) * Math.cos ( degreesToRadians( ( 360.0 * dayNumber ) / 365.0 ) );
        double f3 = Math.cos(lat) * Math.cos(declination)* Math.cos(hourAngleR);
        double f4 = Math.sin(lat) * Math.sin(declination);
        instantaneousRadiation = f1 * f2 * ( f3 + f4 );
        if ( instantaneousRadiation < 0 ) instantaneousRadiation = 0;
        return( instantaneousRadiation );
    }

    // Declination is the angle made between the plane of the equator
    // and the line joining the two centres of the earth and the sun
    // tested working
    double declination( double dn ) {
        double ob = radiansToDegrees( this.obliquity );
//      double d = ob * Math.sin( degreesToRadians( 360 * ( 284.0 + dn ) / 365.0 ) );

        // dayOffset = 284 when this.perihelionLongitude = 1.7962.
        // and has a range of 365 days when when this.perihelionLongitude is between 0 and 2.PI radians
        double dayOffset = 284.0 + ( this.perihelionLongitude - 1.7962 ) * 365.25 / ( 2.0 *Math.PI );
        double d = ob * Math.sin( degreesToRadians( 360 * ( dayOffset + dn ) / 365.0 ) );

        return( degreesToRadians( d ) );
    }

    // The hour angle is the sun’s angular deviation from south
    // tested working
    double hourAngle( double lat, double dec ) {
        double ha;
        double rightAngle = 0.5 * Math.PI;
        if ( lat >= 0 ) {
            // northern hemisphere (tested)
            if ( (lat-dec) > rightAngle ) {
                // sun never rises
                ha = 0;
            } else if ( (lat+dec) > rightAngle ) {
                // sun never sets
                ha =  Math.PI;
            } else {
                // sun rises and sets
                ha = Math.acos( -Math.tan(lat) * Math.tan(dec) );
            }

        } else {
            // southern hemisphere (untested)
            if ( (-lat-dec) > rightAngle ) {
                // sun never sets
                ha =  Math.PI;
            } else if ( (-lat+dec) > rightAngle ) {
                // sun never rises
                ha = 0;
            } else {
                // sun rises and sets
                ha = Math.acos( -Math.tan(lat) * Math.tan(dec) );
            }
        }

//      if ( lat == rightAngle ) System.out.println( "ha " + ha );
        return( ha );
    }

    double degreesToRadians( double d ) {
        double r = d * Math.PI / 180.0;
        return( r );
    }

    double radiansToDegrees( double r ) {
        double d = r * 180.0 / Math.PI;
        return( d );
    }

    // untested
    double arcos(double x) {
        double a = Math.atan(  ( Math.sqrt(1-x*x)  )  / x );
        return( a );
    }

    private void printDebugData(JTable table, double step1 ) {
        int numRows = table.getRowCount();
        int numCols = table.getColumnCount();
        javax.swing.table.TableModel model = table.getModel();

/*
        source: https://www.cs.colostate.edu/~cs160/.Summer16/resources/Java_printf_method_quick_reference.pdf
        Java printf( ) Method Quick Reference
        System.out.printf( “format-string” [, arg1, arg2, … ] );

        Format String:
        Composed of literals and format specifiers. Arguments are required only if there are format specifiers in the
        format string. Format specifiers include: flags, width, precision, and conversion characters in the following
        sequence:
        % [flags] [width] [.precision] conversion-character ( square brackets denote optional parameters )

        Flags:
        - : left-justify ( default is to right-justify )
        + : output a plus ( + ) or minus ( - ) sign for a numerical value
        0 : forces numerical values to be zero-padded ( default is blank padding )
        , : comma grouping separator (for numbers > 1000)
        : space will display a minus sign if the number is negative or a space if it is positive

        Width:
        Specifies the field width for outputting the argument and represents the minimum number of characters to
        be written to the output. Include space for expected commas and a decimal point in the determination of
        the width for numerical values.

        Precision:
        Used to restrict the output depending on the conversion. It specifies the number of digits of precision when
        outputting floating-point values or the length of a substring to extract from a String. Numbers are rounded
        to the specified precision.

        Conversion-Characters:
        d : decimal integer [byte, short, int, long]
        f : floating-point number [float, double]
        c : character Capital C will uppercase the letter
        s : String Capital S will uppercase all the letters in the string
        h : hashcode A hashcode is like an address. This is useful for printing a reference
        n : newline Platform specific newline character- use %n instead of \n for greater compatibility

        Examples:
        System.out.printf("Total is: $%,.2f%n", dblTotal);
        System.out.printf("Total: %-10.2f: ", dblTotal);
        System.out.printf("% 4d", intValue);
        System.out.printf("%20.10s\n", stringVal);
        String s = "Hello World";
        System.out.printf("The String object %s is at hash code %h%n", s, s);

        String class format( ) method:
        You can build a formatted String and assign it to a variable using the static format method in the String class.
        The use of a format string and argument list is identical to its use in the printf method. The format method
        returns a reference to a String. Example:
        String grandTotal = String.format("Grand Total: %,.2f", dblTotal);
*/


        System.out.println("Value of data: ");
        for (int i=0; i < numRows; i++) {
            System.out.printf( "% 6.1f", 2.5 + (double)(i*step1) );
            for (int j=0; j < numCols; j++) {
                System.out.printf("% 9d", model.getValueAt(i, j) );
//             System.out.print("  " + model.getValueAt(i, j));
            }
            System.out.println();
        }
        System.out.println("--------------------------");
    }
}


class Milankovitch {
    
    Glaciation exp;
    long currentEpoch;
    long irregularStartDate;
    long irregularStopDate;
    int irregularRowCount;
    long regularInterval;
    double eventTable[][] = new double[1000][4];
    int eventTablerowcount;
    
    double currentDate;
    double eccentricity;
    double obliquity;
    double perihelionLongitude;
    double nextDate;
    double degreesToRadians = Math.PI / 180;


    public Milankovitch() {
    }

    public Milankovitch( Glaciation a, String filename, long epoch, int interval ) {
        this.exp = a;
        this.currentEpoch = epoch;
        this.regularInterval = interval;
        
        System.out.println( "Milankovitch start" );

        creatMergedTable( filename );

        System.out.println( "Milankovitch end" );

    }
    
    
    void creatMergedTable( String filename ) {
        
        double irregular[][] = csvReader( filename );
        
        int index = 0;
        int irregularIndex = 0;
        int regularIndex = 0;
        long irregularDate;                     // current regular date
        long currentDate = irregularStartDate;  // current date
        long regularDate = irregularStartDate;  // current irregular date
   
        // first entry is first irrgular date 
        eventTable[index][0] = irregularStartDate;
        eventTable[index][1] = irregular[irregularIndex][1];
        eventTable[index][2] = irregular[irregularIndex][2];
        eventTable[index][3] = irregular[irregularIndex][3];
        currentDate = irregularStartDate;
        System.out.println( "[" + index + "] " + (long)eventTable[index][0] + ": " + eventTable[index][1] + " " + eventTable[index][2] * this.degreesToRadians + " " + eventTable[index][3] * this.degreesToRadians );
//      System.out.println( index + " merge idate " + irregularStartDate );
        index++;
        irregularIndex++;
        regularDate += this.regularInterval;
               
        while ( regularDate <= irregularStopDate ) {
            irregularDate = (long)irregular[ irregularIndex ][0] + this.currentEpoch;
            while ( regularDate >= irregularDate ) {
                eventTable[index][0] = irregularDate;
                eventTable[index][1] = irregular[irregularIndex][1];
                eventTable[index][2] = irregular[irregularIndex][2];
                eventTable[index][3] = irregular[irregularIndex][3];
                currentDate = irregularDate;
                System.out.println( "[" + index + "] " + (long)eventTable[index][0] + ": " + eventTable[index][1] + " " + eventTable[index][2] * this.degreesToRadians + " " + eventTable[index][3] * this.degreesToRadians );
//              System.out.println( index + " merge idate " + irregularDate + " " + irregularIndex + " " + irregular[ irregularIndex ][0] );
                index++;
                irregularIndex++;       
                if ( irregularIndex > irregularRowCount  ) break;
                irregularDate = (long)irregular[ irregularIndex ][0] + this.currentEpoch;
            }
            if ( irregularIndex > irregularRowCount  ) break;
            // add emptry regular event to eventTable[]
            if ( regularDate >= currentDate ) {
                eventTable[index][0] = regularDate;
                eventTable[index][1] = -1;
                eventTable[index][2] = -1;
                eventTable[index][3] = -1;
                currentDate = regularDate;
                System.out.println( "[" + index + "] " + (long)eventTable[index][0] + ": " + eventTable[index][1] + " " + eventTable[index][2] * this.degreesToRadians + " " + eventTable[index][3] * this.degreesToRadians );
//              System.out.println( index + " merge date " + regularDate );
            }
            regularDate += this.regularInterval;
            index++;
        }
        
//      System.out.println( "print 0-35" );
//      for ( index=0; index<35; index++ ) {
//          System.out.println( "[" + index + "] " + (long)eventTable[index][0] + ": " + eventTable[index][1] + " " + eventTable[index][2] + " " + eventTable[index][3] );            
//      }
        
        // interpolate values to fill table
        double v1, v2;
        double t1, t2, t3;
        int i[][] = {
            { 0, 0, 0, 0, },
            { 0, 0, 0, 0, }
        };
        index = 0;
        while ( eventTable[ index ][0] < irregularStopDate ) {
            for ( int j=1; j<=3; j++ ) {
                if ( index == i[1][j] ) {
                    // re-index
                    i[0][j] = index;
                    i[1][j]++;
                    while ( eventTable[ i[1][j] ][j] == -1 ) {
                        i[1][j]++;
                    }
                    if ( eventTable[ i[0][j] ][0] < 64816000 ) System.out.println( j + ": " + i[0][j] + " "  + i[1][j] + " v: " + eventTable[ i[0][j] ][j] + " " + eventTable[ i[1][j] ][j] );
                }
                v1 = eventTable[ i[0][j] ] [j];
                v2 = eventTable[ i[1][j] ] [j];
                t1 = eventTable[ i[0][j] ] [0];
                t2 = eventTable[ i[1][j] ] [0];
                t3 = eventTable[ index ] [0];
                eventTable[ index ][j] = interpolate ( v1, t1, v2, t2, t3 );
            }
            if ( eventTable[ index ][0] < 67816000 ) System.out.println( "[" + index + "] " + (long)eventTable[ index ][0] + " " + (float)eventTable[ index ][1] + " " + (float)eventTable[ index ][2] * this.degreesToRadians + " " + (float)eventTable[ index ][3] * this.degreesToRadians );
            index++;
        }
        eventTablerowcount = index;
/*        
        eventManager( 65938000 );
        eventManager( 65938020 );
        
        eventManager( 65693000 );
        eventManager( 66111000 );
*/
    }

    double interpolate ( double v1, double t1, double v2, double t2, double t3 ) {
        double v = v1;
        double f;
        if ( t2 > t1 ) {
            f = ( t3 - t1 ) / ( t2 - t1 );
            v = v1 + f * ( v2 - v1 );
        }
        return( v );
    }
   
    
    double[][] csvReader( String file ) {
        String dataRow;
        String rs[] = new String[10];
        int nlayers, rowcount, fieldcount;
        boolean bodyok;
        boolean filefound = false;
        BufferedReader CSVFile;
//      double variable[] = new double[10];
        double variable[][] = new double[500][4];
        rowcount = 0;

        try {
            CSVFile = new BufferedReader(new FileReader(file));
            rowcount = 0;
            try {
                dataRow = CSVFile.readLine(); // Read first line.
                System.out.println( dataRow  );
            } catch (IOException e) {
                dataRow = new String("");
            }

            while ( dataRow != null ) {
                bodyok = false;
                filefound = true;
                StringTokenizer st = new StringTokenizer(dataRow, ",");
                fieldcount = 0;
                
                while (st.hasMoreTokens()) {
                    String s = st.nextToken();
                    rs[ fieldcount] = s;
                    fieldcount++;
                }

                if (rowcount > 0) {
                    variable[rowcount-1][0] = Double.valueOf( rs[0] ).doubleValue();
                    variable[rowcount-1][1] = Double.valueOf( rs[1] ).doubleValue();
                    variable[rowcount-1][2] = Double.valueOf( rs[2] ).doubleValue();
                    variable[rowcount-1][3] = Double.valueOf( rs[3] ).doubleValue();
                    if ( rowcount == 1 ) this.irregularStartDate = this.currentEpoch + (long)variable[rowcount-1][0];
                    System.out.println( rowcount + ": " + variable[rowcount-1][0] + ", " + variable[rowcount-1][1] + ", " + variable[rowcount-1][2] + ", " + variable[rowcount-1][3] + ", "  );
                }
                rowcount++;
                
                try {
                    dataRow = CSVFile.readLine(); // Read next line.
                } catch (IOException e) {
                    dataRow = new String("");
                }
            }

            // Close the file once all data has been read.
            try {
                CSVFile.close();
            } catch (IOException e) {
            }

        } catch (IOException e) {
            filefound = false;
            System.out.println( file + " CSV file not found");
        }

        if ( filefound ) {
            // End the printout with a blank line.
            System.out.println( "rowcount " + rowcount + " variable[rowcount-1][0] " + (long)variable[rowcount-1][0] );
            this.irregularStopDate = this.currentEpoch + (long)variable[rowcount-1][0];
            this.irregularStopDate = 66098000;  // correct value forced
            System.out.println( "rowcount " + rowcount + " variable[rowcount-1][0] " + variable[rowcount-1][0] );
            this.irregularRowCount = rowcount - 2;
            System.out.println( this.currentEpoch + " " + variable[0][0] + " " + variable[rowcount-1][0] );
            System.out.println("this.irregularStartDate " + this.irregularStartDate );
            System.out.println("this.irregularStopDate " + this.irregularStopDate );
            System.out.println("CSV File complete + " + rowcount + " rows " );
        }

        return( variable );
    }
    
    // get eccentricity, obliquity, perihelion longitude for cureent date
    // if within date range sets current date's eccentricity, obliquity, and perihelion longitude
    // else sets eccentricity, obliquity, and perihelion longitude to default values
    long eventManager( long date ) {
        double lat;
        boolean dateFound = false;
        GeologicalColumn gc;
        double SRtimestep =  1.0 / 365.25;
        double degreesToRadians = Math.PI / 180;
        this.nextDate = this.currentDate;
        
        // lookup date in eventTable
        int index = 0;
        if ( date >= irregularStartDate && date <= irregularStopDate ) {
            while ( index < eventTablerowcount ) {
                if ( eventTable[ index ][0] == date ) {
                    this.currentDate = eventTable[ index ][0];
                    this.eccentricity = eventTable[ index ][1];
                    this.obliquity = eventTable[ index ][2];
                    this.perihelionLongitude = eventTable[ index ][3]  ;
                    if ( this.currentDate < this.irregularStopDate ) {
                        this.nextDate = eventTable[ index+1 ][0];
                    } else {
                        this.nextDate = this.irregularStopDate + 1;
                    }            
                    dateFound = true;
                } else if ( eventTable[ index ][0] > date ) {
                    // get previous index values
                    if ( index > 0 ) index = index - 1;
                    this.currentDate = eventTable[ index ][0];
                    this.eccentricity = eventTable[ index ][1];
                    this.obliquity = eventTable[ index ][2];
                    this.perihelionLongitude = eventTable[ index ][3]  ;
                    if ( this.currentDate < this.irregularStopDate ) {
                        this.nextDate = eventTable[ index+1 ][0];
                    } else {
                        this.nextDate = this.irregularStopDate + 1;
                    }            
                    dateFound = true;
                }    
                if ( nextDate == date ) nextDate = eventTable[ index+2 ][0]; // fix for 2 dates the same
                if ( dateFound ) break;
                index++;
            }
        } 

        // 
        if ( date < this.irregularStartDate ) {
            this.nextDate = eventTable[0][0];       //start of table
        } else if ( date > this.irregularStopDate ) {
            this.nextDate = this.irregularStopDate + 1;  // off end of table
        }
        
        // get solar radiation data for all active latitudes
        for ( int nlat=0; nlat<18; nlat++ ) {
            lat = (nlat * 5) + 2.5;
            gc = exp.column[ nlat ];
            if ( dateFound ) {
                // use Milankovitch data
                if ( gc.columnActiveCalculation ) {                 
                    // use Milankovitch data
                    gc.sr = new SolarRadiation( this.exp, this.exp.column[ nlat ], 1367.0, lat, SRtimestep, 365.25, this.eccentricity, this.obliquity * degreesToRadians, this.perihelionLongitude * degreesToRadians ); 
                }    
            } else {    
                if ( gc.columnActiveCalculation ) {                 
                    // use default data
                    gc.sr = new SolarRadiation( this.exp, this.exp.column[ nlat ], 1367.0, lat, SRtimestep, 365.25, 0.016702, 0.409093, 1.796257 );
                }    
            }
        }

        if ( !dateFound )  {
            System.out.println( "Milankovitch date "  + date +  " not found" );
            this.nextDate = date;  // return current date to flag end of eventTable
        } else {
            System.out.println( "Milankovitch date " + date + " nearest date is " + (long)this.currentDate );            
        }
        
        return( (long)this.nextDate );
    }
}



class Precipitation {
    double meanGlobalPrecipitation = 0.814;     // annua; precipiation, metres source http://www.mdpi.com/2073-4433/9/4/138/htm
    double maxElevation = 3000;                 // max eelevation of rainfall, metres
    double slope;
    double latitude;
    double asteroidRadius;
    double degreesToRadians = Math.PI / 180.0;;

    public Precipitation() {
    }

    public Precipitation( double lat, double maxelev ) {
        latitude = lat;
        maxElevation = maxelev;
        slope = -meanGlobalPrecipitation / maxElevation;

    }

    double latitudeAdjustedPrecipitation( double elevation ) {
        double pmean = annualMeanPrecipitation( elevation );
        pmean = pmean * evaporationFactor( latitude );
        return( pmean );
    }

    // linear decrease in precipiation with elevation
    // y = mx + c where y = precipitation, x = elevation, c = meanGlobalPrecipitation
    double annualMeanPrecipitation( double elevation ) {
        double ap = slope * elevation + meanGlobalPrecipitation;
        if ( ap < 0 ) ap = 0;
        return( ap );
    }

    // Assuming all higher latitudes are glaciated, and all lower latitudes are unglaciated,
    // water evaporation factor is unglaciated area/total araea
    // area of arc of sphere = 2.pi.R.h
    double evaporationFactor( double latitude) {
        double lradians = latitude * degreesToRadians;
        double e = Math.sin( lradians );
        return( e );
    }

}


class MaterialPhase {


    // first find which side of vapour line the t-p point is.
    // if vapour, return state 3
    // else find which side of solid-liquid line t-p point is,
    // if liquid, return state 2
    // else state 3
    // inputs layer temperature tK, layer pressure pPa
    // returns state 0 = solid, 1-liquid, 2=vapour
    // tested working 6 june 2018
    public static double phase( double tK, double pPa ) {
        double[][] water = {
            // 0        1         2
            // n,       T,        p,
            {   1,        0,         0 },      // minimum line
            {   1,      210,         1 },      // vapour line
            {   1,      230,        10 },      // vapour line
            {   1,      250,       100 },      // vapour line
            {   1,   273.15,   611.567 },      // triple point
            {   1,      300,      3500 },      // vapour line
            {   1,      320,      10e3 },      // vapour line
            {   1,      350,      35e3 },      // vapour line
            {   1,   373.35, 101.325e3 },      // boiling point at 1 atmosphere pressure
            {   1,      450,    0.95e6 },      // vapour line
            {   1,      550,    6.50e6 },      // vapour line
            {   1,      647,  22.064e6 },      // critical point
            {   1,      650,  10.00e12 },      // maximum  line
            {   2,   273.16,         0 },      // minimum line
            {   2,   273.16,   611.567 },      // triple point
            {   2,   273.15, 101.325e3 },      // freezing point at 1 atmosphere pressure
            {   2,  251.165,   209.9e6 },      // solid-liquid line
            {   2,  256.164,   350.1e6 },      // solid-liquid line
            {   2,  272.990,   632.4e6 },      // solid-liquid line
            {   2,  355.000,   2.219e9 },      // solid-liquid line
            {   2,  650.000,    10.5e9 },      // solid-liquid line
            {   2,  650.000,  10.00e12 },      // maximum line
            {  -1, }
        };

        double xmin = 1e20;
        double xmax = -1e20;
        double ymin = 1e20;
        double ymax = -1e20;
        double state = 0;
        double nphaseline = 1;       // vapour line is nphaseline 1
        int n = 0;
        Point3d p = new Point3d( tK, pPa, 0 );
        Point3d p1 = new Point3d( water[n][1], water[n][2], 0);
        Point3d p2 = new Point3d( 0, 0, 0);
        Point3d v = new Point3d( 0, 0, 0 );

        // findphase diagram max and min values
        while ( water[n][0] > 0 ) {
            if ( water[n][1] < xmin ) xmin = water[n][1];
            if ( water[n][1] > xmax ) xmax = water[n][1];
            if ( water[n][2] < ymin ) ymin = water[n][2];
            if ( water[n][2] > ymax ) ymax = water[n][2];
            n++;
        }

        n = 1;
        while ( water[n][0] == 1 || water[n][0] == 2 ) {
            p2.x = water[n][1];
            p2.y = water[n][2];
            p2.z = 0;
//          System.out.println( "p1 " + p1.x + ", " + p1.y + ", " + p1.z  );
//          System.out.println( "p2 " + p2.x + ", " + p2.y + ", " + p2.z  );
            if ( water[n][0] == water[n-1][0] ) {
                v = findDistanceFromLine( p1, p2, p );
//              System.out.println( "v " + v.x + ", " + v.y + ", " + v.z + " : " + v.vx );

                if ( inside( v.vx, v.x ) ) {
                    if ( v.y > 0 ) {
                        if ( nphaseline == 1 ) {
                            System.out.println( "       solid/liquid      " );
                            state = 10;
                        } else {
                            System.out.println( "       solid      " );
                            state = 0;
                            break;
                        }
                        nphaseline++;
                    } else {
                        if ( nphaseline == 1 ) {
                            System.out.println( "       vapour      " );
                            state = 2;
                            break;
                        } else {
                            System.out.println( "       liquid      " );
                            state = 1;
                            break;
                        }
                    }
                }
            } else {
                System.out.println( "skip " );
            }

            p1.x = p2.x;
            p1.y = p2.y;
            p1.z = p2.z;
            n++;
//          System.out.println( "line " + n );
        }

        System.out.println( "       state = " + state );

        dataValid( tK, xmax, xmin );
        dataValid( pPa, ymax, ymin );

        return( state );
    }

        // find distance from line b0 - b1 to b2
    public static Point3d findDistanceFromLine( Point3d b0, Point3d b1, Point3d b2 ) {
        double angle1, angle2, angle3, distance = 0;
        Point3d v0 = new Point3d();
        Point3d v1 = new Point3d();

        // transform 3 tracked locations into 2 vectors on x-y plane
        // First transform around z axis
        v0.x = b1.x - b0.x;
        v0.y = b1.y - b0.y;
        v0.z = b1.z - b0.z;
        angle1 = Math.atan2(v0.y, v0.x);
        v0 = transformAroundZaxis( v0, -angle1 );
        v1.x = b2.x - b0.x;
        v1.y = b2.y - b0.y;
        v1.z = b2.z - b0.z;
        v1 = transformAroundZaxis( v1, -angle1 );

        // Then transform around y axis
        angle2 = Math.atan2(v0.z, v0.x);
        v0 = transformAroundYaxis( v0, -angle2 );
        v1 = transformAroundYaxis( v1, -angle2 );

        // And finally transform around x axis
//      angle3 = Math.atan2(v1.z, v1.y);
//      v1 = transformAroundXaxis( v1, -angle3 );

//      distance = v1.y;
//      System.out.println( "       " + v0.x + " + " + v0.y + " + " + v0.z );

        // v0 lies along x axis, and so v0.x is the length of the line element vector
        // and if v1 is > 0 oe < v0.x, it is adjacent to v0, and either above it (v1.y > 0)
        // or below it (v1.y < 0)
        v1.vx = v0.x;

        return( v1 );
    }

    // return true if x1 <= x0 and x1 >= 0
    public static boolean inside( double x0, double x1 ) {
        boolean inside = false;
        if ( x1 >= 0 ) {
            if ( x1 <= x0 ) {
                inside = true;
            }
        }
        return( inside );
    }

    public static boolean dataValid( double v, double vmax, double vmin ) {
        boolean valid = false;
        if ( v <= vmax && v >= vmin ) {
            valid = true;
        } else {
            System.out.println( "Value " + v + " is invalid, outside range " + vmin + "--" + vmax);
        }
        return( valid );
    }

     // transform a point around the x-axis through xangle (june 2013)
    public static Point3d transformAroundXaxis( Point3d m,  double xangle ) {
        Point3d mt = new Point3d();
        mt.x = m.x;
        mt.y = m.y * Math.cos(xangle) - m.z * Math.sin(xangle);
        mt.z = m.z * Math.cos(xangle) + m.y * Math.sin(xangle);
        mt.vx = m.vx;
        mt.vy = m.vy * Math.cos(xangle) - m.vz * Math.sin(xangle);
        mt.vz = m.vz * Math.cos(xangle) + m.vy * Math.sin(xangle);
        return ( mt );
    }

    // transform a point around the y-axis through xangle (june 2013)
    public static Point3d transformAroundYaxis( Point3d m, double xangle ) {
        Point3d mt = new Point3d();
        mt.y = m.y;
        mt.x = m.x * Math.cos(xangle) - m.z * Math.sin(xangle);
        mt.z = m.z * Math.cos(xangle) + m.x * Math.sin(xangle);
        mt.vy = m.vy;
        mt.vx = m.vx * Math.cos(xangle) - m.vz * Math.sin(xangle);
        mt.vz = m.vz * Math.cos(xangle) + m.vx * Math.sin(xangle);
        return ( mt );
    }

    // transform a point around the z-axis through xangle (june 2013)
    public static Point3d transformAroundZaxis( Point3d m, double xangle ) {
        Point3d mt = new Point3d();
        mt.z = m.z;
        mt.y = m.y * Math.cos(xangle) + m.x * Math.sin(xangle);
        mt.x = m.x * Math.cos(xangle) - m.y * Math.sin(xangle);
        mt.vz = m.vz;
        mt.vy = m.vy * Math.cos(xangle) + m.vx * Math.sin(xangle);
        mt.vx = m.vx * Math.cos(xangle) - m.vy * Math.sin(xangle);
        return ( mt );
    }

}

class Point3d {
    double x;
    double y;
    double z;
    double vx;
    double vy;
    double vz;
    double ax;
    double ay;
    double az;

    public Point3d() {
    }

    public Point3d( double x, double y, double z ) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

}

// class Average calculates averages of values
class Average{
    double runningTotal;
    int nValues;
    int averageOver;   
    double currentAverage;
    
    public Average(){        
    }
    
    public Average( int avOver ) {
        this.runningTotal = 0;
        this.nValues = 0;
        this.averageOver = avOver;
    }

    // returns true if a new average value is available
    boolean addNewValue( double value ) {
        boolean newAverageAvailable = false;
        this.runningTotal += value;
        this.nValues++;
        if ( nValues == averageOver ) {
            currentAverage = this.runningTotal / (double)this.averageOver;
            this.runningTotal = 0;
            this.nValues = 0;
            newAverageAvailable = true;
        }
        return( newAverageAvailable );
    }

}


class PseudoMap{
    // default world map is of present day Earth with all continents in N hemisphere
    // world map of fractions of eqch latitude that are ocean. land, and snow
    // and fraction of sphere surface area covered by each latitude
    double pseudoMap[][] = {
        //  ocean land  snow  fAreaSphere              Lat
        {   0.1,  0.4,  0.8,                  },   //      albedos
        {   0.4,  0.6,    0,    0.043577872,  },   //  2.5 40% ocean, 60% land
        {   0.4,  0.6,    0,    0.043246217,  },   //  7.5    
        {   0.4,  0.6,    0,    0.042585433,  },   // 12.5
        {   0.4,  0.6,    0,    0.041600548,  },   // 17.5
        {   0.4,  0.6,    0,    0.040299058,  },   // 22.5
        {   0.4,  0.6,    0,    0.03869087,   },   // 27.5
        {   0.4,  0.6,    0,    0.036788218,  },   // 32.5
        {   0.4,  0.6,    0,    0.034605585,  },   // 37.5
        {   0.4,  0.6,    0,    0.032159586,  },   // 42.5
        {   0.4,  0.6,    0,    0.02946883,   },   // 47.5
        {   0.4,  0.6,    0,    0.0265538,    },   // 52.5
        {   0.4,  0.6,    0,    0.02343668,   },   // 57.5    
        {   0.4,  0.6,    0,    0.020141192,  },   // 62.5
        {   0.4,  0.6,    0,    0.016692417,  },   // 67.5
        {   0.4,  0.6,    0,    0.013116603,  },   // 72.5
        {   0.4,  0.6,    0,    0.009440963,  },   // 77.5
        {   0.4,  0.6,    0,    0.0056934725, },   // 82.5 
        {     0,    0,  1.0,    0.0019026509, },   // 87.5 snow at north pole
        {   1.0,    0,    0,    0.043577872,  },   // -2.5 100% ocean
        {   1.0,    0,    0,    0.043246217,  },   // -7.5    
        {   1.0,    0,    0,    0.042585433,  },   //-12.5
        {   1.0,    0,    0,    0.041600548,  },   //-17.5
        {   1.0,    0,    0,    0.040299058,  },   //-22.5
        {   1.0,    0,    0,    0.03869087,   },   //-27.5
        {   1.0,    0,    0,    0.036788218,  },   //-32.5
        {   1.0,    0,    0,    0.034605585,  },   //-37.5
        {   1.0,    0,    0,    0.032159586,  },   //-42.5
        {   1.0,    0,    0,    0.02946883,   },   //-47.5
        {   1.0,    0,    0,    0.0265538,    },   //-52.5
        {   1.0,    0,    0,    0.02343668,   },   //-57.5    
        {   1.0,    0,    0,    0.020141192,  },   //-62.5
        {   1.0,    0,    0,    0.016692417,  },   //-67.5
        {   1.0,    0,    0,    0.013116603,  },   //-72.5
        {   1.0,    0,    0,    0.009440963,  },   //-77.5
        {   1.0,    0,    0,    0.0056934725, },   //-82.5    
        {     0,    0,  1.0,    0.0019026509, }    //-87.5  snow at south pole
    };
    double oceanAlbedo;
    double landAlbedo;
    double snowAlbedo;
    
    public PseudoMap() {
        oceanAlbedo = pseudoMap[0][0];
        landAlbedo = pseudoMap[0][1];
        snowAlbedo = pseudoMap[0][2];
        System.out.println( "pseudoMap albedos " + oceanAlbedo + ", " + landAlbedo + ", " + snowAlbedo );
    }
    
    // aLat is surface area of latitude circle, a Sphere is surface area of sphere
    double setLatitudeAreaFraction( int i, double aLat, double aSphere ) {
        pseudoMap[i+1][3] = aLat / aSphere;
        return( pseudoMap[i+1][3] );
    }
    
    // glaciate planet from north pole down to  limitLatitude
    void glaciatePseudoMap( double nLimitLatitude ) {
        for ( int y=0; y<18; y++ ) {
            if ( y > nLimitLatitude  ) {
                pseudoMap[y+1][0] = 0;    // no ocean
                pseudoMap[y+1][1] = 0;    // no land
                pseudoMap[y+1][2] = 1;    // all snow
            }
        }
    }
    
    // glaciate planet from north pole down to  limitLatitude
    void glaciateLandPseudoMap( int nLimitLatitude ) {
        for ( int y=0; y<18; y++ ) {
            if ( y > nLimitLatitude  ) {
                pseudoMap[y+1][1] = 0;    // no land
                pseudoMap[y+1][2] = 1.0 - pseudoMap[y+1][0];    // all snow
            }
        }
    }
    
    void glaciateLand( int nLat ) {
        pseudoMap[nLat+1][1] = 0;    // no land
        pseudoMap[nLat+1][2] = 1.0 - pseudoMap[nLat+1][0];    // all snow        
    }
    
    void deglaciateLand( int nLat ) {
        pseudoMap[nLat+1][1] = 1.0 - pseudoMap[nLat+1][0];    // all land
        pseudoMap[nLat+1][2] = 0.0;    // no snow        
    }    
    
    void printPseudoMap() {
        for ( int y=0; y<18; y++ ) {
          System.out.println(  "nLatitude " + y + ": " + pseudoMap[y+1][0] + ", " + pseudoMap[y+1][1] + ", " + pseudoMap[y+1][2] + ", " + pseudoMap[y+1][3] + ", "  );     
        }        
    } 
    
    // simple 3-colour rectangular world map of latitude lines
    // paints from latitude +90 to -90
    // (completely untested)
    public void paint(Graphics g ) {
        int x0 = 0;
        int x1, x2, x3 = 72;
        int latIndex;
        g.setPaintMode();
        for ( int y=0; y<36; y++ ) {
            latIndex = y;
            if ( y < 18 ) latIndex = 17 - y;
            x1 = (int)( (double)x3 * pseudoMap[latIndex][1] );      // land
            x2 = (int)( (double)x3 * pseudoMap[latIndex][2] );      // snow
            g.setColor( Color.orange );                             // land
            g.drawLine(x0, y, x1, y);
            g.setColor( Color.white );                              // snow
            g.drawLine(x1, y, x2, y);
            g.setColor( Color.cyan );                               // ocean
            g.drawLine(x2, y, x3, y);
        }    
    }
}

// daily values, mostly of air temperatures
class DailyValues {    
    double dailyValue[][] = new double[2][366];
    double meanDailyValue;
    double vsum = 0;
    double albedo;
    
    public DailyValues() {
        dailyValue[1][0] = 0;            
    }
    
    void setDailyValue( double v, int i ) {
        dailyValue[0][i] = v;
        vsum+= v;
        dailyValue[1][i] = vsum;        
    }

    double getDailyValue( int i ) {
        return( dailyValue[0][i] );
    }

    // set daily value and calculate mean daily value
    void setMeanDailyValue( double v, int i ) {    
        dailyValue[0][i] = v;
        // assumes this method is repeatedly being called with i from 0 to 364
        if ( i == 0 ) {
            meanDailyValue = v;
        } else if ( i == 364 ) {
            meanDailyValue += v;
            meanDailyValue = meanDailyValue / 365.0;
        } else {
            meanDailyValue += v;
        }
    }
    
    // calculate mean daily value
    double getMeanDailyValue() { 
        meanDailyValue = 0;
        for ( int i=1; i<=365; i++ ) {
            meanDailyValue += dailyValue[0][i];
        }
        meanDailyValue = meanDailyValue / 365.0;
        return( meanDailyValue );
    }
    
    // get mean value between startDay and endDay
    double getMeanDailyValue( int startDay, int endDay ) {
  //    System.out.println( "startDay" + startDay + " endDay " + endDay );
        double period = (double)( endDay - startDay );
        double vMean = ( this.dailyValue[1][endDay] - this.dailyValue[1][startDay] ) / period;
        return( vMean );
    }    
    
    void setDailySouthernHemisphereValue( double v, int i ) {
        // southern hemisphere is offset 365/2 days
        int is = i + 182;
        if ( is >= 365 ) is = is - 365;
        dailyValue[0][is] = v;        
    }

    void setMeanDailySouthernHemisphereValue( double v, int i ) {
        // southern hemisphere is offset 365/2 days
        int is = i + 182;
        if ( is >= 365 ) is = is - 365;
        dailyValue[0][is] = v;        
        
        // assumes this method is repeatedly being called with i from 1 to 365
        if ( is == 1 ) {
            meanDailyValue = v;
        } else if ( is == 365 ) {
            meanDailyValue += v;
            meanDailyValue = meanDailyValue / 365.0;
        } else {
            meanDailyValue += v;
        }
    }
    
    void printDailyValues( String name) {
        System.out.println( name );
        for ( int i=1; i<=365; i++ ) {
            System.out.print( (float)dailyValue[0][i] + " " );
            if ( i % 5 == 0 ) System.out.println();
        }
        System.out.println();    
    }
    
}