Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/HeuristicLab.Problems.DataAnalysis/3.4/Implementation/Regression/ShapeConstraintsParser.cs @ 18091

Last change on this file since 18091 was 17960, checked in by gkronber, 4 years ago

#3073: moved files

File size: 10.4 KB
RevLine 
[17887]1#region License Information
2
3/* HeuristicLab
[17896]4 * Copyright (C) Heuristic and Evolutionary Algorithms Laboratory (HEAL)
[17887]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 {
[17891]32    public static ShapeConstraints ParseConstraints(string text) {
[17887]33      if (string.IsNullOrEmpty(text)) throw new ArgumentNullException(nameof(text));
34
35      var lines = text.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
36
[17891]37      var sc = new ShapeConstraints();
[17887]38      foreach (var line in lines) {
39        var trimmedLine = line.Trim();
[17891]40        if (trimmedLine == "") continue; // If it is a comment just continue without saving anything
[17887]41        if (trimmedLine.StartsWith("#")) {
42          // disabled constraints are commented
[17891]43          var constraint = ParseConstaint(trimmedLine.TrimStart('#', ' '));
44          sc.Add(constraint);
45          sc.SetItemCheckedState(constraint, false);
[17887]46        } else {
[17891]47          var constraint = ParseConstaint(trimmedLine);
48          sc.Add(constraint);
49          sc.SetItemCheckedState(constraint, true);
[17887]50        }
51      }
[17891]52      return sc;
[17887]53    }
54
[17891]55    public static ShapeConstraint ParseConstaint(string expr) {
[17887]56      var trimmedLine = expr.TrimStart();
57      if (trimmedLine.StartsWith("f")) {
58        return ParseFunctionRangeConstraint(expr);
59      } else if (trimmedLine.StartsWith("d") || trimmedLine.StartsWith("∂")) {
[17891]60        return ParseDerivationConstraint(expr);
[17887]61      } else {
62        throw new
[17906]63          ArgumentException($"Error at in definition {expr}. Constraints have to start with (f | d | ∂ | #)",
[17887]64                            nameof(expr));
65      }
66    }
67
68    // [124 .. 145]
69    private const string intervalRegex = @"\s*[\[]" +
[17906]70                                         @"\s*(?<lowerBound>[^\s;]*)" +
[17887]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    public static ShapeConstraint ParseFunctionRangeConstraint(string expr) {
[17911]78      if (!expr.StartsWith("f")) throw new ArgumentException($"Invalid function range constraint {expr} (e.g. f in [1..2])");
[17887]79      var start = "f".Length;
80      var end = expr.Length;
81      var targetConstraint = expr.Substring(start, end - start);
82
83
84      // # Example for a target variable constraint:
85      // f in [0 .. 100]
86      // # Example for constraints on model parameters:
87      // df/d'x' in [0 .. 10]
88      // ∂²f/∂'x'² in [-1 .. inf.]
89      // df/d'x' in [0 .. 10] weight: 2.0
90      // df / d'x' in [0..10], 'x' in [1 .. 3]
91      // df / d'x' in [0..10], 'x' in [1 .. 3], y in [10..30] weight: 1.2
92
93      var match = Regex.Match(targetConstraint,
94                    @"\s*\bin\b" +
95                    intervalRegex +
96                    @"(" +
[17906]97                      @"\s*,\s*" + variableRegex +
[17887]98                      @"\s *\bin\b" +
99                      intervalRegex +
100                    @")*" +
101                    weightRegex
[17906]102                    );
[17887]103
104
105      if (match.Success) {
106        // if (match.Groups.Count < 19) throw new ArgumentException("The target-constraint is not complete.", nameof(expr));
107
108        var lowerBound = ParseIntervalBounds(match.Groups["lowerBound"].Captures[0].Value);
109        var upperBound = ParseIntervalBounds(match.Groups["upperBound"].Captures[0].Value);
110        var interval = new Interval(lowerBound, upperBound);
111        var weight = 1.0;
112
113        if (match.Groups["weight"].Success && !string.IsNullOrWhiteSpace(match.Groups["weight"].Value))
114          weight = ParseAndValidateDouble(match.Groups["weight"].Value);
115
116        if (match.Groups["varName"].Success) {
117          IntervalCollection regions = new IntervalCollection();
118          // option variables found
119          for (int idx = 0; idx < match.Groups["varName"].Captures.Count; ++idx) {
120            KeyValuePair<string, Interval> region = ParseRegion(
121              variable: match.Groups["varName"].Captures[idx].Value,
122              lb: match.Groups["lowerBound"].Captures[idx + 1].Value,
123              ub: match.Groups["upperBound"].Captures[idx + 1].Value);
124            if (regions.GetReadonlyDictionary().All(r => r.Key != region.Key))
125              regions.AddInterval(region.Key, region.Value);
126            else
[17906]127              throw new ArgumentException($"The constraint {expr} has multiple regions of the same variable.");
[17887]128          }
[17891]129          return new ShapeConstraint(interval, regions, weight);
[17887]130        } else
[17891]131          return new ShapeConstraint(interval, weight);
[17887]132      } else
[17911]133        throw new ArgumentException($"The target constraint {expr} is not valid.");
[17887]134    }
[17891]135    public static ShapeConstraint ParseDerivationConstraint(string expr) {
[17887]136      var match = Regex.Match(expr,
137                                @"([d∂])" +
138                                @"(?<numDerivations>[²³]?)\s*" +
139                                @"f\s*" +
140                                @"(\/)\s*" +
141                                @"([d∂])\s*" +
142                                variableRegex +
143                                @"(?<numDerivations>[²³]?)\s*\bin\b\s*" +
144                                intervalRegex +
145                                @"(" +
146                                  @"\s*,\s*" + variableRegex +
147                                  @"\s *\bin\b" +
148                                  intervalRegex +
149                                  @")*" +
150                                weightRegex
[17906]151                                );
[17887]152
153      if (match.Success) {
154        // if (match.Groups.Count < 26)
155        //   throw new ArgumentException("The given derivation-constraint is not complete.", nameof(expr));
156
157        var derivationVariable = match.Groups["varName"].Captures[0].Value.Trim();
158
159        var enumeratorNumDeriv = match.Groups["numDerivations"].Captures[0].Value.Trim();
160        var denominatorNumDeriv = match.Groups["numDerivations"].Captures[1].Value.Trim();
161        if (enumeratorNumDeriv != "" || denominatorNumDeriv != "") {
162          if (enumeratorNumDeriv == "" || denominatorNumDeriv == "")
[17911]163            throw new ArgumentException($"Number of derivation has to be written on both sides in {expr}.");
[17887]164          if (enumeratorNumDeriv != denominatorNumDeriv)
[17911]165            throw new ArgumentException($"Derivation number is not equal on both sides in {expr}.");
[17887]166        }
167
168        var lowerBound = ParseIntervalBounds(match.Groups["lowerBound"].Captures[0].Value);
169        var upperBound = ParseIntervalBounds(match.Groups["upperBound"].Captures[0].Value);
170        var variable = derivationVariable;
171        var numberOfDerivation = ParseDerivationCount(enumeratorNumDeriv);
172        var interval = new Interval(lowerBound, upperBound);
173        var weight = 1.0;
174
175        if (match.Groups["weight"].Success && !string.IsNullOrWhiteSpace(match.Groups["weight"].Value))
176          weight = ParseAndValidateDouble(match.Groups["weight"].Value);
177
178        if (match.Groups["varName"].Captures.Count > 1) {
179          IntervalCollection regions = new IntervalCollection();
180          // option variables found
181          for (int idx = 0; idx < match.Groups["varName"].Captures.Count - 1; ++idx) {
182            KeyValuePair<string, Interval> region = ParseRegion(
183              variable: match.Groups["varName"].Captures[idx + 1].Value,
184              lb: match.Groups["lowerBound"].Captures[idx + 1].Value,
185              ub: match.Groups["upperBound"].Captures[idx + 1].Value);
186            if (regions.GetReadonlyDictionary().All(r => r.Key != region.Key))
187              regions.AddInterval(region.Key, region.Value);
188            else
[17906]189              throw new ArgumentException($"The constraint {expr} has multiple regions of the same variable.");
[17887]190          }
[17891]191          return new ShapeConstraint(variable, numberOfDerivation, interval, regions, weight);
[17887]192        } else
[17891]193          return new ShapeConstraint(variable, numberOfDerivation, interval, weight);
[17887]194      } else
[17911]195        throw new ArgumentException($"The derivation constraint {expr} is not valid.");
[17887]196    }
197
198
199    private static KeyValuePair<string, Interval> ParseRegion(string variable, string lb, string ub) {
200      var regionLb = ParseIntervalBounds(lb);
201      var regionUb = ParseIntervalBounds(ub);
202      return new KeyValuePair<string, Interval>(variable, new Interval(regionLb, regionUb));
203    }
204
205    private static double ParseIntervalBounds(string input) {
206      input = input.ToLower();
207      switch (input) {
208        case "+inf.":
209        case "inf.":
210          return double.PositiveInfinity;
211        case "-inf.":
212          return double.NegativeInfinity;
213        default: {
214            return ParseAndValidateDouble(input);
215          }
216      }
217    }
218
219    private static double ParseAndValidateDouble(string input) {
220      var valid = double.TryParse(input, out var value);
221      if (!valid) {
[17906]222        throw new ArgumentException($"Invalid value {input} (valid value format: \"" + "+inf. | inf. | -inf. | " + FormatPatterns.GetDoubleFormatPattern() + "\")");
[17887]223      }
224
225      return value;
226    }
227
228    private static int ParseDerivationCount(string input) {
229      switch (input) {
230        case "":
231          return 1;
232        case "²":
233          return 2;
234        case "³":
235          return 3;
236        default:
237          int.TryParse(input, out var value);
238          return value;
239      }
240    }
241  }
242}
Note: See TracBrowser for help on using the repository browser.