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

Last change on this file since 17960 was 17960, checked in by gkronber, 6 months ago

#3073: moved files

File size: 10.4 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    public static ShapeConstraint ParseFunctionRangeConstraint(string expr) {
78      if (!expr.StartsWith("f")) throw new ArgumentException($"Invalid function range constraint {expr} (e.g. f in [1..2])");
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                    @"(" +
97                      @"\s*,\s*" + variableRegex +
98                      @"\s *\bin\b" +
99                      intervalRegex +
100                    @")*" +
101                    weightRegex
102                    );
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
127              throw new ArgumentException($"The constraint {expr} has multiple regions of the same variable.");
128          }
129          return new ShapeConstraint(interval, regions, weight);
130        } else
131          return new ShapeConstraint(interval, weight);
132      } else
133        throw new ArgumentException($"The target constraint {expr} is not valid.");
134    }
135    public static ShapeConstraint ParseDerivationConstraint(string expr) {
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
151                                );
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 == "")
163            throw new ArgumentException($"Number of derivation has to be written on both sides in {expr}.");
164          if (enumeratorNumDeriv != denominatorNumDeriv)
165            throw new ArgumentException($"Derivation number is not equal on both sides in {expr}.");
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
189              throw new ArgumentException($"The constraint {expr} has multiple regions of the same variable.");
190          }
191          return new ShapeConstraint(variable, numberOfDerivation, interval, regions, weight);
192        } else
193          return new ShapeConstraint(variable, numberOfDerivation, interval, weight);
194      } else
195        throw new ArgumentException($"The derivation constraint {expr} is not valid.");
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) {
222        throw new ArgumentException($"Invalid value {input} (valid value format: \"" + "+inf. | inf. | -inf. | " + FormatPatterns.GetDoubleFormatPattern() + "\")");
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.