SoilCalc.java [src/java/d/util] Revision: default  Date:
/*
 * $Id$
 *
 * This file is part of the Cloud Services Integration Platform (CSIP),
 * a Model-as-a-Service framework, API, and application suite.
 *
 * 2012-2017, OMSLab, Colorado State University.
 *
 * OMSLab licenses this file to you under the MIT license.
 * See the LICENSE file in the project root for more information.
 */
package d.util;

import csip.Config;
import csip.SessionLogger;
import d.soils.wwe02_wepssoilinput.StratifiedTextures;
import java.io.File;
import java.io.IOException;
import soils.Component;
import soils.Fragments;
import soils.Horizon;
import soils.MapUnit;
import soils.TextureGroup;
import soils.utils.EvalResult;

/**
 *
 * @author Brad
 * Only use this class if we are suppose to estimate the IFC values
 */
public class SoilCalc {

    private static String FORCE_CALC = "force_calculations";

    
    public static void convertUnits(MapUnit mapUnit,Component comp){
        
        mapUnit.brockdepmin(mapUnit.brockdepmin() *10);
        comp.calculated_resdeptmin_r(comp.calculated_resdeptmin_r() *10);
        comp.slope_r(comp.slope_r()/100);
        
        for(Horizon h : comp.horizons.values()){
            h.hzdepb_r(Utils.metricConversion(h.hzdepb_r(), Utils.Metric.CM, Utils.Metric.MM));
            h.hzdept_r(Utils.metricConversion(h.hzdept_r(), Utils.Metric.CM, Utils.Metric.MM));
            h.sandtotal_r(Utils.pctToFraction(h.sandtotal_r()));
            h.claytotal_r(Utils.pctToFraction(h.claytotal_r()));
            h.silttotal_r(Utils.pctToFraction(h.silttotal_r()));
            h.sandvc_r(Utils.pctToFraction(h.sandvc_r()));
            h.sandco_r(Utils.pctToFraction(h.sandco_r()));
            h.sandmed_r(Utils.pctToFraction(h.sandmed_r()));
            h.sandfine_r(Utils.pctToFraction(h.sandfine_r()));
            h.sandvf_r(Utils.pctToFraction(h.sandvf_r()));
            h.om_r(Utils.pctToFraction(h.om_r()));
            h.caco3_r(Utils.pctToFraction(h.caco3_r()));
        }      
    }
    
    public static double getSurfaceAlbedo(double surfaceAlbedo,double organicMaterial) {
        if (surfaceAlbedo == 0.0 || EvalResult.testDefaultDouble(surfaceAlbedo) || surfaceAlbedo < 0 || surfaceAlbedo > 1) {
            surfaceAlbedo = 0.6 / (Math.exp(0.4 * organicMaterial * 100.0));
        }
        
        return surfaceAlbedo;
    }

    /**
     * This variable in the WEPS code is only set by the GUI but I created this in case we hear otherwise
     * @return 0
     */
    public static double getSurfaceFragmentCover() {
        return 0;
    }
    
    public static double getSoilpH(Horizon horizon){
        double soilPh;
        
        soilPh = !EvalResult.testDefaultDouble(horizon.ph1to1h2o_r()) ? horizon.ph1to1h2o_r() : horizon.ph01mcacl2_r();
        if (soilPh == 0.0 || EvalResult.testDefaultDouble(soilPh))
            soilPh = 7;
        return soilPh;
    }

    static double getCationExchangeCapacity(Horizon horizon ) {
        double cec7;
        
        cec7 = !EvalResult.testDefaultDouble(horizon.cec7_r()) ? horizon.cec7_r() : horizon.ecec_r();
        if(cec7 == 0 || EvalResult.testDefaultDouble(cec7)){
            cec7 = horizon.claytotal_r() * 100 * 0.5 + horizon.om_r() * 100 * 2.0;
            
            cec7 = Utils.checkBoundaries(cec7, 0.0, 400);
        }
        horizon.cec7_r(cec7);
        return cec7;
    }

