Free cookie consent management tool by TermsFeed Policy Generator

source: branches/3119_AdditionalShapeConstraintFeatures/HeuristicLab.Problems.DataAnalysis/3.4/Implementation/Interval/ShapeConstraintsParser.cs @ 17995

Last change on this file since 17995 was 17995, checked in by dpiringe, 3 years ago

#3119

  • added additional parameters to enable different evaluation options
  • added additive restrictions
  • added additional implementations for dynamic restrictions:
    • dynamic intervalls
    • exponatial smoothing
    • rising multiplier
  • adapted IntervalUtil to get model bounds and refactored some sections
  • adapted ShapeConstraintsParser for added features
  • added a ResultCollection in SymbolicRegressionSolution for shape constraint violations
File size: 13.2 KB
Line 
1#region License Information
2
3/* HeuristicLab
4 * Copyright (C) Heuristic and Evolutionary Algorithms Laboratory (HEAL)
5 *
6 * This file is part of HeuristicLab.
7 *
8 * HeuristicLab is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * HeuristicLab is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#endregion
23
24using System;
25using System.Collections.Generic;
26using System.Linq;
27using System.Text.RegularExpressions;
28using HeuristicLab.Data;
29
30namespace HeuristicLab.Problems.DataAnalysis {
31  public static class ShapeConstraintsParser {
32    public static ShapeConstraints ParseConstraints(string text) {
33      if (string.IsNullOrEmpty(text)) throw new ArgumentNullException(nameof(text));
34
35      var lines = text.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
36
37      var sc = new ShapeConstraints();
38      foreach (var line in lines) {
39        var trimmedLine = line.Trim();
40        if (trimmedLine == "") continue; // If it is a comment just continue without saving anything
41        if (trimmedLine.StartsWith("#")) {
42          // disabled constraints are commented
43          var constraint = ParseConstaint(trimmedLine.TrimStart('#', ' '));
44          sc.Add(constraint);
45          sc.SetItemCheckedState(constraint, false);
46        } else {
47          var constraint = ParseConstaint(trimmedLine);
48          sc.Add(constraint);
49          sc.SetItemCheckedState(constraint, true);
50        }
51      }
52      return sc;
53    }
54
55    public static ShapeConstraint ParseConstaint(string expr) {
56      var trimmedLine = expr.TrimStart();
57      if (trimmedLine.StartsWith("f")) {
58        return ParseFunctionRangeConstraint(expr);
59      } else if (trimmedLine.StartsWith("d") || trimmedLine.StartsWith("∂")) {
60        return ParseDerivationConstraint(expr);
61      } else {
62        throw new
63          ArgumentException($"Error at in definition {expr}. Constraints have to start with (f | d | ∂ | #)",
64                            nameof(expr));
65      }
66    }
67
68    // [124 .. 145]
69    private const string intervalRegex = @"\s*[\[]" +
70                                         @"\s*(?<lowerBound>[^\s;]*)" +
71                                         @"\s*(\.{2}|\s+|;)" +
72                                         @"\s*(?<upperBound>[^\s\]]*)" +
73                                         @"\s*[\]]";
74
75    private const string variableRegex = @"(['](?<varName>.*)[']|(?<varName>[^\s²³]+))\s*";
76    private const string weightRegex = @"\s*(weight:\s*(?<weight>\S*))?";
77    private const string thresholdRegex = @"\s*(threshold\s*in\s*(?<threshold> "+ intervalRegex + @"))?";
78    private const string dynIntervalStartRegex = @"\s*(start\s*in\s*(?<dynInterval> " + intervalRegex + @"))?";
79    public static ShapeConstraint ParseFunctionRangeConstraint(string expr) {
80      if (!expr.StartsWith("f")) throw new ArgumentException($"Invalid function range constraint {expr} (e.g. f in [1..2])");
81      var start = "f".Length;
82      var end = expr.Length;
83      var targetConstraint = expr.Substring(start, end - start);
84
85
86      // # Example for a target variable constraint:
87      // f in [0 .. 100]
88      // # Example for constraints on model parameters:
89      // df/d'x' in [0 .. 10]
90      // ∂²f/∂'x'² in [-1 .. inf.]
91      // df/d'x' in [0 .. 10] weight: 2.0
92      // df / d'x' in [0..10], 'x' in [1 .. 3]
93      // df / d'x' in [0..10], 'x' in [1 .. 3], 'y' in [10..30] weight: 1.2
94      // df / d'x' in [0..10], 'x' in [1 .. 3], 'y' in [10..30] weight: 1.2 threshold in [-10 .. 10]
95      var match = Regex.Match(targetConstraint,
96                    @"\s*\bin\b" +
97                    intervalRegex +
98                    @"(" +
99                      @"\s*,\s*" + variableRegex +
100                      @"\s *\bin\b" +
101                      intervalRegex +
102                    @")*" +
103                    weightRegex +
104                    thresholdRegex +
105                    dynIntervalStartRegex
106                    );
107
108
109      if (match.Success) {
110        // if (match.Groups.Count < 19) throw new ArgumentException("The target-constraint is not complete.", nameof(expr));
111
112        var lowerBound = ParseIntervalBounds(match.Groups["lowerBound"].Captures[0].Value);
113        var upperBound = ParseIntervalBounds(match.Groups["upperBound"].Captures[0].Value);
114        var interval = new Interval(lowerBound, upperBound);
115        var weight = 1.0;
116        var threshold = new Interval(0, 0);
117        Interval dynInterval = new Interval(double.NegativeInfinity, double.PositiveInfinity);
118        int intervalIdx = 1;
119        var lowerboundCount = match.Groups["lowerBound"].Captures.Count;
120        var upperboundCount = match.Groups["upperBound"].Captures.Count;
121
122        if (match.Groups["weight"].Success && !string.IsNullOrWhiteSpace(match.Groups["weight"].Value))
123          weight = ParseAndValidateDouble(match.Groups["weight"].Value);
124
125        if (match.Groups["dynInterval"].Success) {
126          var dynIntervalLb = ParseIntervalBounds(match.Groups["lowerBound"].Captures[lowerboundCount - intervalIdx].Value);
127          var dynIntervalUb = ParseIntervalBounds(match.Groups["upperBound"].Captures[upperboundCount - intervalIdx].Value);
128          intervalIdx++;
129          dynInterval = new Interval(dynIntervalLb, dynIntervalUb);
130        }
131
132        if (match.Groups["threshold"].Success) {
133          var thresholdLb = ParseIntervalBounds(match.Groups["lowerBound"].Captures[lowerboundCount - intervalIdx].Value);
134          var thresholdUb = ParseIntervalBounds(match.Groups["upperBound"].Captures[upperboundCount - intervalIdx].Value);
135          intervalIdx++;
136          threshold = new Interval(thresholdLb, thresholdUb);
137        }
138
139
140        if (match.Groups["varName"].Success) {
141          IntervalCollection regions = new IntervalCollection();
142          // option variables found
143          for (int idx = 0; idx < match.Groups["varName"].Captures.Count; ++idx) {
144            KeyValuePair<string, Interval> region = ParseRegion(
145              variable: match.Groups["varName"].Captures[idx].Value,
146              lb: match.Groups["lowerBound"].Captures[idx + 1].Value,
147              ub: match.Groups["upperBound"].Captures[idx + 1].Value);
148            if (regions.GetReadonlyDictionary().All(r => r.Key != region.Key))
149              regions.AddInterval(region.Key, region.Value);
150            else
151              throw new ArgumentException($"The constraint {expr} has multiple regions of the same variable.");
152          }
153          return new ShapeConstraint(interval, regions, weight, threshold, dynInterval);
154        } else
155          return new ShapeConstraint(interval, weight, threshold, dynInterval);
156      } else
157        throw new ArgumentException($"The target constraint {expr} is not valid.");
158    }
159    public static ShapeConstraint ParseDerivationConstraint(string expr) {
160      var match = Regex.Match(expr,
161                                @"([d∂])" +
162                                @"(?<numDerivations>[²³]?)\s*" +
163                                @"f\s*" +
164                                @"(\/)\s*" +
165                                @"([d∂])\s*" +
166                                variableRegex +
167                                @"(?<numDerivations>[²³]?)\s*\bin\b\s*" +
168                                intervalRegex +
169                                @"(" +
170                                  @"\s*,\s*" + variableRegex +
171                                  @"\s *\bin\b" +
172                                  intervalRegex +
173                                  @")*" +
174                                weightRegex +
175                                thresholdRegex +
176                                dynIntervalStartRegex
177                                );
178
179      if (match.Success) {
180        // if (match.Groups.Count < 26)
181        //   throw new ArgumentException("The given derivation-constraint is not complete.", nameof(expr));
182
183        var derivationVariable = match.Groups["varName"].Captures[0].Value.Trim();
184
185        var enumeratorNumDeriv = match.Groups["numDerivations"].Captures[0].Value.Trim();
186        var denominatorNumDeriv = match.Groups["numDerivations"].Captures[1].Value.Trim();
187        if (enumeratorNumDeriv != "" || denominatorNumDeriv != "") {
188          if (enumeratorNumDeriv == "" || denominatorNumDeriv == "")
189            throw new ArgumentException($"Number of derivation has to be written on both sides in {expr}.");
190          if (enumeratorNumDeriv != denominatorNumDeriv)
191            throw new ArgumentException($"Derivation number is not equal on both sides in {expr}.");
192        }
193
194        var lowerBound = ParseIntervalBounds(match.Groups["lowerBound"].Captures[0].Value);
195        var upperBound = ParseIntervalBounds(match.Groups["upperBound"].Captures[0].Value);
196        var variable = derivationVariable;
197        var numberOfDerivation = ParseDerivationCount(enumeratorNumDeriv);
198        var interval = new Interval(lowerBound, upperBound);
199        var weight = 1.0;
200        var threshold = new Interval(0, 0);
201        Interval dynInterval = new Interval(double.NegativeInfinity, double.PositiveInfinity);
202        int intervalIdx = 1;
203        var lowerboundCount = match.Groups["lowerBound"].Captures.Count;
204        var upperboundCount = match.Groups["upperBound"].Captures.Count;
205
206        if (match.Groups["weight"].Success && !string.IsNullOrWhiteSpace(match.Groups["weight"].Value))
207          weight = ParseAndValidateDouble(match.Groups["weight"].Value);
208
209        if (match.Groups["dynInterval"].Success) {
210          var dynIntervalLb = ParseIntervalBounds(match.Groups["lowerBound"].Captures[lowerboundCount - intervalIdx].Value);
211          var dynIntervalUb = ParseIntervalBounds(match.Groups["upperBound"].Captures[upperboundCount - intervalIdx].Value);
212          intervalIdx++;
213          dynInterval = new Interval(dynIntervalLb, dynIntervalUb);
214        }
215
216        if (match.Groups["threshold"].Success) {
217          var thresholdLb = ParseIntervalBounds(match.Groups["lowerBound"].Captures[lowerboundCount - intervalIdx].Value);
218          var thresholdUb = ParseIntervalBounds(match.Groups["upperBound"].Captures[upperboundCount - intervalIdx].Value);
219          intervalIdx++;
220          threshold = new Interval(thresholdLb, thresholdUb);
221        }
222
223        if (match.Groups["varName"].Captures.Count > 1) {
224          IntervalCollection regions = new IntervalCollection();
225          // option variables found
226          for (int idx = 0; idx < match.Groups["varName"].Captures.Count - 1; ++idx) {
227            KeyValuePair<string, Interval> region = ParseRegion(
228              variable: match.Groups["varName"].Captures[idx + 1].Value,
229              lb: match.Groups["lowerBound"].Captures[idx + 1].Value,
230              ub: match.Groups["upperBound"].Captures[idx + 1].Value);
231            if (regions.GetReadonlyDictionary().All(r => r.Key != region.Key))
232              regions.AddInterval(region.Key, region.Value);
233            else
234              throw new ArgumentException($"The constraint {expr} has multiple regions of the same variable.");
235          }
236          return new ShapeConstraint(variable, numberOfDerivation, interval, regions, weight, threshold, dynInterval);
237        } else
238          return new ShapeConstraint(variable, numberOfDerivation, interval, weight, threshold, dynInterval);
239      } else
240        throw new ArgumentException($"The derivation constraint {expr} is not valid.");
241    }
242
243
244    private static KeyValuePair<string, Interval> ParseRegion(string variable, string lb, string ub) {
245      var regionLb = ParseIntervalBounds(lb);
246      var regionUb = ParseIntervalBounds(ub);
247      return new KeyValuePair<string, Interval>(variable, new Interval(regionLb, regionUb));
248    }
249
250    private static double ParseIntervalBounds(string input) {
251      input = input.ToLower();
252      switch (input) {
253        case "+inf.":
254        case "inf.":
255          return double.PositiveInfinity;
256        case "-inf.":
257          return double.NegativeInfinity;
258        default: {
259            return ParseAndValidateDouble(input);
260          }
261      }
262    }
263
264    private static double ParseAndValidateDouble(string input) {
265      var valid = double.TryParse(input, out var value);
266      if (!valid) {
267        throw new ArgumentException($"Invalid value {input} (valid value format: \"" + "+inf. | inf. | -inf. | " + FormatPatterns.GetDoubleFormatPattern() + "\")");
268      }
269
270      return value;
271    }
272
273    private static int ParseDerivationCount(string input) {
274      switch (input) {
275        case "":
276          return 1;
277        case "²":
278          return 2;
279        case "³":
280          return 3;
281        default:
282          int.TryParse(input, out var value);
283          return value;
284      }
285    }
286  }
287}
Note: See TracBrowser for help on using the repository browser.