All Downloads are FREE. Search and download functionalities are using the official Maven repository.

geotrellis.vector.interpolation.NonLinearSemivariogram.scala Maven / Gradle / Ivy

Go to download

GeoTrellis is an open source geographic data processing engine for high performance applications.

There is a newer version: 0.10.3
Show newest version
/*
 * Copyright (c) 2015 Azavea.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package geotrellis.vector.interpolation

import geotrellis.vector._

object NonLinearSemivariogram {

  /** Explicit [[Gaussian]] Semivariogram model */
  private def explicitGaussian(r: Double, s: Double, a: Double): Double => Double = {
    h: Double => {
      if (h == 0) 0
      else
        a + (s - a) * (1 - math.exp(- math.pow(h, 2) / math.pow(r, 2)))
    }
    /*                      | 0                                 , h = 0
     *  gamma(h; r, s, a) = |
     *                      | a + (s - a) {1 - e^(-h^2 / r^2)}  , h > 0
     */
  }

  private def explicitGaussianNugget(r: Double, s: Double): Double => Double =
    explicitGaussian(r, s, 0)

  /** [[Gaussian]] Semivariogram model's Jacobian for use in fitting an empirical semivariogram
    * to the explicit models
    */
  private def jacobianGaussian(variables: Array[Double]): Double => Array[Double] = {
    x: Double => {
      if (x == 0)
        Array.fill[Double](3)(0)
      else {
        val jacobianRet: Array[Double] = Array.ofDim[Double](3)
        jacobianRet(0) =
          (variables(2) - variables(1)) * (2 * math.pow(x, 2) / math.pow(variables(0), 3)) * math.exp(-1 * math.pow(x, 2) / math.pow(variables(0), 2))
        jacobianRet(1) = 1 - math.exp(-1 * math.pow(x, 2) / math.pow(variables(0), 2))
        jacobianRet(2) = 1 - jacobianRet(1)
        jacobianRet
      }
    }
  }

  private def jacobianGaussianNugget(variables: Array[Double]): Double => Array[Double] = {
    x: Double => {
      if (x == 0)
        Array.fill[Double](2)(0)
      else {
        val jacobianRet: Array[Double] = Array.ofDim[Double](2)
        jacobianRet(0) = variables(1) * (2 * math.pow(x, 2) / math.pow(variables(0), 3)) * math.exp(-1 * math.pow(x, 2) / math.pow(variables(0), 2))
        jacobianRet(1) = 1 - math.exp(-1 * math.pow(x, 2) / math.pow(variables(0), 2))
        jacobianRet
      }
    }
  }

  /** Explicit [[Circular]] Semivariogram model */
  private def explicitCircular(r: Double, s: Double, a: Double): Double => Double = {
    h: Double => {
      if (h == 0) 0
      else if (h > r)
        s
      else
        a + (s - a) * (1 - (2 / math.Pi) * math.acos(h/r) + ((2 * h) / (math.Pi * r)) * math.sqrt(1 - math.pow(h, 2) / math.pow(r, 2)))
    }
    /*                      | 0                                                                        , h = 0
     *                      |
     *                      |               |                                              _________ |
     *                      |               |      2                | h |      2h         /    h^2   |
     *  gamme(h; r, s, a) = | a + (s - a) * |1 - ----- * cos_inverse|---| + -------- *   /1 - -----  | , 0 < h <= r
     *                      |               |      pi               | r |    pi * r    \/      r^2   |
     *                      |               |                                                        |
     *                      |
     *                      | s                                                                        , h > r
     */
  }

  private def explicitCircularNugget(r: Double, s: Double): Double => Double =
    explicitCircular(r, s, 0)

  /** [[Circular]] Semivariogram model's Jacobian for use in fitting an empirical semivariogram
    * to the explicit models
    */
  private def jacobianCircular(variables: Array[Double]): Double => Array[Double] = {
    x: Double => {
      if (x == 0)
        Array.fill[Double](3)(0)
      else {
        val jacobianRet: Array[Double] = Array.ofDim[Double](3)
        jacobianRet(0) = -4 * x * (variables(1) - variables(2)) * math.sqrt(math.pow(variables(0), 2) - math.pow(x, 2)) / (math.Pi * math.pow(variables(0), 3))
        jacobianRet(1) = 1 - (2 / math.Pi) * math.acos(x / variables(0)) + ((2 * x) / (math.Pi * variables(0))) * math.sqrt(1 - math.pow(x / variables(0), 2))
        jacobianRet(2) = 1 - jacobianRet(1)
        jacobianRet
      }
    }
  }

  private def jacobianCircularNugget(variables: Array[Double]): Double => Array[Double] = {
    x: Double => {
      if (x == 0)
        Array.fill[Double](2)(0)
      else {
        val jacobianRet: Array[Double] = Array.ofDim[Double](2)
        jacobianRet(0) = -4 * x * variables(1) * math.sqrt(math.pow(variables(0), 2) - math.pow(x, 2)) / (math.Pi * math.pow(variables(0), 3))
        jacobianRet(1) = 1 - (2 / math.Pi) * math.acos(x / variables(0)) + ((2 * x) / (math.Pi * variables(0))) * math.sqrt(1 - math.pow(x / variables(0), 2))
        jacobianRet
      }
    }
  }

  /** Explicit [[Spherical]] Semivariogram model */
  private def explicitSpherical(r: Double, s: Double, a: Double): Double => Double = {
    h: Double => {
      if (h == 0) 0
      else if (h > r) s
      else
        a + (s - a) * ((3 * h / (2 * r)) - (math.pow(h, 3) / (2 * math.pow(r, 3)) ))
    }
    /*                      | 0                             , h = 0
     *                      |             | 3h      h^3   |
     *  gamma(h; r, s, a) = | a + (s - a) |---- - ------- | , 0 < h <= r
     *                      |             | 2r     2r^3   |
     *                      | s                             , h > r
     */
  }

  private def explicitSphericalNugget(r: Double, s: Double): Double => Double =
    explicitSpherical(r, s, 0)

  /** [[Spherical]] Semivariogram model's Jacobian for use in fitting an empirical semivariogram
    * to the explicit models
    */
  private def jacobianSpherical(variables: Array[Double]): Double => Array[Double] = {
    x: Double => {
      if (x == 0)
        Array.fill[Double](3)(0)
      else if (x > 0 && x <= variables(0)) {
        val jacobianRet: Array[Double] = Array.ofDim[Double](3)
        jacobianRet(0) = (variables(1) - variables(2)) * ((-3 * x) / (2 * math.pow(variables(0), 2)) + (3 * math.pow(x, 3)/(2 * math.pow(variables(0), 4))))
        jacobianRet(1) = (3 * x) / (2 * variables(0)) - (0.5 * math.pow(x / variables(0), 3))
        jacobianRet(2) = 1 - jacobianRet(1)
        jacobianRet
      }
      else
        Array[Double](0, 1, 0)
    }
  }

  private def jacobianSphericalNugget(variables: Array[Double]): Double => Array[Double] = {
    x: Double => {
      if (x == 0)
        Array.fill[Double](2)(0)
      else if (x>0 && x<=variables(0)) {
        val jacobianRet: Array[Double] = Array.ofDim[Double](2)
        jacobianRet(0) = variables(1) * ((-3 * x) / (2 * math.pow(variables(0), 2)) + (3 * math.pow(x, 3)/(2 * math.pow(variables(0), 4))))
        jacobianRet(1) = ((3 * x)/(2 * variables(0))) - (0.5 * math.pow(x / variables(0), 3))
        jacobianRet
      }
      else
        Array[Double](0, 1)
    }
  }

  /** Explicit [[Exponential]] Semivariogram model */
  private def explicitExponential(r: Double, s: Double, a: Double): Double => Double = {
    h: Double => {
      if (h == 0) 0
      else
        a + (s - a) * (1 - math.exp(- 3 * h / r))
    }
    /*                      | 0                                  , h = 0
     *  gamma(h; r, s, a) = |
     *                      | a + (s - a) {1 - e^(-3 * h / r)}   , h > 0
     */
  }

  private def explicitExponentialNugget(r: Double, s: Double): Double => Double =
    explicitExponential(r, s, 0)

  /** [[Exponential]] Semivariogram model's Jacobian for use in fitting an empirical semivariogram
    * to the explicit models
    */
  private def jacobianExponential(variables: Array[Double]): Double => Array[Double] = {
    x: Double => {
      if (x == 0)
        Array.fill[Double](3)(0)
      else {
        val jacobianRet: Array[Double] = Array.ofDim[Double](3)
        jacobianRet(0) = (variables(2) - variables(1)) * (3 * x / math.pow(variables(0), 2)) * math.exp(-3 * x / variables(0))
        jacobianRet(1) = 1 - math.exp(-3 * x / variables(0))
        jacobianRet(2) = 1 - jacobianRet(1)
        jacobianRet
      }
    }
  }

  private def jacobianExponentialNugget(variables: Array[Double]): Double => Array[Double] = {
    x: Double => {
      if (x == 0)
        Array.fill[Double](2)(0)
      else {
        val jacobianRet: Array[Double] = Array.ofDim[Double](2)
        jacobianRet(0) = -1 * variables(1) * (3 * x / math.pow(variables(0), 2)) * math.exp(-3 * x / variables(0))
        jacobianRet(1) = 1 - math.exp(-3 * x / variables(0))
        jacobianRet
      }
    }
  }

  /** Explicit [[Wave]] Semivariogram model */
  private def explicitWave(w: Double, s: Double, a: Double): Double => Double = {
    h: Double => {
      if (h == 0) 0
      else
        a + (s - a) * (1 - w * math.sin(h / w) / h)
    }
    /*                      | 0                                 , h = 0
     *                      |
     *  gamma(h; w, s, a) = |             |       sin(h / w)  |
     *                      | a + (s - a) |1 - w ------------ | , h > 0
     *                      |             |           h       |
     */
  }

  private def explicitWaveNugget(w: Double, s: Double): Double => Double =
    explicitWave(w, s, 0)

  /** [[Wave]] Semivariogram model's Jacobian for use in fitting an empirical semivariogram
    * to the explicit models
    */
  private def jacobianWave(variables: Array[Double]): Double => Array[Double] = {
    x: Double => {
      if (x == 0)
        Array.fill[Double](3)(0)
      else {
        val jacobianRet: Array[Double] = Array.ofDim[Double](3)
        jacobianRet(0) = (variables(1) - variables(2)) * ((math.cos(x / variables(0)) / variables(0)) - (math.sin(x / variables(0)) / x))
        jacobianRet(1) = 1 - variables(0) * math.sin(x / variables(0)) / x
        jacobianRet(2) = 1 - jacobianRet(1)
        jacobianRet
      }
    }
  }

  private def jacobianWaveNugget(variables: Array[Double]): Double => Array[Double] = {
    x: Double => {
      if (x == 0)
        Array.fill[Double](2)(0)
      else {
        val jacobianRet: Array[Double] = Array.ofDim[Double](2)
        jacobianRet(0) = variables(1) * ((math.cos(x / variables(0)) / variables(0)) - (math.sin(x / variables(0)) / x))
        jacobianRet(1) = 1 - variables(0) * math.sin(x / variables(0)) / x
        jacobianRet
      }
    }
  }

  def explicitNuggetModel(svParam: Array[Double], model: ModelType): Double => Double = {
    val (range: Double, sill: Double) = (svParam(0), svParam(1))
    explicitNuggetModel(range, sill, model)
  }

  def explicitNuggetModel(range: Double, sill: Double, model: ModelType): Double => Double = {
    model match {
      case Circular     =>  explicitCircularNugget(range, sill)
      case Spherical    =>  explicitSphericalNugget(range, sill)
      case Gaussian     =>  explicitGaussianNugget(range, sill)
      case Exponential  =>  explicitExponentialNugget(range, sill)
      case Wave         =>  explicitWaveNugget(range, sill)
      case _            =>
        throw new UnsupportedOperationException("$model is an invalid model or is not implemented")
    }
  }

  /**
    * @param svParam Semivariogram parameters in Array format (range, sill, nugget) or (range, sill)
    * @param model   [[ModelType]] input
    * @return        Semivariogram function
    */
  def explicitModel(svParam: Array[Double], model: ModelType): Double => Double = {
    val (range: Double, sill: Double, nugget: Double) = (svParam(0), svParam(1), svParam(2))
    explicitModel(range, sill, nugget, model)
  }

  /**
    * @param range   Range of Semivariogram
    * @param sill    Sill (flattening value) of Semivariogram
    * @param nugget  Nugget (intercept value) of Semivariogram
    * @param model   [[ModelType]] input
    * @return        Semivariogram function
    */
  def explicitModel(range: Double, sill: Double, nugget: Double, model: ModelType): Double => Double = {
    model match {
      case Circular     =>  explicitCircular(range, sill, nugget)
      case Spherical    =>  explicitSpherical(range, sill, nugget)
      case Gaussian     =>  explicitGaussian(range, sill, nugget)
      case Exponential  =>  explicitExponential(range, sill, nugget)
      case Wave         =>  explicitWave(range, sill, nugget)
      case _            =>
        throw new UnsupportedOperationException("$model is an invalid model or is not implemented")
    }
  }

  /**
    * @param variables The (range, sill, nugget) variable's current value being used while optimizing the function parameters
    * @param model     The [[ModelType]] being fitted into
    * @return          [[https://en.wikipedia.org/wiki/Jacobian_matrix_and_determinant]]
    */
  def jacobianModel(variables: Array[Double], model: ModelType): Double => Array[Double] = {
    if(variables.length == 3)
      model match {
        case Circular     => jacobianCircular(variables)
        case Spherical    => jacobianSpherical(variables)
        case Gaussian     => jacobianGaussian(variables)
        case Exponential  => jacobianExponential(variables)
        case Wave         => jacobianWave(variables)
        case _            =>
          throw new UnsupportedOperationException("$model is an invalid model or is not implemented")
      }
    else
      model match {
        case Circular     => jacobianCircularNugget(variables)
        case Spherical    => jacobianSphericalNugget(variables)
        case Gaussian     => jacobianGaussianNugget(variables)
        case Exponential  => jacobianExponentialNugget(variables)
        case Wave         => jacobianWaveNugget(variables)
        case _            =>
          throw new UnsupportedOperationException("$model is an invalid model or is not implemented")
      }
  }

  def apply(svParam: Array[Double], model: ModelType): Semivariogram = {
    if(svParam.length == 3)
      Semivariogram(explicitModel(svParam, model), svParam(0), svParam(1), svParam(2))
    else
      Semivariogram(explicitNuggetModel(svParam, model), svParam(0), svParam(1), 0)
  }

  /**
    * @param range   Range (Flattening point) of [[Semivariogram]]
    * @param sill    Sill (flattening value) of [[Semivariogram]]
    * @param nugget  Nugget (intercept value) of [[Semivariogram]]
    * @param model   [[NonLinearModelType]] model to be returned
    * @return        [[Semivariogram]]
    */
  def apply(range: Double, sill: Double, nugget: Double, model: ModelType): Semivariogram =
    Semivariogram(explicitModel(range, sill, nugget, model), range, sill, nugget)

  def apply(range: Double, sill: Double, model: ModelType): Semivariogram =
    Semivariogram(explicitNuggetModel(range, sill, model), range, sill, 0)

  /**
    * @param pts                   Points to be modelled and fitted
    * @param maxDistanceBandwidth  the maximum inter-point distance to be captured into the empirical semivariogram used for fitting
    * @param binMaxCount           the maximum number of bins in the empirical variogram
    * @param model                 The [[ModelType]] being fitted into
    * @return                      [[Semivariogram]]
    */
  def apply(pts: Array[PointFeature[Double]],
            maxDistanceBandwidth: Double,
            binMaxCount: Int,
            model: ModelType): Semivariogram = {
    val es: EmpiricalVariogram =
      EmpiricalVariogram.nonlinear(pts, maxDistanceBandwidth, binMaxCount)

    //Fitting the empirical variogram to the input model
    def stdev(data: Array[Double]): Double = {
      if (data.length < 2)
        return Double.NaN
      // average
      val mean: Double = data.sum / data.length
      // reduce function
      def f(sum: Double, tail: Double): Double = {
        val dif = tail - mean
        sum + dif * dif
      }

      val sum = data.foldLeft(0.0)((s, t) => f(s, t))
      math.sqrt(sum / (data.length - 1))
    }
    val dist: Array[Double] = es.distances
    val varianceValue: Array[Double] = es.variance
    val start: Array[Double] = Array.fill(3)(0)
    start(0) =
      dist.foldLeft(dist(0))
      { case (maxM, e) => math.max(maxM, e) }
    val Z: Array[Double] =
      Array.tabulate(pts.length)
      {i => pts(i).data}
    start(1) = math.pow(stdev(Z), 2)
    start(2) =
      math.max(0, varianceValue.foldLeft(dist(0))
                { case (minM, e) => math.min(minM, e) })
    Semivariogram.fit(es, model, start)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy