using HeuristicLab.Collections; using HeuristicLab.Common; using HeuristicLab.Problems.DataAnalysis; using System; using System.Globalization; using System.Linq; namespace HeuristicLab.Algorithms.DataAnalysis.FastFunctionExtraction { internal struct SimpleBasisFunction : ISimpleBasisFunction { public double Exponent { get; set; } public NonlinearOperator Operator { get; set; } public bool IsDenominator { get; set; } public string Feature { get; set; } public double Threshold { get; set; } // only relevant for hinge function public bool HasExponent => !(Exponent == 1.0); public bool HasOperator => Operator != NonlinearOperator.None; public int Complexity => Operator == NonlinearOperator.GT_Hinge || Operator == NonlinearOperator.LT_Hinge ? 3 : 1; public SimpleBasisFunction(string feature, double exponent = 1, NonlinearOperator op = NonlinearOperator.None, bool isNominator = true, double threshold = 0) { Feature = feature ?? throw new ArgumentNullException(nameof(feature)); Exponent = exponent; Operator = op; IsDenominator = isNominator; Threshold = threshold; } public IBasisFunction DeepCopy() { return new SimpleBasisFunction(Feature, Exponent, Operator, IsDenominator, Threshold); } public override string ToString() { var culture = new CultureInfo("en-US"); string str = $"\"{Feature}\""; if (HasExponent) { if (Exponent.IsAlmost((double)-1 / 2)) str = $"1 / SQRT({str})"; else if (Exponent.IsAlmost((double)1 / 2)) str = $"SQRT({str})"; else if (Exponent.IsAlmost(1)) str = $"1 / {str}"; else if (Exponent.IsAlmost(2)) str = $"SQR({str})"; else if (Exponent.IsAlmost(3)) str = $"CUBE({str})"; else str = $"({str}) ^ ({Exponent.ToString(culture)})"; } if (!HasOperator) return str; var thr = Threshold.ToString(culture); if (Operator == NonlinearOperator.LT_Hinge) { var expr = $"{str} {((Threshold >= 0) ? " - " + Math.Abs(Threshold).ToString(culture) : " + " + Math.Abs(Threshold).ToString(culture))}"; str = $"IF(GT(0, {expr}), 0, {expr})"; } else if (Operator == NonlinearOperator.GT_Hinge) { var expr = $"{thr} - {str}"; str = $"IF(GT(0, {expr}), 0, {expr})"; } else { str = $"{opToStr.GetByFirst(Operator)}({str})"; } return str; } public double[] Evaluate(IRegressionProblemData data) { var exp = Exponent; // exponentVals : e.g. "x3^2" var exponentVals = data.Dataset.GetDoubleValues(Feature) .Select(val => Math.Pow(val, exp)) .ToArray(); // vals: e.g. "log(x3^2) var vals = Eval(exponentVals); if (!IsDenominator) { var y = data.TargetVariableValues; vals = vals.Zip(y, (a, b) => -a * b).ToArray(); } return vals; } private double[] Eval(double[] x) { var thr = this.Threshold; switch (Operator) { case NonlinearOperator.None: return x; case NonlinearOperator.Abs: return x.Select(val => Math.Abs(val)).ToArray(); case NonlinearOperator.Log: return x.Select(val => Math.Log(val)).ToArray(); case NonlinearOperator.Sin: return x.Select(val => Math.Sin(val)).ToArray(); case NonlinearOperator.Cos: return x.Select(val => Math.Cos(val)).ToArray(); case NonlinearOperator.GT_Hinge: return x.Select(val => Math.Max(0, thr - val)).ToArray(); case NonlinearOperator.LT_Hinge: return x.Select(val => Math.Max(0, val - thr)).ToArray(); default: throw new Exception("Unimplemented operator: " + Operator.ToString()); } } private static readonly BidirectionalDictionary opToStr = new BidirectionalDictionary() { { NonlinearOperator.Abs, "ABS"}, { NonlinearOperator.Log, "LOG"}, { NonlinearOperator.Sin, "SIN"}, { NonlinearOperator.Cos, "COS"} }; } }