github.com/go-graphite/carbonapi@v0.17.0/expr/functions/polyfit/function.go (about)

     1  package polyfit
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"math"
     7  	"strconv"
     8  
     9  	"github.com/go-graphite/carbonapi/expr/consolidations"
    10  	"github.com/go-graphite/carbonapi/expr/helper"
    11  	"github.com/go-graphite/carbonapi/expr/interfaces"
    12  	"github.com/go-graphite/carbonapi/expr/types"
    13  	"github.com/go-graphite/carbonapi/pkg/parser"
    14  	"gonum.org/v1/gonum/mat"
    15  )
    16  
    17  type polyfit struct{}
    18  
    19  func GetOrder() interfaces.Order {
    20  	return interfaces.Any
    21  }
    22  
    23  func New(configFile string) []interfaces.FunctionMetadata {
    24  	res := make([]interfaces.FunctionMetadata, 0)
    25  	f := &polyfit{}
    26  	functions := []string{"polyfit"}
    27  	for _, n := range functions {
    28  		res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
    29  	}
    30  	return res
    31  }
    32  
    33  // polyfit(seriesList, degree=1, offset="0d")
    34  func (f *polyfit) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
    35  	// Fitting Nth degree polynom to the dataset
    36  	// https://en.wikipedia.org/wiki/Polynomial_regression#Matrix_form_and_calculation_of_estimates
    37  	arg, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  
    42  	degree, err := e.GetIntNamedOrPosArgDefault("degree", 1, 1)
    43  	if err != nil {
    44  		return nil, err
    45  	} else if degree < 1 {
    46  		return nil, errors.New("degree must be larger or equal to 1")
    47  	}
    48  	degreeStr := strconv.Itoa(degree)
    49  
    50  	offsStr, err := e.GetStringNamedOrPosArgDefault("offset", 2, "0d")
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	offs, err := parser.IntervalString(offsStr, 1)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	results := make([]*types.MetricData, 0, len(arg))
    61  	for _, a := range arg {
    62  		r := a.CopyLinkTags()
    63  		if e.ArgsLen() > 2 {
    64  			r.Name = "polyfit(" + a.Name + "," + degreeStr + ",'" + offsStr + "')"
    65  		} else if e.ArgsLen() > 1 {
    66  			r.Name = "polyfit(" + a.Name + "," + degreeStr + ")"
    67  		} else {
    68  			r.Name = "polyfit(" + a.Name + ")"
    69  		}
    70  		// Extending slice by "offset" so our graph slides into future!
    71  		r.Values = make([]float64, len(a.Values)+int(offs)/int(r.StepTime))
    72  		r.StopTime = a.StopTime + int64(offs)
    73  
    74  		// Removing absent values from original dataset
    75  		nonNulls := make([]float64, 0, len(a.Values))
    76  		for _, v := range a.Values {
    77  			if !math.IsNaN(v) {
    78  				nonNulls = append(nonNulls, v)
    79  			}
    80  		}
    81  		if len(nonNulls) < 2 {
    82  			for i := range r.Values {
    83  				r.Values[i] = math.NaN()
    84  			}
    85  			results = append(results, r)
    86  			continue
    87  		}
    88  
    89  		// STEP 1: Creating Vandermonde (X)
    90  		v := consolidations.Vandermonde(a.Values, degree)
    91  		// STEP 2: Creating (X^T * X)**-1
    92  		var t mat.Dense
    93  		t.Mul(v.T(), v)
    94  		var i mat.Dense
    95  		err := i.Inverse(&t)
    96  		if err != nil {
    97  			continue
    98  		}
    99  		// STEP 3: Creating I * X^T * y
   100  		var c mat.Dense
   101  		c.Product(&i, v.T(), mat.NewDense(len(nonNulls), 1, nonNulls))
   102  		// END OF STEPS
   103  
   104  		for i := range r.Values {
   105  			r.Values[i] = consolidations.Poly(float64(i), c.RawMatrix().Data...)
   106  		}
   107  		results = append(results, r)
   108  	}
   109  	return results, nil
   110  }
   111  
   112  func (f *polyfit) Description() map[string]types.FunctionDescription {
   113  	return map[string]types.FunctionDescription{
   114  		"polyfit": {
   115  			Description: "Fitting Nth degree polynom to the dataset. https://en.wikipedia.org/wiki/Polynomial_regression#Matrix_form_and_calculation_of_estimates",
   116  			Function:    "polyfit(seriesList, degree=1, offset=\"0d\")",
   117  			Group:       "Combine",
   118  			Module:      "graphite.render.functions.custom",
   119  			Name:        "polyfit",
   120  			Params: []types.FunctionParam{
   121  				{
   122  					Name:     "seriesList",
   123  					Required: true,
   124  					Type:     types.SeriesList,
   125  				},
   126  				{
   127  					Name:     "degree",
   128  					Default:  types.NewSuggestion(1),
   129  					Required: true,
   130  					Type:     types.Integer,
   131  				},
   132  				{
   133  					Default: types.NewSuggestion("0d"),
   134  					Name:    "offset",
   135  					Type:    types.Interval,
   136  				},
   137  			},
   138  			SeriesChange: true, // function aggregate metrics or change series items count
   139  			NameChange:   true, // name changed
   140  			TagsChange:   true, // name tag changed
   141  			ValuesChange: true, // values changed
   142  		},
   143  	}
   144  }