    static double getLinearExtensibility(Horizon horizon) {
        double lep;
        double bsd;
        
        lep = horizon.lep_r();
        
        if(lep == 0 || EvalResult.testDefaultDouble(lep)){
            bsd = SoilUtil.estimateSettledBulkDensity(horizon.claytotal_r(), horizon.sandtotal_r(), horizon.om_r());
            
            if(horizon.wthirdbar_r() > bsd)
                bsd = horizon.wthirdbar_r();
            lep = SoilUtil.estimateLinearExtensibility(bsd, horizon.wthirdbar_r());
            
            lep = Utils.checkBoundaries(lep,0,30);
        }
                
        horizon.lep_r(lep);
        return lep;
    }

    static double getAggregateMeanDiameter(Horizon horizon) {
        double mean;
        
        mean = Math.exp(1.343 - 2.235 * horizon.sandtotal_r() - // aggregate geometric mean diameter
                    1.226 * horizon.silttotal_r() - 0.0238 * horizon.sandtotal_r() / horizon.claytotal_r()
                    + 33.6 * horizon.om_r() + 6.85
                    * horizon.caco3_r()) * (1 + 0.006 * horizon.hzdepb_r());
        
        mean = Utils.checkBoundaries(mean, 0.03, 30.0);     
        
        return mean;
    }
    
    static double getAggregateStdDeviation(Horizon horizon, double mean){
        double deviation;
        
        if(EvalResult.testDefaultDouble(mean))
            mean = getAggregateMeanDiameter(horizon);
        
        deviation = 1 / (0.012448 + 0.002463 * mean
                + (0.093467 / (Math.pow(mean, 0.5))));
        
        deviation = Utils.checkBoundaries(deviation, 1.0, 20.0);        
        
        return deviation;
    }

    static double getAggregateStdDeviation(Horizon horizon) {  
        return getAggregateStdDeviation(horizon,Double.NaN);
    }

    static double getMaxAggregateSize(Horizon horizon) {
        double maxsize;
        double mean;
        double dev;
        
        mean = getAggregateMeanDiameter(horizon);
        dev = getAggregateStdDeviation(horizon,mean);
        
        maxsize = Math.pow(dev,(1.52 * (Math.pow(mean, -0.449)))) * mean;
        
        maxsize = Utils.checkBoundaries(maxsize, 1.0, 1000.0);
        
        return maxsize;
    }

    /**
     * The WEPS code set this value to 0.01.
     * @return 
     */
    static double getMinAggregateSize() {
        return 0.01;
    }

    /**
     * The WEPS code sets this to 1.8
     * It then checks to make sure it is between 0.6 and 2.5?
     * @return 
     */
    static double getAggregateDensity(Horizon horizon) {
        return 1.8;
    }

    static double getAggregateStability(Horizon horizon) {
        double stability;
        
        stability = (horizon.claytotal_r() > 0.5) ? 2.73 : 0.83 + 15.7 * horizon.claytotal_r()
                    - 23.8 * Math.pow(horizon.claytotal_r(), 2);	
        
        stability = Utils.checkBoundaries(stability, 0.1, 7.0);
        
        return stability;
    }

    /**
     * The WEPS code sets this to 0.01
     * @return 
     */
    static double getCrustThickness() {
        return 0.01;
    }

    static double getCrustDensity(Horizon horizon) {
        return getAggregateDensity(horizon);
    }

    static double getCrustStability(Horizon horizon) {
        return getAggregateStability(horizon);
    }

    /**
     * Only set by WEPS GUI
     * @return 
     */
    static double getCrustFraction() {
        return 0.0;
    }

    /**
     * Only set by WEPS GUI
     * @return 
     */
    static double getCrustLooseMaterialMass() {
        return 0.0;
    }

    /**
     * Only set by WEPS GUI
     * @return 
     */
    static double getCrustLooseMaterialFraction() {
        return 0.0;
    }

