#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.Linq;
using HeuristicLab.Common;
using HeuristicLab.Core;
using HeuristicLab.Data;
using HeuristicLab.Parameters;
using HEAL.Attic;
namespace HeuristicLab.Operators {
[Item("DataReducer", "An operator to reduce values of sub scopes.")]
[StorableType("87DC7AB9-4C8D-4FF9-B0B0-70B7E7F32C3C")]
public sealed class DataReducer : SingleSuccessorOperator {
#region Parameter Properties
public ScopeTreeLookupParameter ParameterToReduce {
get { return (ScopeTreeLookupParameter)Parameters["ParameterToReduce"]; }
}
public LookupParameter TargetParameter {
get { return (LookupParameter)Parameters["TargetParameter"]; }
}
public ValueLookupParameter ReductionOperation {
get { return (ValueLookupParameter)Parameters["ReductionOperation"]; }
}
public ValueLookupParameter TargetOperation {
get { return (ValueLookupParameter)Parameters["TargetOperation"]; }
}
#endregion
[StorableConstructor]
private DataReducer(StorableConstructorFlag _) : base(_) { }
private DataReducer(DataReducer original, Cloner cloner)
: base(original, cloner) {
}
public DataReducer()
: base() {
#region Create parameters
Parameters.Add(new ScopeTreeLookupParameter("ParameterToReduce", "The parameter on which the reduction operation should be applied."));
Parameters.Add(new LookupParameter("TargetParameter", "The target variable in which the reduced value should be stored."));
Parameters.Add(new ValueLookupParameter("ReductionOperation", "The operation which is applied on the parameters to reduce.", new ReductionOperation()));
Parameters.Add(new ValueLookupParameter("TargetOperation", "The operation used to apply the reduced value to the target variable.", new ReductionOperation()));
#endregion
}
public override IDeepCloneable Clone(Cloner cloner) {
return new DataReducer(this, cloner);
}
[StorableHook(HookType.AfterDeserialization)]
private void AfterDeserialization() {
// BackwardsCompatibility3.3
var oldReductionOperation = Parameters["ReductionOperation"] as ValueParameter;
if (oldReductionOperation != null) {
Parameters.Remove("ReductionOperation");
Parameters.Add(new ValueLookupParameter("ReductionOperation", "The operation which is applied on the parameters to reduce.", oldReductionOperation.Value));
}
var oldTargetOperation = Parameters["TargetOperation"] as ValueParameter;
if (oldTargetOperation != null) {
Parameters.Remove("TargetOperation");
Parameters.Add(new ValueLookupParameter("TargetOperation", "The operation used to apply the reduced value to the target variable.", oldTargetOperation.Value));
}
}
public override IOperation Apply() {
var values = ParameterToReduce.ActualValue;
if (!values.Any()) return base.Apply();
if (values.All(x => x is IntValue)) {
CalculateResult(values.OfType().Select(x => x.Value), values.First().GetType());
} else if (values.All(x => x is DoubleValue)) {
CalculateResult(values.OfType().Select(x => x.Value), values.First().GetType());
} else if (values.All(x => x is TimeSpanValue)) {
CalculateResult(values.OfType().Select(x => x.Value), values.First().GetType());
} else if (values.All(x => x is BoolValue)) {
CalculateResult(values.OfType().Select(x => x.Value), values.First().GetType());
} else {
throw new ArgumentException(string.Format("Type {0} is not supported by the DataReducer.", values.First().GetType()));
}
return base.Apply();
}
#region integer reduction
private void CalculateResult(IEnumerable values, Type targetType) {
int result;
switch (ReductionOperation.ActualValue.Value) {
case ReductionOperations.Sum:
result = values.Sum();
break;
case ReductionOperations.Product:
result = values.Aggregate(1, (x, y) => x * y);
break;
case ReductionOperations.Count:
result = values.Count();
break;
case ReductionOperations.Min:
result = values.Min();
break;
case ReductionOperations.Max:
result = values.Max();
break;
case ReductionOperations.Avg:
result = (int)Math.Round(values.Average());
break;
case ReductionOperations.Assign:
result = values.Last();
break;
default:
throw new InvalidOperationException(string.Format("Operation {0} is not supported as ReductionOperation for type: {1}.", ReductionOperation.ActualValue.Value, targetType));
}
IntValue target;
switch (TargetOperation.ActualValue.Value) {
case ReductionOperations.Sum:
target = InitializeTarget(targetType, 0);
target.Value += result;
break;
case ReductionOperations.Product:
target = InitializeTarget(targetType, 1);
target.Value = target.Value * result;
break;
case ReductionOperations.Min:
target = InitializeTarget(targetType, int.MaxValue);
target.Value = Math.Min(target.Value, result);
break;
case ReductionOperations.Max:
target = InitializeTarget(targetType, int.MinValue);
target.Value = Math.Max(target.Value, result);
break;
case ReductionOperations.Avg:
target = InitializeTarget(targetType, result);
target.Value = (int)Math.Round((target.Value + result) / 2.0);
break;
case ReductionOperations.Assign:
target = InitializeTarget(targetType, 0);
target.Value = result;
break;
default:
throw new InvalidOperationException(string.Format("Operation {0} is not supported as TargetOperation for type: {1}.", TargetOperation.ActualValue.Value, targetType));
}
}
#endregion
#region double reduction
private void CalculateResult(IEnumerable values, Type targetType) {
double result;
switch (ReductionOperation.ActualValue.Value) {
case ReductionOperations.Sum:
result = values.Sum();
break;
case ReductionOperations.Product:
result = values.Aggregate(1.0, (x, y) => x * y);
break;
case ReductionOperations.Count:
result = values.Count();
break;
case ReductionOperations.Min:
result = values.Min();
break;
case ReductionOperations.Max:
result = values.Max();
break;
case ReductionOperations.Avg:
result = values.Average();
break;
case ReductionOperations.Assign:
result = values.Last();
break;
default:
throw new InvalidOperationException(string.Format("Operation {0} is not supported as ReductionOperation for type: {1}.", ReductionOperation.ActualValue.Value, targetType));
}
DoubleValue target;
switch (TargetOperation.ActualValue.Value) {
case ReductionOperations.Sum:
target = InitializeTarget(targetType, 0.0);
target.Value += result;
break;
case ReductionOperations.Product:
target = InitializeTarget(targetType, 1.0);
target.Value = target.Value * result;
break;
case ReductionOperations.Min:
target = InitializeTarget(targetType, double.MaxValue);
target.Value = Math.Min(target.Value, result);
break;
case ReductionOperations.Max:
target = InitializeTarget(targetType, double.MinValue);
target.Value = Math.Max(target.Value, result);
break;
case ReductionOperations.Avg:
target = InitializeTarget(targetType, result);
target.Value = (target.Value + result) / 2.0;
break;
case ReductionOperations.Assign:
target = InitializeTarget(targetType, 0.0);
target.Value = result;
break;
default:
throw new InvalidOperationException(string.Format("Operation {0} is not supported as TargetOperation for type: {1}.", TargetOperation.ActualValue.Value, targetType));
}
}
#endregion
#region TimeSpan reduction
private void CalculateResult(IEnumerable values, Type targetType) {
TimeSpan result;
switch (ReductionOperation.ActualValue.Value) {
case ReductionOperations.Sum:
result = values.Aggregate(new TimeSpan(), (x, y) => x + y);
break;
case ReductionOperations.Min:
result = values.Min();
break;
case ReductionOperations.Max:
result = values.Max();
break;
case ReductionOperations.Avg:
result = TimeSpan.FromMilliseconds(values.Average(x => x.TotalMilliseconds));
break;
case ReductionOperations.Assign:
result = values.Last();
break;
default:
throw new InvalidOperationException(string.Format("Operation {0} is not supported as ReductionOperation for type: {1}.", ReductionOperation.ActualValue.Value, targetType));
}
TimeSpanValue target;
switch (TargetOperation.ActualValue.Value) {
case ReductionOperations.Sum:
target = InitializeTarget(targetType, new TimeSpan());
target.Value += result;
break;
case ReductionOperations.Min:
target = InitializeTarget(targetType, TimeSpan.MaxValue);
target.Value = target.Value < result ? target.Value : result;
break;
case ReductionOperations.Max:
target = InitializeTarget(targetType, TimeSpan.MinValue);
target.Value = target.Value > result ? target.Value : result;
break;
case ReductionOperations.Avg:
target = InitializeTarget(targetType, result);
target.Value = TimeSpan.FromMilliseconds((target.Value.TotalMilliseconds + result.TotalMilliseconds) / 2);
break;
case ReductionOperations.Assign:
target = InitializeTarget(targetType, new TimeSpan());
target.Value = result;
break;
default:
throw new InvalidOperationException(string.Format("Operation {0} is not supported as TargetOperation for type: {1}.", TargetOperation.ActualValue.Value, targetType));
}
}
#endregion
#region bool reduction
private void CalculateResult(IEnumerable values, Type targetType) {
bool result;
switch (ReductionOperation.ActualValue.Value) {
case ReductionOperations.All:
result = values.All(x => x);
break;
case ReductionOperations.Any:
result = values.Any(x => x);
break;
default:
throw new InvalidOperationException(string.Format("Operation {0} is not supported as ReductionOperation for type: {1}.", ReductionOperation.ActualValue.Value, targetType));
}
BoolValue target;
switch (TargetOperation.ActualValue.Value) {
case ReductionOperations.Assign:
target = InitializeTarget(targetType, true);
target.Value = result;
break;
default:
throw new InvalidOperationException(string.Format("Operation {0} is not supported as TargetOperation for type: {1}.", TargetOperation.ActualValue.Value, targetType));
}
}
#endregion
#region helpers
private T1 InitializeTarget(Type targetType, T2 initialValue)
where T1 : ValueTypeValue
where T2 : struct {
T1 target = (T1)TargetParameter.ActualValue;
if (target == null) {
target = (T1)Activator.CreateInstance(targetType);
TargetParameter.ActualValue = target;
target.Value = initialValue;
}
return target;
}
#endregion
}
}