#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.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 (single-objective)", "Represents a single objective optimization problem which uses configurable regression solutions to evaluate targets from a given dataset.")] [Creatable("Problems")] [StorableClass] public sealed class SingleObjectiveGoalSeekingProblem : SingleObjectiveBasicProblem, IGoalSeekingProblem { #region parameter names private const string ModifiableDatasetParameterName = "Dataset"; private const string InputsParameterName = "Inputs"; private const string GoalsParameterName = "Goals"; private const string ModelsParameterName = "Models"; #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]; } } #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); } } [Storable] private ModifiableDataset dataset; // modifiable dataset public override bool Maximization { get { return false; } } #region constructors [StorableConstructor] private SingleObjectiveGoalSeekingProblem(bool deserializing) : base(deserializing) { } private SingleObjectiveGoalSeekingProblem(SingleObjectiveGoalSeekingProblem original, Cloner cloner) : base(original, cloner) { this.dataset = cloner.Clone(original.dataset); RegisterEvents(); } public override IDeepCloneable Clone(Cloner cloner) { return new SingleObjectiveGoalSeekingProblem(this, cloner); } [StorableHook(HookType.AfterDeserialization)] private void AfterDeserialization() { RegisterEvents(); } public SingleObjectiveGoalSeekingProblem() { dataset = new ModifiableDataset(); Parameters.Add(new ValueParameter(ModifiableDatasetParameterName, dataset) { Hidden = true }); Parameters.Add(new ValueParameter>(InputsParameterName)); Parameters.Add(new ValueParameter>(GoalsParameterName)); Parameters.Add(new FixedValueParameter>(ModelsParameterName, new ItemList())); EncodingParameter.Hidden = true; EvaluatorParameter.Hidden = true; SolutionCreatorParameter.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 quality = ActiveGoals.Zip(estimatedValues, (t, v) => new { Target = t, EstimatedValue = v }) .Average(x => x.Target.Weight * Math.Pow(x.EstimatedValue - x.Target.Goal, 2) / x.Target.Variance); return quality; } #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 IEnumerable GetModels(string target) { return Models.Where(x => x.TargetVariable == target); } private static double RoundToNearestStepMultiple(double value, double step) { return step * (long)Math.Round(value / step); } #endregion } }