#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;
}
}
}
}