[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 |
|
---|
| 24 | using System;
|
---|
| 25 | using System.Collections.Generic;
|
---|
| 26 | using System.Linq;
|
---|
| 27 | using System.Text.RegularExpressions;
|
---|
| 28 | using HeuristicLab.Data;
|
---|
| 29 |
|
---|
| 30 | namespace 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 | } |
---|