#region License Information /* HeuristicLab * Copyright (C) 2002-2012 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; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; using HeuristicLab.Collections; using HeuristicLab.Common; using HeuristicLab.Core; using HeuristicLab.Data; using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding; using HeuristicLab.Parameters; using HeuristicLab.Persistence.Default.CompositeSerializers.Storable; using HeuristicLab.PluginInfrastructure; namespace HeuristicLab.Encodings.ParameterConfigurationEncoding { [StorableClass] public class ParameterConfiguration : Item, IParameterConfiguration, IStorableContent { public bool IsOptimizable { get { return true; } } public string Filename { get; set; } [Storable] protected bool optimize; public virtual bool Optimize { get { return optimize; } set { if (optimize != value) { optimize = value; if (optimize) { PopulateValueConfigurations(); } else { ClearValueConfigurations(); } OnOptimizeChanged(); OnCombinationsCountChanged(); OnToStringChanged(); } } } [Storable] protected long combinationsCount; public virtual long CombinationsCount { get { return combinationsCount; } set { if (combinationsCount != value) { combinationsCount = value; OnCombinationsCountChanged(); } } } [Storable] protected Image itemImage; public override Image ItemImage { get { return itemImage ?? base.ItemImage; } } [Storable] protected string parameterName; public string ParameterName { get { return parameterName; } set { if (parameterName != value) { parameterName = value; OnToStringChanged(); } } } [Storable] protected Type parameterDataType; public Type ParameterDataType { get { return parameterDataType; } } protected Type[] validTypes; public Type[] ValidTypes { get { return validTypes; } protected set { if (validTypes != value) { validTypes = value; } } } [Storable] protected Type valueDataType; public Type ValueDataType { get { return valueDataType; } protected set { valueDataType = value; } } [Storable] protected ICheckedValueConfigurationList valueConfigurations; public ICheckedValueConfigurationList ValueConfigurations { get { return valueConfigurations; } protected set { if (valueConfigurations != value) { if (valueConfigurations != null) DeregisterValueConfigurationEvents(); valueConfigurations = value; KeepActualValueConfigurationIndexConsistent(); if (valueConfigurations != null) RegisterValueConfigurationEvents(); } } } [Storable] private int actualValueConfigurationIndex; public int ActualValueConfigurationIndex { get { return actualValueConfigurationIndex; } set { actualValueConfigurationIndex = value; } } [Storable] protected ConstrainedValue actualValue; public ConstrainedValue ActualValue { get { return actualValue; } set { if (actualValue != value) { DeregisterActualValueEvents(); actualValue = value; RegisterActualValueEvents(); } } } [Storable] protected bool isNullable; public bool IsNullable { get { return isNullable; } protected set { if (isNullable != value) { isNullable = value; } } } [Storable] protected bool discoverValidValues; public bool DiscoverValidValues { get { return discoverValidValues; } set { discoverValidValues = value; } } protected IItemSet validValues; [Storable] private bool autoPopulateValueConfigurations = true; public bool AutoPopulateValueConfigurations { get { return autoPopulateValueConfigurations; } set { autoPopulateValueConfigurations = value; } } [Storable] protected bool valuesReadOnly = false; public virtual bool ValuesReadOnly { get { return valuesReadOnly; } set { if (valuesReadOnly != value) { valuesReadOnly = value; foreach (var vc in valueConfigurations) { vc.ValuesReadOnly = value; } } } } #region Constructors and Cloning public ParameterConfiguration(string parameterName, IValueParameter valueParameter, bool discoverValidValues) : base() { this.AutoPopulateValueConfigurations = true; this.ParameterName = parameterName; this.parameterDataType = valueParameter.GetType(); this.valueDataType = valueParameter.DataType; this.discoverValidValues = discoverValidValues; this.validValues = discoverValidValues ? GetValidValues(valueParameter) : new ItemSet { valueParameter.Value }; this.validTypes = GetValidTypes(valueParameter).ToArray(); this.IsNullable = valueParameter.ItemName.StartsWith("Optional"); this.itemImage = valueParameter.ItemImage; if (IsNullable) { validTypes = new List(validTypes) { typeof(NullValue) }.ToArray(); } this.ValueConfigurations = new CheckedValueConfigurationList(this.validValues ?? CreateValidValues()); this.ActualValue = new ConstrainedValue( valueParameter.Value != null ? valueParameter.Value : null, // don't clone here; otherwise settings of a non-optimized ValueParameter will not be synchronized with the ConstrainedValue valueParameter.DataType, this.validValues != null ? new ItemSet(this.validValues) : CreateValidValues(), this.IsNullable); if (Optimize) { PopulateValueConfigurations(); } } public ParameterConfiguration(string parameterName, Type type, IItem actualValue, IEnumerable valueConfigurations) : base() { this.AutoPopulateValueConfigurations = false; this.ParameterName = parameterName; this.parameterDataType = type; this.valueDataType = type; this.discoverValidValues = false; this.validValues = null; // maybe use values from valueConfigurations this.validTypes = new Type[] { type }; this.IsNullable = false; this.itemImage = valueConfigurations.Any() ? valueConfigurations.First().ItemImage : null; this.ValueConfigurations = new CheckedValueConfigurationList(valueConfigurations); this.ActualValue = new ConstrainedValue(actualValue, type, CreateValidValues(), this.IsNullable); } public ParameterConfiguration(string parameterName, Type type, IItem actualValue) : base() { this.AutoPopulateValueConfigurations = true; this.ParameterName = parameterName; this.parameterDataType = type; this.valueDataType = type; this.discoverValidValues = false; this.validValues = null; this.validTypes = new Type[] { type }; this.IsNullable = false; this.itemImage = actualValue.ItemImage; this.ValueConfigurations = new CheckedValueConfigurationList(); this.ActualValue = new ConstrainedValue(actualValue, type, CreateValidValues(), this.IsNullable); if (Optimize) { PopulateValueConfigurations(); } } protected ParameterConfiguration() : base() { } [StorableConstructor] protected ParameterConfiguration(bool deserializing) { } protected ParameterConfiguration(ParameterConfiguration original, Cloner cloner) : base(original, cloner) { this.parameterName = original.ParameterName; this.parameterDataType = original.parameterDataType; this.valueDataType = original.ValueDataType; this.validValues = cloner.Clone(original.validValues); this.validTypes = original.validTypes.ToArray(); this.valueConfigurations = cloner.Clone(original.ValueConfigurations); if (this.valueConfigurations != null) RegisterValueConfigurationEvents(); this.actualValue = cloner.Clone(original.actualValue); if (this.actualValue != null) RegisterActualValueEvents(); this.optimize = original.optimize; this.actualValueConfigurationIndex = original.actualValueConfigurationIndex; this.isNullable = original.isNullable; this.itemImage = original.itemImage; this.discoverValidValues = original.discoverValidValues; this.AutoPopulateValueConfigurations = original.AutoPopulateValueConfigurations; this.valuesReadOnly = original.valuesReadOnly; } public override IDeepCloneable Clone(Cloner cloner) { return new ParameterConfiguration(this, cloner); } [StorableHook(HookType.AfterDeserialization)] protected virtual void AfterDeserialization() { this.validTypes = GetValidTypes(parameterDataType).ToArray(); if (IsNullable) { validTypes = new List(validTypes) { typeof(NullValue) }.ToArray(); } this.validValues = CreateValidValues(); if (this.valueConfigurations != null) RegisterValueConfigurationEvents(); if (this.actualValue != null) RegisterActualValueEvents(); } #endregion private void RegisterValueConfigurationEvents() { this.ValueConfigurations.CheckedItemsChanged += new CollectionItemsChangedEventHandler>(ValueConfigurations_CheckedItemsChanged); this.ValueConfigurations.ItemsAdded += new CollectionItemsChangedEventHandler>(ValueConfigurations_ItemsAdded); this.ValueConfigurations.ItemsRemoved += new CollectionItemsChangedEventHandler>(ValueConfigurations_ItemsRemoved); } private void DeregisterValueConfigurationEvents() { this.ValueConfigurations.CheckedItemsChanged -= new CollectionItemsChangedEventHandler>(ValueConfigurations_CheckedItemsChanged); this.ValueConfigurations.ItemsAdded -= new CollectionItemsChangedEventHandler>(ValueConfigurations_ItemsAdded); this.ValueConfigurations.ItemsRemoved -= new CollectionItemsChangedEventHandler>(ValueConfigurations_ItemsRemoved); } private void RegisterActualValueEvents() { if (this.ActualValue != null) this.ActualValue.ToStringChanged += new EventHandler(ActualValue_ToStringChanged); } private void DeregisterActualValueEvents() { if (this.ActualValue != null) this.ActualValue.ToStringChanged -= new EventHandler(ActualValue_ToStringChanged); } protected virtual void PopulateValueConfigurations() { if (!this.AutoPopulateValueConfigurations) return; foreach (Type t in this.validTypes) { if (t == typeof(NullValue)) { this.ValueConfigurations.Add(new NullValueConfiguration()); } else { IItem val; if (ActualValue.Value != null && ActualValue.Value.GetType() == t) { val = (IItem)ActualValue.Value.Clone(); // use existing value for that type (if available) } else { val = CreateItem(t); } if (val != null) { // val can be null when ValidValues does not contain the type (this can happen when discoverValidValues=false) IValueConfiguration valueConfiguration; if (val is IParameterizedItem) { valueConfiguration = new ParameterizedValueConfiguration(val, val.GetType(), true); } else { if (val is ISymbolicExpressionGrammar) { valueConfiguration = new SymbolicExpressionGrammarValueConfiguration((ISymbolicExpressionGrammar)val); } else { valueConfiguration = new RangeValueConfiguration(val, val.GetType()); } } valueConfiguration.CombinationsCountChanged += (sender, args) => OnCombinationsCountChanged(); this.ValueConfigurations.Add(valueConfiguration, true); } } } } protected virtual void ClearValueConfigurations() { if (!this.AutoPopulateValueConfigurations) return; this.ValueConfigurations.Clear(); } private static IEnumerable GetValidTypes(IValueParameter parameter) { // in case of IOperator return empty list, otherwise hundreds of types would be returned. this mostly occurs for Successor which will never be optimized if (parameter.DataType == typeof(IOperator)) return new List(); // return only one type for ValueTypeValues<> (Int, Double, Percent, Bool). Otherwise 2 types would be returned for DoubleValue (PercentValue which is derived) if (IsSubclassOfRawGeneric(typeof(ValueTypeValue<>), parameter.DataType)) return new List { parameter.DataType }; if (IsSubclassOfRawGeneric(typeof(OptionalConstrainedValueParameter<>), parameter.GetType())) { // use existing validvalues if known var parameterValidValues = (IEnumerable)parameter.GetType().GetProperty("ValidValues").GetValue(parameter, new object[] { }); return parameterValidValues.Cast().Select(x => x.GetType()); } else { // otherwise use all assignable types return ApplicationManager.Manager.GetTypes(parameter.DataType, true); } } private static IEnumerable GetValidTypes(Type parameter) { // in case of IOperator return empty list, otherwise hundreds of types would be returned. this mostly occurs for Successor which will never be optimized if (parameter == typeof(IOperator)) return new List(); // return only one type for ValueTypeValues<> (Int, Double, Percent, Bool). Otherwise 2 types would be returned for DoubleValue (PercentValue which is derived) if (IsSubclassOfRawGeneric(typeof(ValueTypeValue<>), parameter)) return new List { parameter }; // TODO: check if this makes sense; maybe only leave else branch here if (IsSubclassOfRawGeneric(typeof(OptionalConstrainedValueParameter<>), parameter.GetType())) { // use existing validvalues if known var parameterValidValues = (IEnumerable)parameter.GetType().GetProperty("ValidValues").GetValue(parameter, new object[] { }); return parameterValidValues.Cast().Select(x => x.GetType()); } else { // otherwise use all assignable types return ApplicationManager.Manager.GetTypes(parameter, true); } } private IItemSet GetValidValues(IValueParameter parameter) { if (IsSubclassOfRawGeneric(typeof(OptionalConstrainedValueParameter<>), parameter.GetType())) { var x = (IEnumerable)parameter.GetType().GetProperty("ValidValues").GetValue(parameter, new object[] { }); return new ItemSet(x.Cast().Select(y => (IItem)y.Clone())); // cloning actually saves memory, because references to event-subscribers are removed } else { return null; } } public IItem CreateItem(Type type) { // no valid values; just instantiate try { if (validValues == null) return (IItem)Activator.CreateInstance(type); } catch (MissingMemberException) { return null; // can happen, when ApplicationManager.Manager.GetTypes(type, OnlyInstantiable=true) returns objects which have no default constructor } if (type == typeof(NullValue)) return new NullValue(); // copy value from ValidValues; this ensures that previously set ActualNames for a type are kept IItem value = this.validValues.SingleOrDefault(v => v.GetType() == type); if (value != null) return value; return null; } private IItemSet CreateValidValues() { var validValues = new ItemSet(); foreach (Type t in this.validTypes) { try { var val = CreateItem(t); if (val != null) validValues.Add(val); } catch (MissingMethodException) { /* Constructor is missing, don't use those types */ } } return validValues; } public static bool IsSubclassOfRawGeneric(Type generic, Type toCheck) { while (toCheck != typeof(object)) { var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck; if (generic == cur) { return true; } toCheck = toCheck.BaseType; // baseType is null when toCheck is an Interface if (toCheck == null) return false; } return false; } private void ValueConfigurations_CheckedItemsChanged(object sender, Collections.CollectionItemsChangedEventArgs> e) { OnToStringChanged(); OnCombinationsCountChanged(); KeepActualValueConfigurationIndexConsistent(); } private void ValueConfigurations_ItemsRemoved(object sender, Collections.CollectionItemsChangedEventArgs> e) { OnToStringChanged(); OnCombinationsCountChanged(); KeepActualValueConfigurationIndexConsistent(); } private void ValueConfigurations_ItemsAdded(object sender, Collections.CollectionItemsChangedEventArgs> e) { OnToStringChanged(); OnCombinationsCountChanged(); KeepActualValueConfigurationIndexConsistent(); } #region INamedItem Properties public virtual string Name { get { return ParameterName; } set { throw new NotSupportedException(); } } public virtual string Description { get { return base.ItemDescription; } set { throw new NotSupportedException(); } } public virtual bool CanChangeDescription { get { return false; } } public virtual bool CanChangeName { get { return false; } } #endregion #region Events protected virtual void ActualValue_ToStringChanged(object sender, EventArgs e) { OnToStringChanged(); } #endregion #region Event Handler public virtual event EventHandler NameChanged; protected virtual void OnNameChanged(object sender, EventArgs e) { var handler = NameChanged; if (handler != null) handler(sender, e); } public virtual event EventHandler> NameChanging; protected virtual void OnNameChanging(object sender, CancelEventArgs e) { var handler = NameChanging; if (handler != null) handler(sender, e); } public virtual event EventHandler DescriptionChanged; protected virtual void OnDescriptionChanged(object sender, EventArgs e) { var handler = DescriptionChanged; if (handler != null) handler(sender, e); } public virtual event EventHandler IsOptimizableChanged; private void OnIsOptimizableChanged() { var handler = IsOptimizableChanged; if (handler != null) handler(this, EventArgs.Empty); } public virtual event EventHandler OptimizeChanged; protected virtual void OnOptimizeChanged() { var handler = OptimizeChanged; if (handler != null) handler(this, EventArgs.Empty); } public event EventHandler CombinationsCountChanged; protected void OnCombinationsCountChanged() { var handler = CombinationsCountChanged; if (handler != null) handler(this, EventArgs.Empty); } #endregion public override string ToString() { if (Optimize) { return string.Format("{0}: (Optimize: {1})", ParameterName, ValueConfigurations.CheckedItems.Count()); } else { return string.Format("{0}: {1}", ParameterName, ActualValue.Value); } } public virtual string ParameterInfoString { get { StringBuilder sb = new StringBuilder(); if (this.Optimize) { var vc = this.ValueConfigurations[actualValueConfigurationIndex]; if (vc.ActualValue == null || vc.ActualValue.Value == null) { sb.Append(string.Format("{0}: null", parameterName)); } else if (IsSubclassOfRawGeneric(typeof(ValueTypeValue<>), vc.ActualValue.Value.GetType())) { // for int, double, bool use the value directly sb.Append(string.Format("{0}: {1}", parameterName, this.ActualValue.Value.ToString())); } else { // for other types use NumberedName (this also uses the Number-Property for otherwise ambiguous ValueConfigurations) sb.Append(string.Format("{0}: {1}", parameterName, vc.NumberedName)); } if (this.ValueConfigurations[actualValueConfigurationIndex] is ParameterizedValueConfiguration) { string subParams = this.ValueConfigurations[actualValueConfigurationIndex].ParameterInfoString; if (!string.IsNullOrEmpty(subParams)) { sb.Append(" ("); sb.Append(subParams); sb.Append(")"); } } } return sb.ToString(); } } public static IParameterConfiguration Create(IParameterizedNamedItem parent, IParameter parameter, bool discoverValidValues) { if (parameter is IValueParameter) { IValueParameter valueParameter = parameter as IValueParameter; return new ParameterConfiguration(parameter.Name, valueParameter, discoverValidValues); } return null; } public void Parameterize(IValueParameter parameter) { if (!Optimize && ValuesReadOnly) return; if (Optimize) { if (this.ActualValue.Value is IParameterizedItem) { ((ParameterizedValueConfiguration)this.ValueConfigurations[actualValueConfigurationIndex]).Parameterize((IParameterizedItem)this.ActualValue.Value); } if (this.ActualValue.Value is ISymbolicExpressionGrammar) { ((SymbolicExpressionGrammarValueConfiguration)this.ValueConfigurations[actualValueConfigurationIndex]).Parameterize((ISymbolicExpressionGrammar)this.ActualValue.Value); } } var clonedValue = this.ActualValue.Value != null ? (IItem)this.ActualValue.Value.Clone() : null; if (clonedValue != null) AdaptValidValues(parameter, clonedValue); if (!(parameter is IFixedValueParameter)) { parameter.Value = clonedValue; } else { var original = (dynamic)parameter.Value; var clone = (dynamic)clonedValue; if (typeof(ValueTypeValue<>).IsAssignableFrom(parameter.DataType) && !original.ReadOnly) { original.Value = clone.Value; } else if (typeof(ValueTypeArray<>).IsAssignableFrom(parameter.DataType) && !original.ReadOnly) { for (int i = 0; i < original.Length; i++) original[i] = clone[i]; } else if (typeof(ValueTypeMatrix<>).IsAssignableFrom(parameter.DataType) && !original.ReadOnly) { for (int i = 0; i < original.Rows; i++) for (int j = 0; j < original.Columns; j++) original[i, j] = clone[i, j]; } // StringConvertibleValueTuple<,> not supported } } /// /// Adds value to the ValidValues of the parameter if they don't contain the value /// private void AdaptValidValues(IValueParameter parameter, IItem value) { // this requires some tricky reflection, because the type is unknown at runtime so parameter.ValidValues cannot be casted to Collection if (IsSubclassOfRawGeneric(typeof(OptionalConstrainedValueParameter<>), parameter.GetType())) { var validValues = parameter.GetType().GetProperty("ValidValues").GetValue(parameter, new object[] { }); Type validValuesType = validValues.GetType(); var containsMethod = validValuesType.GetMethod("Contains"); if (!(bool)containsMethod.Invoke(validValues, new object[] { value })) { var addMethod = validValuesType.GetMethod("Add"); addMethod.Invoke(validValues, new object[] { value }); } } } public void Randomize(IRandom random) { if (Optimize) { foreach (var vc in this.ValueConfigurations) { if (this.ValueConfigurations.ItemChecked(vc)) { if (vc.Optimize) vc.Randomize(random); } } do { actualValueConfigurationIndex = random.Next(ValueConfigurations.Count()); } while (!this.ValueConfigurations.ItemChecked(this.ValueConfigurations[actualValueConfigurationIndex])); this.ActualValue = this.ValueConfigurations[actualValueConfigurationIndex].ActualValue; } } public void UpdateActualValueIndexToItem(IValueConfiguration vc) { for (int i = 0; i < this.ValueConfigurations.Count(); i++) { if (this.ValueConfigurations.ElementAt(i) == vc) { this.actualValueConfigurationIndex = i; return; } } } public List GetAllOptimizables() { var list = new List(); foreach (var vc in ValueConfigurations) { if (vc.Optimize) { list.Add(vc); list.AddRange(vc.GetAllOptimizables()); } } return list; } public void CollectOptimizedParameterNames(List parameterNames, string prefix) { foreach (var vc in ValueConfigurations) { if (vc.Optimize) { vc.CollectOptimizedParameterNames(parameterNames, prefix); } } } public double CalculateSimilarity(IOptimizable optimizable) { var other = (IParameterConfiguration)optimizable; if (this.ActualValueConfigurationIndex == other.ActualValueConfigurationIndex) { return this.ValueConfigurations[this.ActualValueConfigurationIndex].CalculateSimilarity(other.ValueConfigurations[other.ActualValueConfigurationIndex]); } else { return 0.0; } } /// /// Ensures that the ActualValueConfigurationIndex has a valid value /// Only indices of items which are checked are allowed /// private void KeepActualValueConfigurationIndexConsistent() { if (this.valueConfigurations != null && this.valueConfigurations.CheckedItems.Count() > 0) { if (this.valueConfigurations.Count <= this.actualValueConfigurationIndex && this.valueConfigurations.CheckedItems.Count(x => x.Index == this.actualValueConfigurationIndex) == 1) { // everything is ok; do nothing } else { // current index is invalid, set new one this.ActualValueConfigurationIndex = this.valueConfigurations.CheckedItems.First().Index; } } else { // no checked valueConfiguration is available; cannot be used this.ActualValueConfigurationIndex = -1; } } } }