= Curve Fitting
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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.

These functions support constructing a curve.

== Polynomial Curve Fitting

The `polyfit` function is a general purpose curve fitter used to model
the non-linear relationship between two random variables.

The `polyfit` function is passed x- and y-axes and fits a smooth curve to the data.
If only a single array is provided it is treated as the y-axis and a sequence is generated
for the x-axis.

The `polyfit` function also has a parameter the specifies the degree of the polynomial. The higher
the degree the more curves that can be modeled.

The example below uses the `polyfit` function to fit a curve to an array using
a 3 degree polynomial. The fitted curve is then subtracted from the original curve. The output
shows the error between the fitted curve and the original curve, known as the residuals.
The output also includes the sum-of-squares of the residuals which provides a measure
of how large the error is.

[source,text]
----
let(echo="residuals, sumSqError",
    y=array(0, 1, 2, 3, 4, 5.7, 6, 7, 6, 5, 5, 3, 2, 1, 0),
    curve=polyfit(y, 3),
    residuals=ebeSubtract(y, curve),
    sumSqError=sumSq(residuals))
----

When this expression is sent to the `/stream` handler it
responds with:

[source,json]
----
{
  "result-set": {
    "docs": [
      {
        "residuals": [
          0.5886274509803899,
          -0.0746078431372561,
          -0.49492135315664765,
          -0.6689571213100631,
          -0.5933591898297781,
          0.4352283990519288,
          0.32016160310277897,
          1.1647963800904968,
          0.272488687782805,
          -0.3534055160525744,
          0.2904697263520779,
          -0.7925296272355089,
          -0.5990476190476182,
          -0.12572829131652274,
          0.6307843137254909
        ],
        "sumSqError": 4.7294282482223595
      },
      {
        "EOF": true,
        "RESPONSE_TIME": 0
      }
    ]
  }
}
----

In the next example the curve is fit using a 5 degree polynomial. Notice that the curve
is fit closer, shown by the smaller residuals and lower value for the sum-of-squares of the
residuals. This is because the higher polynomial produced a closer fit.

[source,text]
----
let(echo="residuals, sumSqError",
    y=array(0, 1, 2, 3, 4, 5.7, 6, 7, 6, 5, 5, 3, 2, 1, 0),
    curve=polyfit(y, 5),
    residuals=ebeSubtract(y, curve),
    sumSqError=sumSq(residuals))
----

When this expression is sent to the `/stream` handler it
responds with:

[source,json]
----
{
  "result-set": {
    "docs": [
      {
        "residuals": [
          -0.12337461300309674,
          0.22708978328173413,
          0.12266015718028167,
          -0.16502738747320755,
          -0.41142804563857105,
          0.2603044014808713,
          -0.12128970101106162,
          0.6234168308471704,
          -0.1754692675745293,
          -0.5379689969473249,
          0.4651616185671843,
          -0.288175756132409,
          0.027970945463215102,
          0.18699690402476687,
          -0.09086687306501587
        ],
        "sumSqError": 1.413089480179252
      },
      {
        "EOF": true,
        "RESPONSE_TIME": 0
      }
    ]
  }
}
----


=== Prediction, Derivatives and Integrals

The `polyfit` function returns a function that can be used with the `predict`
function.

In the example below the x-axis is included for clarity.
The `polyfit` function returns a function for the fitted curve.
The `predict` function is then used to predict a value along the curve, in this
case the prediction is made for the *`x`* value of 5.

[source,text]
----
let(x=array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14),
    y=array(0, 1, 2, 3, 4, 5.7, 6, 7, 6, 5, 5, 3, 2, 1, 0),
    curve=polyfit(x, y, 5),
    p=predict(curve, 5))
----

When this expression is sent to the `/stream` handler it
responds with:

[source,json]
----
{
  "result-set": {
    "docs": [
      {
        "p": 5.439695598519129
      },
      {
        "EOF": true,
        "RESPONSE_TIME": 0
      }
    ]
  }
}
----

The `derivative` and `integrate` functions can be used to compute the derivative
and integrals for the fitted
curve. The example below demonstrates how to compute a derivative
for the fitted curve.


[source,text]
----
let(x=array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14),
    y=array(0, 1, 2, 3, 4, 5.7, 6, 7, 6, 5, 5, 3, 2, 1, 0),
    curve=polyfit(x, y, 5),
    d=derivative(curve))
----

When this expression is sent to the `/stream` handler it
responds with:

[source,json]
----
{
  "result-set": {
    "docs": [
      {
        "d": [
          0.3198918573686361,
          0.9261492094077225,
          1.2374272373653175,
          1.30051359631081,
          1.1628032287629813,
          0.8722983646900058,
          0.47760852150945,
          0.02795050408827482,
          -0.42685159525716865,
          -0.8363663967611356,
          -1.1495552332084857,
          -1.3147721499346892,
          -1.2797639048258267,
          -0.9916699683185771,
          -0.3970225234002308
        ]
      },
      {
        "EOF": true,
        "RESPONSE_TIME": 0
      }
    ]
  }
}
----

== Harmonic Curve Fitting

