using System; using System.Collections; using System.Collections.Generic; using System.Linq; using HeuristicLab.Common; using HeuristicLab.Core; using HeuristicLab.Data; using HeuristicLab.Parameters; using HeuristicLab.Persistence.Default.CompositeSerializers.Storable; using HeuristicLab.PluginInfrastructure; using System.Text; using System.Reflection; using HeuristicLab.Optimization; using HeuristicLab.Collections; using System.Drawing; namespace HeuristicLab.Problems.MetaOptimization { [StorableClass] public class ParameterConfiguration : Item, IParameterConfiguration, IStorableContent { public bool IsOptimizable { get { return true; } } [Storable] public string Filename { get; set; } [Storable] protected bool optimize; public bool Optimize { get { return optimize; } set { if (optimize != value) { optimize = value; if (optimize) { PopulateValueConfigurations(); } else { this.ValueConfigurations.Clear(); } OnOptimizeChanged(); OnToStringChanged(); } } } [Storable] private Image itemImage; public virtual 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 this.parameterDataType; } } [Storable] protected Type[] validTypes; public Type[] ValidTypes { get { return validTypes; } protected set { if (this.validTypes != value) { this.validTypes = value; } } } [Storable] protected Type valueDataType; public Type ValueDataType { get { return valueDataType; } protected set { this.valueDataType = value; } } [Storable] protected ICheckedValueConfigurationList valueConfigurations; public ICheckedValueConfigurationList ValueConfigurations { get { return this.valueConfigurations; } protected set { if (this.valueConfigurations != value) { if (this.valueConfigurations != null) DeregisterValueConfigurationEvents(); this.valueConfigurations = value; if (this.valueConfigurations != null) RegisterValueConfigurationEvents(); } } } [Storable] private int actualValueConfigurationIndex = 0; 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 (this.isNullable != value) { this.isNullable = value; } } } [Storable] protected IItemSet validValues; #region Constructors and Cloning public ParameterConfiguration(string parameterName, IValueParameter valueParameter) { this.ParameterName = parameterName; this.parameterDataType = valueParameter.GetType(); this.valueDataType = valueParameter.DataType; this.validValues = GetValidValues(valueParameter); 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() { } [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); this.ActualValue = cloner.Clone(original.ActualValue); this.optimize = original.optimize; this.actualValueConfigurationIndex = original.actualValueConfigurationIndex; this.isNullable = original.isNullable; this.itemImage = original.itemImage; if (this.valueConfigurations != null) RegisterValueConfigurationEvents(); } public override IDeepCloneable Clone(Cloner cloner) { return new ParameterConfiguration(this, cloner); } [StorableHook(HookType.AfterDeserialization)] private void AfterDeserialization() { if (this.valueConfigurations != null) RegisterValueConfigurationEvents(); } #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); } private void PopulateValueConfigurations() { foreach (Type t in this.validTypes) { if (t == typeof(NullValue)) { this.ValueConfigurations.Add(new NullValueConfiguration()); } else { IItem val; if (ActualValue.Value != null && ActualValue.ValueDataType == t) { val = (IItem)ActualValue.Value.Clone(); // use existing value for that type (if available) } else { val = CreateItem(t); } this.ValueConfigurations.Add(new ValueConfiguration(val, val.GetType()), true); } } } 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 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 if (validValues == null) return (IItem)Activator.CreateInstance(type); 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.Where(v => v.GetType() == type).SingleOrDefault(); 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); validValues.Add(val); } catch (MissingMethodException) { /* Constructor is missing, don't use those types */ } } return validValues; } private 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; } void ValueConfigurations_CheckedItemsChanged(object sender, Collections.CollectionItemsChangedEventArgs> e) { OnToStringChanged(); } void ValueConfigurations_ItemsRemoved(object sender, Collections.CollectionItemsChangedEventArgs> e) { OnToStringChanged(); } void ValueConfigurations_ItemsAdded(object sender, Collections.CollectionItemsChangedEventArgs> e) { OnToStringChanged(); } #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; } } public override string ItemDescription { get { return base.ItemDescription; } } public override string ItemName { get { return base.ItemName; } } #endregion #region Events 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); } #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 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.ActualValue.Value is IParameterizedItem) { 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) { if (parameter is IValueParameter) { IValueParameter valueParameter = parameter as IValueParameter; return new ParameterConfiguration(parameter.Name, valueParameter); } return null; } public void Parameterize(IValueParameter parameter) { if (Optimize) { if (this.ActualValue.Value is IParameterizedItem) { this.ValueConfigurations[actualValueConfigurationIndex].Parameterize((IParameterizedItem)this.ActualValue.Value); } } var clonedValue = this.ActualValue.Value != null ? (IItem)this.ActualValue.Value.Clone() : null; AdaptValidValues(parameter, clonedValue); parameter.Value = clonedValue; } /// /// 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)) { vc.Randomize(random); } } do { actualValueConfigurationIndex = random.Next(ValueConfigurations.Count()); } while (!this.ValueConfigurations.ItemChecked(this.ValueConfigurations[actualValueConfigurationIndex])); this.ActualValue = this.ValueConfigurations[actualValueConfigurationIndex].ActualValue; } } public void Mutate(IRandom random, MutateDelegate mutate, IIntValueManipulator intValueManipulator, IDoubleValueManipulator doubleValueManipulator) { if (Optimize) { foreach (var vc in this.ValueConfigurations) { if (this.ValueConfigurations.ItemChecked(vc)) { vc.Mutate(random, mutate, intValueManipulator, doubleValueManipulator); } } mutate(random, this, intValueManipulator, doubleValueManipulator); } } public void Cross(IRandom random, IOptimizable other, CrossDelegate cross, IIntValueCrossover intValueCrossover, IDoubleValueCrossover doubleValueCrossover) { if (Optimize) { IParameterConfiguration otherPc = (IParameterConfiguration)other; for (int i = 0; i < this.ValueConfigurations.Count; i++) { if (this.ValueConfigurations.ItemChecked(this.ValueConfigurations[i])) { this.ValueConfigurations[i].Cross(random, otherPc.ValueConfigurations[i], cross, intValueCrossover, doubleValueCrossover); } } cross(random, this, other, intValueCrossover, doubleValueCrossover); } } 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; } } } }