#region License Information /* HeuristicLab * Copyright (C) 2002-2019 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.Globalization; using System.Linq; using System.Text.RegularExpressions; namespace HeuristicLab.Problems.DataAnalysis { public static class IntervalConstraintsParser { public static IEnumerable ParseInput(string inputText, string target, IEnumerable variables) { if (string.IsNullOrEmpty(inputText)) throw new ArgumentNullException("No input text has been provided."); if (string.IsNullOrEmpty(target)) throw new ArgumentNullException("No target variable has been provided."); if (variables == null) throw new ArgumentNullException("No variables have been provided."); if (!variables.Any()) throw new ArgumentException("Variables are empty."); var lines = inputText.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.None); foreach (var line in lines) { var trimmedLine = line.TrimStart(); //Check for target-variable constraint if (trimmedLine.StartsWith("Target:")) { var start = "Target:".Length; var end = trimmedLine.Length; var targetConstraint = trimmedLine.Substring(start, end - start); var match = Regex.Match(targetConstraint, @"(['](.*)[']|(.*[^\s]))" + @"\s*(\bin\b)" + @"\s*([\[])" + @"\s*(\S*)" + // 6: interval lower bound @"\s*(\.{2})" + @"\s*(\S*)" + // 8: interval upper bound @"\s*([\]])" + @"(" + @"\s*,\s*(\S*)\s*=\s*" + // 11: variable name @"([(])\s*" + @"(\S*)\s*" + // 13: region lower bound @"(\.{2})\s*" + @"(\S*)\s*" + // 15: region upper bound @"([)])" + @")*" + @"\s*(<(\S*)>)?"); // 17, 18 if (match.Success) { if (match.Groups.Count < 19) throw new ArgumentException("The target-constraint is not complete.", line); var targetVariable = match.Groups[1].Value.Trim(); if (targetVariable.StartsWith("'") && targetVariable.EndsWith("'")) targetVariable = targetVariable.Substring(1, targetVariable.Length - 2); if (targetVariable != target) throw new ArgumentException($"The target variable {targetVariable} does not match the provided target {target}.", line); var lowerBound = ParseIntervalBounds(match.Groups[6].Value); var upperBound = ParseIntervalBounds(match.Groups[8].Value); var expression = "Target:" + match.Groups[0].Value; var parsedTarget = match.Groups[1].Value.Trim(); var variable = targetVariable; var isEnabled = true; var numberOfDerivation = 0; var interval = new Interval(lowerBound, upperBound); var weight = 1.0; if (match.Groups[18].Success && !string.IsNullOrWhiteSpace(match.Groups[18].Value)) weight = ParseWeight(match.Groups[18].Value); if (match.Groups[10].Success) { IntervalCollection regions = new IntervalCollection(); // option variables found for(int idx = 0; idx < match.Groups[10].Captures.Count; ++idx) { KeyValuePair region = ParseRegion( match.Groups[11].Captures[idx].Value, match.Groups[13].Captures[idx].Value, match.Groups[15].Captures[idx].Value); if (regions.GetReadonlyDictionary().All(r => r.Key != region.Key)) regions.AddInterval(region.Key, region.Value); else throw new ArgumentException("A constraint cannot contain multiple regions of the same variable."); } yield return new IntervalConstraint(expression, variable, parsedTarget, numberOfDerivation, interval, regions, weight, isEnabled); } else yield return new IntervalConstraint(expression, variable, parsedTarget, numberOfDerivation, interval, weight, isEnabled); } else throw new ArgumentException("The inserted target constraint is not valid.", line); //Check for derivation } else if (trimmedLine.StartsWith("d") || trimmedLine.StartsWith("\u2202")) { var match = Regex.Match(trimmedLine, @"([d∂])" + @"([²³]?)\s*" + @"(['](.*)[']|(.*[^\s]))\s*" + @"(\/)\s*" + @"([d∂])\s*" + @"(['](.*)[']|(.*[^\s²³]))\s*" + @"([²³]?)\s*\bin\b\s*" + @"([\[])\s*" + @"(\S*)\s*" + @"(\.{2})\s*" + @"(\S*)\s*" + @"([\]])" + @"(" + @"\s*,\s*(\S*)\s*=\s*" + // 18: variable name @"([(])\s*" + @"(\S*)\s*" + // 20: region lower bound @"(\.{2})\s*" + @"(\S*)\s*" + // 22: region upper bound @"([)])" + @")*" + @"\s*(<(\S*)>)?"); // 24, 25 if (match.Success) { if (match.Groups.Count < 26) throw new ArgumentException("The given derivation-constraint is not complete.", line); var derivationTarget = match.Groups[3].Value.Trim(); var derivationVariable = match.Groups[8].Value.Trim(); if (match.Groups[3].Value.Trim().StartsWith("'") && match.Groups[3].Value.Trim().EndsWith("'")) derivationTarget = derivationTarget.Substring(1, derivationTarget.Length - 2); if (match.Groups[8].Value.Trim().StartsWith("'") && match.Groups[8].Value.Trim().EndsWith("'")) derivationVariable = derivationVariable.Substring(1, derivationVariable.Length - 2); if (derivationTarget != target) throw new ArgumentException($"The target variable {derivationTarget} does not match the provided target {target}.", line); if (variables.All(v => v != derivationVariable)) throw new ArgumentException($"The given variable {derivationVariable} does not exist in the dataset.", line); if (match.Groups[2].Value.Trim() != "" || match.Groups[11].Value.Trim() != "") { if (match.Groups[2].Value.Trim() == "" || match.Groups[11].Value.Trim() == "") throw new ArgumentException("Number of derivation has to be written on both sides.", line); if (match.Groups[2].Value.Trim() != match.Groups[11].Value.Trim()) throw new ArgumentException("Derivation number is not equal on both sides.", line); } var lowerBound = ParseIntervalBounds(match.Groups[13].Value); var upperBound = ParseIntervalBounds(match.Groups[15].Value); var expression = match.Groups[0].Value; var parsedTarget = derivationTarget; var isEnabled = true; var variable = derivationVariable; var numberOfDerivation = ParseDerivationCount(match.Groups[2].Value.Trim()); var interval = new Interval(lowerBound, upperBound); var weight = 1.0; if(match.Groups[25].Success && !string.IsNullOrWhiteSpace(match.Groups[25].Value)) weight = ParseWeight(match.Groups[25].Value); if(match.Groups[17].Success) { IntervalCollection regions = new IntervalCollection(); // option variables found for (int idx = 0; idx < match.Groups[17].Captures.Count; ++idx) { KeyValuePair region = ParseRegion( match.Groups[18].Captures[idx].Value, match.Groups[20].Captures[idx].Value, match.Groups[22].Captures[idx].Value); if (regions.GetReadonlyDictionary().All(r => r.Key != region.Key)) regions.AddInterval(region.Key, region.Value); else throw new ArgumentException("A constraint cannot contain multiple regions of the same variable."); } yield return new IntervalConstraint(expression, variable, parsedTarget, numberOfDerivation, interval, regions, weight, isEnabled); } else yield return new IntervalConstraint(expression, variable, parsedTarget, numberOfDerivation, interval, weight, isEnabled); } else throw new ArgumentException("The inserted derivation constraint is not valid.", line); //Check for comment } else if (trimmedLine.StartsWith("#") || trimmedLine == "") { //If it is a comment just continue without saving anything } else { throw new ArgumentException("Error at your constraints definition constraints have to start with (Target: | d | \u2202 | #)", line); } } } 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)); //return new Region(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: { if (double.TryParse(input.Replace(',', '.'), NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) return value; throw new ArgumentException("The given boundary is not a double value!"); } } } private static double ParseWeight(string input) { if (double.TryParse(input, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) return value; throw new ArgumentException("The given weight is not a double 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; } } } }