    /**
     * WEPS code sets this to 4
     * @return 
     */
    static double getRandomRoughness() {
        return 4;
    }

    /**
     * Only set by WEPS GUI
     * @return 
     */
    static double getRoughnessOrientation() {
        return 0.0;
    }

    /**
     * Only set by WEPS GUI
     * @return 
     */
    static double getRoughnessHeight() {
        return 0.0;
    }

    /**
     * Only set by WEPS GUI
     * @return 
     */
    static double getRoughnessSpacing() {
        return 10.0;
    }

    /**
     * Only set by WEPS GUI
     * @return 
     */
    static double getRoughnessWidth(){
        return 10.0;
    }

    static double getInitialSWC(Horizon horizon) {
        return (getFieldCapacitySWC(horizon) + getWiltingPointSWC(horizon)) / 2;
    }

    static double getSaturatedSWC(Horizon horizon) {
        double saturated;
        double sax_x = 0.332, sax_y = -7.251e-4, sax_z = 0.1276;			// coef used to calc THETAS
        
        saturated = sax_x + sax_y * horizon.sandtotal_r() * 100. + // saturated soil water content
                    sax_z * (Math.log(horizon.claytotal_r() * 100.) / Math.log(10.));
        
        saturated = Utils.checkBoundaries(saturated, 0.208,0.550);
        
        return saturated;
    }
    
    static double getSoilCB(Horizon horizon) {
        double cb;
        double sax_e = -3.140, sax_f = -2.22e-3, sax_g = -3.484e-5;			// coef used to calc CB
        
        cb = -(sax_e + // cb(k) == ifc3_layer[][23]
            (sax_f * Math.pow(horizon.claytotal_r() * 100., 2.0))
            + (sax_g * Math.pow(horizon.sandtotal_r() * 100., 2.0) * horizon.claytotal_r() * 100.));
        
        cb = Utils.checkBoundaries(cb, 0.917, 27.027);
        
        return cb;
    }

    static double getFieldCapacitySWC(Horizon horizon) {
        double capacity;
        boolean force = Config.getBoolean(FORCE_CALC, false);

        
        capacity = Utils.pctToFraction(horizon.wthirdbar_r());
        
        if (force || capacity == 0.0 || EvalResult.testDefaultDouble(capacity)) {
            capacity = Math.pow((33.0 / get_sax_a(horizon)), (1 / -getSoilCB(horizon)));
            
            capacity = Utils.checkBoundaries(capacity, 0.012, .0335);
        }
        
        return capacity;
    }

    static double getWiltingPointSWC(Horizon horizon) {
        double wilting;
        boolean force = Config.getBoolean(FORCE_CALC, false);
        
        wilting = Utils.pctToFraction(horizon.wfifteenbar_r());
        
        if (force || wilting == 0.0 || EvalResult.testDefaultDouble(wilting)) {
            wilting = Math.pow((1500.0 / get_sax_a(horizon)), (1 / -getSoilCB(horizon)));
            
            wilting = Utils.checkBoundaries(wilting, 0.005, 0.242);
        }
        
        return wilting;
    }

    static double getAirEntryPotential(Horizon horizon) {
        double aep;
        
        aep = -get_sax_a(horizon) * Math.pow(getSaturatedSWC(horizon), -getSoilCB(horizon)); 
        
        aep = Utils.checkBoundaries(aep, -17.91, 0.0);
        
        return aep;
    }

    static double getSaturatedHydraulicConductivity(Horizon horizon) {
        double shc;
        boolean force = Config.getBoolean(FORCE_CALC, false);
        double sk;
        
        shc = horizon.ksat_r() / 1000000; // cvt from ppm to p
        
        if (force || shc == 0.0 || EvalResult.testDefaultDouble(shc)) {
            sk = 9.6 - 0.81 * Math.log(horizon.silttotal_r() * 100.) / Math.log(10.)
                        - 1.09 * Math.log(horizon.claytotal_r() * 100.) / Math.log(10.) - 4.64 * horizon.dbthirdbar_r();
            
            shc = Math.pow(10, sk) * 0.24 * (1.0 / 86400);
            
            shc = Utils.checkBoundaries(shc, 0.0, 0.001);
        }
        
        return shc;
    }
    
