#region License Information /* HeuristicLab * Copyright (C) Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using HeuristicLab.Data; namespace HeuristicLab.Problems.DataAnalysis { public static class ShapeConstraintsParser { public static ShapeConstraints ParseConstraints(string text) { if (string.IsNullOrEmpty(text)) throw new ArgumentNullException(nameof(text)); var lines = text.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); var sc = new ShapeConstraints(); foreach (var line in lines) { var trimmedLine = line.Trim(); if (trimmedLine == "") continue; // If it is a comment just continue without saving anything if (trimmedLine.StartsWith("#")) { // disabled constraints are commented var constraint = ParseConstaint(trimmedLine.TrimStart('#', ' ')); sc.Add(constraint); sc.SetItemCheckedState(constraint, false); } else { var constraint = ParseConstaint(trimmedLine); sc.Add(constraint); sc.SetItemCheckedState(constraint, true); } } return sc; } public static ShapeConstraint ParseConstaint(string expr) { var trimmedLine = expr.TrimStart(); if (trimmedLine.StartsWith("f")) { return ParseFunctionRangeConstraint(expr); } else if (trimmedLine.StartsWith("d") || trimmedLine.StartsWith("∂")) { return ParseDerivationConstraint(expr); } else { throw new ArgumentException($"Error at in definition {expr}. Constraints have to start with (f | d | ∂ | #)", nameof(expr)); } } // [124 .. 145] private const string intervalRegex = @"\s*[\[]" + @"\s*(?[^\s;]*)" + @"\s*(\.{2}|\s+|;)" + @"\s*(?[^\s\]]*)" + @"\s*[\]]"; private const string variableRegex = @"(['](?.*)[']|(?[^\s²³]+))\s*"; private const string weightRegex = @"\s*(weight:\s*(?\S*))?"; public static ShapeConstraint ParseFunctionRangeConstraint(string expr) { if (!expr.StartsWith("f")) throw new ArgumentException($"Invalid function range constraint {expr} (e.g. f in [1..2])"); var start = "f".Length; var end = expr.Length; var targetConstraint = expr.Substring(start, end - start); // # Example for a target variable constraint: // f in [0 .. 100] // # Example for constraints on model parameters: // df/d'x' in [0 .. 10] // ∂²f/∂'x'² in [-1 .. inf.] // df/d'x' in [0 .. 10] weight: 2.0 // df / d'x' in [0..10], 'x' in [1 .. 3] // df / d'x' in [0..10], 'x' in [1 .. 3], y in [10..30] weight: 1.2 var match = Regex.Match(targetConstraint, @"\s*\bin\b" + intervalRegex + @"(" + @"\s*,\s*" + variableRegex + @"\s *\bin\b" + intervalRegex + @")*" + weightRegex ); if (match.Success) { // if (match.Groups.Count < 19) throw new ArgumentException("The target-constraint is not complete.", nameof(expr)); var lowerBound = ParseIntervalBounds(match.Groups["lowerBound"].Captures[0].Value); var upperBound = ParseIntervalBounds(match.Groups["upperBound"].Captures[0].Value); var interval = new Interval(lowerBound, upperBound); var weight = 1.0; if (match.Groups["weight"].Success && !string.IsNullOrWhiteSpace(match.Groups["weight"].Value)) weight = ParseAndValidateDouble(match.Groups["weight"].Value); if (match.Groups["varName"].Success) { IntervalCollection regions = new IntervalCollection(); // option variables found for (int idx = 0; idx < match.Groups["varName"].Captures.Count; ++idx) { KeyValuePair region = ParseRegion( variable: match.Groups["varName"].Captures[idx].Value, lb: match.Groups["lowerBound"].Captures[idx + 1].Value, ub: match.Groups["upperBound"].Captures[idx + 1].Value); if (regions.GetReadonlyDictionary().All(r => r.Key != region.Key)) regions.AddInterval(region.Key, region.Value); else throw new ArgumentException($"The constraint {expr} has multiple regions of the same variable."); } return new ShapeConstraint(interval, regions, weight); } else return new ShapeConstraint(interval, weight); } else throw new ArgumentException($"The target constraint {expr} is not valid."); } public static ShapeConstraint ParseDerivationConstraint(string expr) { var match = Regex.Match(expr, @"([d∂])" + @"(?[²³]?)\s*" + @"f\s*" + @"(\/)\s*" + @"([d∂])\s*" + variableRegex + @"(?[²³]?)\s*\bin\b\s*" + intervalRegex + @"(" + @"\s*,\s*" + variableRegex + @"\s *\bin\b" + intervalRegex + @")*" + weightRegex ); if (match.Success) { // if (match.Groups.Count < 26) // throw new ArgumentException("The given derivation-constraint is not complete.", nameof(expr)); var derivationVariable = match.Groups["varName"].Captures[0].Value.Trim(); var enumeratorNumDeriv = match.Groups["numDerivations"].Captures[0].Value.Trim(); var denominatorNumDeriv = match.Groups["numDerivations"].Captures[1].Value.Trim(); if (enumeratorNumDeriv != "" || denominatorNumDeriv != "") { if (enumeratorNumDeriv == "" || denominatorNumDeriv == "") throw new ArgumentException($"Number of derivation has to be written on both sides in {expr}."); if (enumeratorNumDeriv != denominatorNumDeriv) throw new ArgumentException($"Derivation number is not equal on both sides in {expr}."); } var lowerBound = ParseIntervalBounds(match.Groups["lowerBound"].Captures[0].Value); var upperBound = ParseIntervalBounds(match.Groups["upperBound"].Captures[0].Value); var variable = derivationVariable; var numberOfDerivation = ParseDerivationCount(enumeratorNumDeriv); var interval = new Interval(lowerBound, upperBound); var weight = 1.0; if (match.Groups["weight"].Success && !string.IsNullOrWhiteSpace(match.Groups["weight"].Value)) weight = ParseAndValidateDouble(match.Groups["weight"].Value); if (match.Groups["varName"].Captures.Count > 1) { IntervalCollection regions = new IntervalCollection(); // option variables found for (int idx = 0; idx < match.Groups["varName"].Captures.Count - 1; ++idx) { KeyValuePair region = ParseRegion( variable: match.Groups["varName"].Captures[idx + 1].Value, lb: match.Groups["lowerBound"].Captures[idx + 1].Value, ub: match.Groups["upperBound"].Captures[idx + 1].Value); if (regions.GetReadonlyDictionary().All(r => r.Key != region.Key)) regions.AddInterval(region.Key, region.Value); else throw new ArgumentException($"The constraint {expr} has multiple regions of the same variable."); } return new ShapeConstraint(variable, numberOfDerivation, interval, regions, weight); } else return new ShapeConstraint(variable, numberOfDerivation, interval, weight); } else throw new ArgumentException($"The derivation constraint {expr} is not valid."); } private static KeyValuePair ParseRegion(string variable, string lb, string ub) { var regionLb = ParseIntervalBounds(lb); var regionUb = ParseIntervalBounds(ub); return new KeyValuePair(variable, new Interval(regionLb, regionUb)); } private static double ParseIntervalBounds(string input) { input = input.ToLower(); switch (input) { case "+inf.": case "inf.": return double.PositiveInfinity; case "-inf.": return double.NegativeInfinity; default: { return ParseAndValidateDouble(input); } } } private static double ParseAndValidateDouble(string input) { var valid = double.TryParse(input, out var value); if (!valid) { throw new ArgumentException($"Invalid value {input} (valid value format: \"" + "+inf. | inf. | -inf. | " + FormatPatterns.GetDoubleFormatPattern() + "\")"); } return value; } private static int ParseDerivationCount(string input) { switch (input) { case "": return 1; case "²": return 2; case "³": return 3; default: int.TryParse(input, out var value); return value; } } } }