#region License Information /* HeuristicLab * Copyright (C) 2002-2016 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.Collections; using HeuristicLab.Common; using HeuristicLab.Core; using HeuristicLab.Data; using HeuristicLab.Encodings.RealVectorEncoding; using HeuristicLab.Optimization; using HeuristicLab.Parameters; using HeuristicLab.Persistence.Default.CompositeSerializers.Storable; using HeuristicLab.Problems.DataAnalysis; namespace HeuristicLab.GoalSeeking { [Item("Goal seeking problem (multi-objective)", "Represents a single objective optimization problem which uses configurable regression models to evaluate targets from a given dataset.")] [Creatable("Problems")] [StorableClass] public sealed class MultiObjectiveGoalSeekingProblem : MultiObjectiveBasicProblem, IGoalSeekingProblem { #region parameter names private const string InputsParameterName = "Inputs"; private const string GoalsParameterName = "Goals"; private const string ModelsParameterName = "Models"; private const string QualitySumCutoffParameterName = "QualitySumCutoff"; #endregion #region parameters public IValueParameter> InputsParameter { get { return (IValueParameter>)Parameters[InputsParameterName]; } } public IValueParameter> GoalsParameter { get { return (IValueParameter>)Parameters[GoalsParameterName]; } } public IFixedValueParameter> ModelsParameter { get { return (IFixedValueParameter>)Parameters[ModelsParameterName]; } } public IFixedValueParameter QualitySumCutoffParameter { get { return (IFixedValueParameter)Parameters[QualitySumCutoffParameterName]; } } #endregion #region IGoalSeekingProblem implementation public IEnumerable Models { get { return ModelsParameter.Value; } } public IEnumerable Goals { get { return GoalsParameter.Value; } } public IEnumerable Inputs { get { return InputsParameter.Value; } } public void AddModel(IRegressionModel model) { var models = ModelsParameter.Value; models.Add(model); GoalSeekingUtil.RaiseEvent(this, ModelsChanged); } public void RemoveModel(IRegressionModel model) { var models = ModelsParameter.Value; models.Remove(model); GoalSeekingUtil.RaiseEvent(this, ModelsChanged); } public void Configure(IRegressionProblemData problemData, int row) { GoalSeekingUtil.Configure(Goals, Inputs, problemData, row); } public IEnumerable GetEstimatedGoalValues(IEnumerable parameterValues, bool round = false) { var ds = (ModifiableDataset)dataset.Clone(); foreach (var parameter in ActiveInputs.Zip(parameterValues, (p, v) => new { Name = p.Name, Value = v })) { ds.SetVariableValue(parameter.Value, parameter.Name, 0); } var rows = new[] { 0 }; // actually just one row var estimatedValues = round ? ActiveGoals.Select(t => RoundToNearestStepMultiple(GetModels(t.Name).Average(m => m.GetEstimatedValues(ds, rows).Single()), t.Step)) : ActiveGoals.Select(t => GetModels(t.Name).Average(m => m.GetEstimatedValues(ds, rows).Single())); return estimatedValues; } public event EventHandler ModelsChanged; public event EventHandler TargetsChanged; public event EventHandler ParametersChanged; #endregion private IEnumerable ActiveGoals { get { return Goals.Where(x => x.Active); } } private IEnumerable ActiveInputs { get { return Inputs.Where(x => x.Active); } } private double QualitySumCutoff { get { return QualitySumCutoffParameter.Value.Value; } } [Storable] private ModifiableDataset dataset; // modifiable dataset [Storable] private bool[] maximization; public override bool[] Maximization { get { return maximization ?? new bool[] { false }; } } public ValueParameter MaximizationParameter { get { return (ValueParameter)Parameters["Maximization"]; } } #region constructors [StorableConstructor] private MultiObjectiveGoalSeekingProblem(bool deserializing) : base(deserializing) { } private MultiObjectiveGoalSeekingProblem(MultiObjectiveGoalSeekingProblem original, Cloner cloner) : base(original, cloner) { this.dataset = cloner.Clone(original.dataset); RegisterEvents(); } public override IDeepCloneable Clone(Cloner cloner) { return new MultiObjectiveGoalSeekingProblem(this, cloner); } [StorableHook(HookType.AfterDeserialization)] private void AfterDeserialization() { RegisterEvents(); } public MultiObjectiveGoalSeekingProblem() { dataset = new ModifiableDataset(); Parameters.Add(new ValueParameter>(InputsParameterName)); Parameters.Add(new ValueParameter>(GoalsParameterName)); Parameters.Add(new FixedValueParameter>(ModelsParameterName, new ItemList())); Parameters.Add(new FixedValueParameter(QualitySumCutoffParameterName, new DoubleValue(0.2))); QualitySumCutoffParameter.Hidden = true; EncodingParameter.Hidden = true; EvaluatorParameter.Hidden = true; SolutionCreatorParameter.Hidden = true; MaximizationParameter.Hidden = true; RegisterEvents(); } #endregion public override double[] Evaluate(Individual individual, IRandom random) { var vector = individual.RealVector(); vector.ElementNames = ActiveInputs.Select(x => x.Name); int i = 0; // round vector according to parameter step sizes foreach (var parameter in ActiveInputs) { vector[i] = RoundToNearestStepMultiple(vector[i], parameter.Step); ++i; } var estimatedValues = GetEstimatedGoalValues(vector, round: true); var qualities = ActiveGoals.Zip(estimatedValues, (t, v) => new { Target = t, EstimatedValue = v }) .Select(x => x.Target.Weight * Math.Pow(x.EstimatedValue - x.Target.Goal, 2) / x.Target.Variance); return qualities.ToArray(); } #region pareto analyzer public override void Analyze(Individual[] individuals, double[][] qualities, ResultCollection results, IRandom random) { var matrix = FilterFrontsByQualitySum(individuals, qualities, Math.Max(QualitySumCutoff, qualities.Min(x => x.Sum()))); const string resultName = "Pareto Front Solutions"; // disclaimer: not really a pareto front if (!results.ContainsKey(resultName)) { results.Add(new Result(resultName, matrix)); } else { results[resultName].Value = matrix; } base.Analyze(individuals, qualities, results, random); } private DoubleMatrix FilterFrontsByQualitySum(Individual[] individuals, double[][] qualities, double qualitySumCutoff) { var activeParameters = ActiveInputs.ToList(); var activeGoals = ActiveGoals.ToList(); var filteredModels = new List(); var rowNames = new List(); // build list of column names by combining target and parameter names (with their respective original and estimated values) var columnNames = new List { "Quality Sum" }; foreach (var target in activeGoals) { columnNames.Add(target.Name); columnNames.Add(target.Name + " (estimated)"); } foreach (var parameter in activeParameters) { columnNames.Add(parameter.Name); columnNames.Add(parameter.Name + " (estimated)"); columnNames.Add(parameter.Name + " (deviation)"); } // filter models based on their quality sum; remove duplicate models var dec = new DoubleEqualityComparer(); // comparer which uses the IsAlmost method for comparing floating point numbers for (int i = 0; i < individuals.Length; ++i) { var qualitySum = qualities[i].Sum(); if (qualitySum > qualitySumCutoff) continue; var vector = individuals[i].RealVector(); var estimatedValues = GetEstimatedGoalValues(vector).ToList(); var rowValues = new double[columnNames.Count]; rowValues[0] = qualitySum; int offset = 1; for (int j = 0; j < activeGoals.Count * 2; j += 2) { int k = j + offset; var goal = activeGoals[j / 2].Goal; rowValues[k] = goal; // original value rowValues[k + 1] = estimatedValues[j / 2]; // estimated value } offset += activeGoals.Count * 2; for (int j = 0; j < activeParameters.Count * 3; j += 3) { int k = j + offset; rowValues[k] = activeParameters[j / 3].Value; rowValues[k + 1] = vector[j / 3]; rowValues[k + 2] = rowValues[k + 1] - rowValues[k]; } if (!filteredModels.Any(x => x.SequenceEqual(rowValues, dec))) { rowNames.Add((i + 1).ToString()); filteredModels.Add(rowValues); } } var matrix = new DoubleMatrix(filteredModels.Count, columnNames.Count) { RowNames = rowNames, ColumnNames = columnNames, SortableView = true }; for (int i = 0; i < filteredModels.Count; ++i) { for (int j = 0; j < filteredModels[i].Length; ++j) { matrix[i, j] = filteredModels[i][j]; } } return matrix; } #endregion #region event handlers private void RegisterEvents() { ModelsParameter.Value.ItemsAdded += ModelCollection_ItemsChanged; ModelsParameter.Value.ItemsRemoved += ModelCollection_ItemsChanged; GoalsParameter.Value.CheckedItemsChanged += GoalSeekingUtil.Goals_CheckedItemsChanged; InputsParameter.Value.CheckedItemsChanged += GoalSeekingUtil.Inputs_CheckedItemsChanged; foreach (var input in Inputs) input.Changed += InputParameterChanged; foreach (var goal in Goals) goal.Changed += GoalParameterChanged; } private void ModelCollection_ItemsChanged(object sender, CollectionItemsChangedEventArgs> e) { if (e.Items == null || !e.Items.Any()) return; GoalSeekingUtil.UpdateInputs(InputsParameter.Value, Models, InputParameterChanged); GoalSeekingUtil.UpdateEncoding(Encoding, ActiveInputs); dataset = Inputs.Any() ? new ModifiableDataset(Inputs.Select(x => x.Name), Inputs.Select(x => new List { x.Value })) : new ModifiableDataset(); GoalSeekingUtil.UpdateTargets(GoalsParameter.Value, Models, GoalParameterChanged); GoalSeekingUtil.RaiseEvent(this, ModelsChanged); } private void InputParameterChanged(object sender, EventArgs args) { var inputParameter = (InputParameter)sender; var inputs = InputsParameter.Value; if (inputs.ItemChecked(inputParameter) != inputParameter.Active) inputs.SetItemCheckedState(inputParameter, inputParameter.Active); GoalSeekingUtil.UpdateEncoding(Encoding, ActiveInputs); } private void GoalParameterChanged(object sender, EventArgs args) { var goalParameter = (GoalParameter)sender; var goals = GoalsParameter.Value; if (goals.ItemChecked(goalParameter) != goalParameter.Active) goals.SetItemCheckedState(goalParameter, goalParameter.Active); } #endregion #region helper methods // method which throws an exception that can be caught in the event handler if the check fails private void CheckIfDatasetContainsTarget(string target) { if (dataset.DoubleVariables.All(x => x != target)) throw new ArgumentException(string.Format("Model target \"{0}\" does not exist in the dataset.", target)); } private static double RoundToNearestStepMultiple(double value, double step) { return step * (long)Math.Round(value / step); } private IEnumerable GetModels(string target) { return Models.Where(x => x.TargetVariable == target); } private class DoubleEqualityComparer : IEqualityComparer { public bool Equals(double x, double y) { return x.IsAlmost(y); } public int GetHashCode(double obj) { return obj.GetHashCode(); } } #endregion } }