#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.Drawing; using System.Linq; using System.Threading; using Google.ProtocolBuffers; using HEAL.Attic; using HeuristicLab.Analysis; using HeuristicLab.Common; using HeuristicLab.Core; using HeuristicLab.Data; using HeuristicLab.Optimization; using HeuristicLab.Parameters; namespace HeuristicLab.Problems.ExternalEvaluation { [Item("External Evaluation Problem (single-objective)", "A problem that is evaluated in a different process.")] [Creatable(CreatableAttribute.Categories.ExternalEvaluationProblems, Priority = 100)] [StorableType("115EB3A5-A8A8-4A2E-9799-9485FE896DEC")] // BackwardsCompatibility3.3 // Rename class to SingleObjectiveExternalEvaluationProblem public class ExternalEvaluationProblem : SingleObjectiveProblem, IExternalEvaluationProblem where TEncoding : class, IEncoding where TEncodedSolution : class, IEncodedSolution { public static new Image StaticItemImage { get { return HeuristicLab.Common.Resources.VSImageLibrary.Type; } } #region Parameters public OptionalValueParameter CacheParameter { get { return (OptionalValueParameter)Parameters["Cache"]; } } public IValueParameter> ClientsParameter { get { return (IValueParameter>)Parameters["Clients"]; } } public IValueParameter MessageBuilderParameter { get { return (IValueParameter)Parameters["MessageBuilder"]; } } public IFixedValueParameter> SupportScriptParameter { get { return (IFixedValueParameter>)Parameters["SupportScript"]; } } #endregion #region Properties public new TEncoding Encoding { get { return base.Encoding; } set { base.Encoding = value; } } public EvaluationCache Cache { get { return CacheParameter.Value; } } public CheckedItemCollection Clients { get { return ClientsParameter.Value; } } public SolutionMessageBuilder MessageBuilder { get { return MessageBuilderParameter.Value; } } public SingleObjectiveOptimizationSupportScript OptimizationSupportScript { get { return SupportScriptParameter.Value; } } private ISingleObjectiveOptimizationSupport OptimizationSupport { get { return SupportScriptParameter.Value; } } #endregion [StorableConstructor] protected ExternalEvaluationProblem(StorableConstructorFlag _) : base(_) { } protected ExternalEvaluationProblem(ExternalEvaluationProblem original, Cloner cloner) : base(original, cloner) { } public override IDeepCloneable Clone(Cloner cloner) { return new ExternalEvaluationProblem(this, cloner); } public ExternalEvaluationProblem(TEncoding encoding) : base(encoding) { MaximizationParameter.ReadOnly = false; MaximizationParameter.Value = new BoolValue(); // is a read-only boolvalue in base class Parameters.Add(new OptionalValueParameter("Cache", "Cache of previously evaluated solutions.")); Parameters.Add(new ValueParameter>("Clients", "The clients that are used to communicate with the external application.", new CheckedItemCollection() { new EvaluationServiceClient() })); Parameters.Add(new ValueParameter("MessageBuilder", "The message builder that converts from HeuristicLab objects to SolutionMessage representation.", new SolutionMessageBuilder()) { Hidden = true }); Parameters.Add(new FixedValueParameter>("SupportScript", "A script that can provide neighborhood and analyze the results of the optimization.", new SingleObjectiveOptimizationSupportScript())); Operators.Add(new BestScopeSolutionAnalyzer()); } #region Single Objective Problem Overrides public override double Evaluate(TEncodedSolution solution, IRandom random) { var qualityMessage = Evaluate(BuildSolutionMessage(solution)); if (!qualityMessage.HasExtension(SingleObjectiveQualityMessage.QualityMessage_)) throw new InvalidOperationException("The received message is not a SingleObjectiveQualityMessage."); return qualityMessage.GetExtension(SingleObjectiveQualityMessage.QualityMessage_).Quality; } public virtual QualityMessage Evaluate(SolutionMessage solutionMessage) { return Cache == null ? EvaluateOnNextAvailableClient(solutionMessage) : Cache.GetValue(solutionMessage, EvaluateOnNextAvailableClient, GetQualityMessageExtensions()); } public override void Analyze(TEncodedSolution[] solutions, double[] qualities, ResultCollection results, IRandom random) { OptimizationSupport.Analyze(solutions, qualities, results, random); } public override IEnumerable GetNeighbors(TEncodedSolution solutions, IRandom random) { return OptimizationSupport.GetNeighbors(solutions, random); } #endregion public virtual ExtensionRegistry GetQualityMessageExtensions() { var extensions = ExtensionRegistry.CreateInstance(); extensions.Add(SingleObjectiveQualityMessage.QualityMessage_); return extensions; } #region Evaluation private HashSet activeClients = new HashSet(); private object clientLock = new object(); private QualityMessage EvaluateOnNextAvailableClient(SolutionMessage message) { IEvaluationServiceClient client = null; lock (clientLock) { client = Clients.CheckedItems.FirstOrDefault(c => !activeClients.Contains(c)); while (client == null && Clients.CheckedItems.Any()) { Monitor.Wait(clientLock); client = Clients.CheckedItems.FirstOrDefault(c => !activeClients.Contains(c)); } if (client != null) activeClients.Add(client); } try { return client.Evaluate(message, GetQualityMessageExtensions()); } finally { lock (clientLock) { activeClients.Remove(client); Monitor.PulseAll(clientLock); } } } private SolutionMessage BuildSolutionMessage(TEncodedSolution solution, int solutionId = 0) { lock (clientLock) { SolutionMessage.Builder protobufBuilder = SolutionMessage.CreateBuilder(); protobufBuilder.SolutionId = solutionId; var scope = new Scope(); ScopeUtil.CopyEncodedSolutionToScope(scope, Encoding, solution); foreach (var variable in scope.Variables) { try { MessageBuilder.AddToMessage(variable.Value, variable.Name, protobufBuilder); } catch (ArgumentException ex) { throw new InvalidOperationException(string.Format("ERROR while building solution message: Parameter {0} cannot be added to the message", Name), ex); } } return protobufBuilder.Build(); } } #endregion } }