    private static double get_sax_a(Horizon horizon){
        double sax_a = 100 * Math.exp(-4.396 - // calc A factor for pote
            0.0715 * (horizon.claytotal_r() * 100.) - // using two steps because that is how
            (4.880e-4) * Math.pow(horizon.sandtotal_r() * 100., 2) - // saxton95 does it
            (4.285e-5) * Math.pow(horizon.sandtotal_r() * 100., 2) * (horizon.claytotal_r() * 100.));
        
        return sax_a;
    }

    public static void averageStratifiedLayers(Component comp,StratifiedTextures st) throws IOException {
        boolean first = true;
        double sand = 0;
        double clay = 0;
        double vsf = 0;
        int count = 0;
        String[] textures;
        double silt;

        Layers:
        for (Horizon h : comp.horizons.values()) {
            for(TextureGroup t : h.textureGroups.values()){
                if(t.stratextsflag() && t.rvindicator() && t.texture().trim().toUpperCase().startsWith("SR")){ // stratified key
                    textures = t.texture().split(" ", -1);
                    
                first = true;
                sand = 0;
                clay = 0;
                vsf = 0;
                count = 0;
                
                    for (String subTexture : textures) {
                        subTexture = subTexture.trim();
                        if (first) {
                            //skip the sr marker
                            first = false;
                            continue;
                        }
                        if (st.isTextureValid(subTexture)) {
                            count++;
                            sand += st.getSand(subTexture);
                            sand /= count;

                            clay += st.getClay(subTexture);
                            clay /= count;

                            vsf += st.getVeryFineSand(subTexture);
                            vsf /= count;
                        } else {
                            //can't fix up this layer
                            continue Layers;
                        }
                    }
                    if (sand + clay > 1) {
                        //TODO: log error
                        continue Layers;
                    }
                    silt = 1 - sand - clay;
                    //if(EvalResult.testDefaultDouble(h.sandtotal_r()))
                        h.sandtotal_r(sand * 100);
                    //if(EvalResult.testDefaultDouble(h.silttotal_r()))
                        h.silttotal_r(silt * 100);
                    //if(EvalResult.testDefaultDouble(h.claytotal_r()))
                        h.claytotal_r(clay * 100);
                    //if(EvalResult.testDefaultDouble(h.sandvf_r()))
                        h.sandvf_r(vsf * 100);
                }
            }       
        }
    }
    
    public static String getSurfaceTexture(Component comp){
        
        return getTexture(Utils.getSurfaceHorizon(comp));
    }
    
    public static String getTexture(Horizon horizon){
        for(TextureGroup t : horizon.textureGroups.values())
            if(t.rvindicator())
                return t.texture();
        return "";
    }
    
    public static double getRockFrags(Horizon horizon){
        double rockFrag = 0;
        
        for(Fragments f : horizon.fragments.values()){
            rockFrag += Utils.averageMissingValue(f.fragvol_h(), f.fragvol_l(), f.fragvol_r());
        }
        
        return rockFrag / 100;
    }
    
    public static double testNumericSoilElement(double high,double low,double r, double defaultValue) {

        if(EvalResult.testDefaultDouble(r))
            r = defaultValue;
        
        if(r < low)
            r = defaultValue;
        if(r > high)
            r= defaultValue;
        
        return r;
    }
    
    public static int testNumericSoilElement(int high,int low,int r, int defaultValue) {

        if(EvalResult.testDefaultInteger(r))
            r = defaultValue;
        
        if(r < low)
            r = defaultValue;
        if(r > high)
            r = defaultValue;
        
        return r;
    }
}