The `harmonicFit` function (or `harmfit`, for short) fits a smooth line through control points of a sine wave.
The `harmfit` function is passed x- and y-axes and fits a smooth curve to the data.
If a single array is provided it is treated as the y-axis and a sequence is generated
for the x-axis.

The example below shows `harmfit` fitting a single oscillation of a sine wave. The `harmfit` function
returns the smoothed values at each control point. The return value is also a model which can be used by
the `predict`, `derivative` and `integrate` functions.

There are also three helper functions that can be used to retrieve the estimated parameters of the fitted model:

* `getAmplitude`: Returns the amplitude of the sine wave.
* `getAngularFrequency`: Returns the angular frequency of the sine wave.
* `getPhase`: Returns the phase of the sine wave.

NOTE: The `harmfit` function works best when run on a single oscillation rather than a long sequence of
oscillations. This is particularly true if the sine wave has noise. After the curve has been fit it can be
extrapolated to any point in time in the past or future.

In the example below the `harmfit` function fits control points, provided as x and y axes, and then the
angular frequency, phase and amplitude are retrieved from the fitted model.

[source,text]
----
let(echo="freq, phase, amp",
    x=array(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19),
    y=array(-0.7441113653915925,-0.8997532112139415, -0.9853140681578838, -0.9941296760805463,
            -0.9255133950087844, -0.7848096869247675, -0.5829778403072583, -0.33573836075915076,
            -0.06234851460699166, 0.215897602691855, 0.47732764497752245, 0.701579055431586,
             0.8711850882773975, 0.9729352782968976, 0.9989043923858761, 0.9470697190130273,
             0.8214686154479715, 0.631884041542757, 0.39308257356494, 0.12366424851680227),
    model=harmfit(x, y),
    freq=getAngularFrequency(model),
    phase=getPhase(model),
    amp=getAmplitude(model))
----

[source,json]
----
{
  "result-set": {
    "docs": [
      {
        "freq": 0.28,
        "phase": 2.4100000000000006,
        "amp": 0.9999999999999999
      },
      {
        "EOF": true,
        "RESPONSE_TIME": 0
      }
    ]
  }
}
----

=== Interpolation and Extrapolation

The `harmfit` function returns a fitted model of the sine wave that can used by the `predict` function to
interpolate or extrapolate the sine wave.

The example below uses the fitted model to extrapolate the sine wave beyond the control points
to the x-axis points 20, 21, 22, 23.

[source,text]
----
let(x=array(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19),
    y=array(-0.7441113653915925,-0.8997532112139415, -0.9853140681578838, -0.9941296760805463,
            -0.9255133950087844, -0.7848096869247675, -0.5829778403072583, -0.33573836075915076,
            -0.06234851460699166, 0.215897602691855, 0.47732764497752245, 0.701579055431586,
             0.8711850882773975, 0.9729352782968976, 0.9989043923858761, 0.9470697190130273,
             0.8214686154479715, 0.631884041542757, 0.39308257356494, 0.12366424851680227),
    model=harmfit(x, y),
    extrapolation=predict(model, array(20, 21, 22, 23)))
----

[source,json]
----
{
  "result-set": {
    "docs": [
      {
        "extrapolation": [
          -0.1553861764415666,
          -0.42233370833176975,
          -0.656386037906838,
          -0.8393130343914845
        ]
      },
      {
        "EOF": true,
        "RESPONSE_TIME": 0
      }
    ]
  }
}
----

== Gaussian Curve Fitting

The `gaussfit` function fits a smooth curve through a Gaussian peak.
This is shown in the example below.


[source,text]
----
let(x=array(0,1,2,3,4,5,6,7,8,9, 10),
    y=array(4,55,1200,3028,12000,18422,13328,6426,1696,239,20),
    f=gaussfit(x, y))
----

When this expression is sent to the `/stream` handler it responds with:

[source,json]
----
{
  "result-set": {
    "docs": [
      {
        "f": [
          2.81764431935644,
          61.157417979413424,
          684.2328985468831,
          3945.9411154167447,
          11729.758936952656,
          17972.951897338007,
          14195.201949425435,
          5779.03836032222,
          1212.7224502169634,
          131.17742331530349,
          7.3138931735866946
        ]
      },
      {
        "EOF": true,
        "RESPONSE_TIME": 0
      }
    ]
  }
}
----

Like the `polyfit` function, the `gaussfit` function returns a function that can
be used directly by the `predict`, `derivative` and `integrate` functions.

The example below demonstrates how to compute an integral for a fitted Gaussian curve.

[source,text]
----
let(x=array(0,1,2,3,4,5,6,7,8,9, 10),
    y=array(4,55,1200,3028,12000,18422,13328,6426,1696,239,20),
    f=gaussfit(x, y),
    i=integrate(f, 0, 5))

----

When this expression is sent to the `/stream` handler it
responds with:

[source,json]
----
{
  "result-set": {
    "docs": [
      {
        "i": 25261.666789766092
      },
      {
        "EOF": true,
        "RESPONSE_TIME": 3
      }
    ]
  }
}
----
