Posts in category ExternalEvaluation

External Evaluation with Programmable Problems

The ExternalEvaluationProblem in HeuristicLab can be used when the fitness calculation has to be done by a separate process. Setting up such a problem is not difficult and covered in a tutorial. However, the external evaluation problem due to its simplicity is limited in what you can do. You don't have so much control on how you fill the SolutionMessage and you also can do less with the QualityMessage that you obtain in return, e.g. regarding custom extensions.

If you need more freedom use can also use the programmable problem in HeuristicLab to do external evaluations. In the following code snippet the programmable problem's evaluate attempts to use an external client to do the evaluation. It expects that there is a process running at localhost and listening on port 2112 for solution messages.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using HeuristicLab.Common;
using HeuristicLab.Collections;
using HeuristicLab.Core;
using HeuristicLab.Data;
using HeuristicLab.Encodings.BinaryVectorEncoding;
using HeuristicLab.Optimization;
using HeuristicLab.Problems.Programmable;
using Google.ProtocolBuffers;
using HeuristicLab.Problems.ExternalEvaluation;

namespace HeuristicLab.Problems.Custom {
  public class ExternalKnapsack : CompiledProblemDefinition, ISingleObjectiveProblemDefinition {
    public bool Maximization { get { return true; } }

    public override void Initialize() {
      Encoding = new BinaryVectorEncoding("kp", length: ((IntArray)vars.weights).Length);
      vars.cache = new EvaluationCache();
      var clients = new CheckedItemCollection<IEvaluationServiceClient>();
      var client = new EvaluationServiceClient();
      client.ChannelParameter.Value = new EvaluationTCPChannel("127.0.0.1", 2112);
      clients.Add(client);
      vars.clients = clients;
      
      vars.weights = new IntArray(new [] { 2, 3, 7, 5, 9, 7, 6, 5, 7, 3, 2, 1, 5 });
      vars.values = new IntArray(new [] { 2, 9, 5, 1, 9, 4, 5, 7, 8, 5, 9, 3, 7 });
      vars.maxWeight = new IntValue(43);
      vars.messageBuilder = new SolutionMessageBuilder();
    }

    public double Evaluate(Individual individual, IRandom random) {
      var solutionMessage = BuildSolutionMessage(individual);
      
      var cache = (EvaluationCache)vars.cache;
      return (cache == null
        ? EvaluateOnNextAvailableClient(solutionMessage)
        : cache.GetValue(solutionMessage, EvaluateOnNextAvailableClient,
            GetQualityMessageExtensions()))
        .GetExtension(SingleObjectiveQualityMessage.QualityMessage_).Quality;
    }
    
    private HashSet<IEvaluationServiceClient> activeClients =
      new HashSet<IEvaluationServiceClient>();
    private object clientLock = new object();
    
    public virtual ExtensionRegistry GetQualityMessageExtensions() {
      var extensions = ExtensionRegistry.CreateInstance();
      extensions.Add(SingleObjectiveQualityMessage.QualityMessage_);
      return extensions;
    }
    
    private QualityMessage EvaluateOnNextAvailableClient(SolutionMessage message) {
      var clients = (CheckedItemCollection<IEvaluationServiceClient>)vars.clients;
      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(Individual individual,
        int solutionId = 0) {
      var messageBuilder = (SolutionMessageBuilder)vars.messageBuilder;
      lock (clientLock) {
        SolutionMessage.Builder protobufBuilder = SolutionMessage.CreateBuilder();
        protobufBuilder.SolutionId = solutionId;
        foreach (var variable in individual.Values) {
          try {
            messageBuilder.AddToMessage(variable.Value, variable.Key, protobufBuilder);
          }
          catch (ArgumentException ex) {
            throw new InvalidOperationException(
string.Format(@"ERROR while building solution message:
Parameter {0} cannot be added to the message", variable.Key), ex);
          }
        }
        return protobufBuilder.Build();
      }
    }
    
    
    public void Analyze(Individual[] individuals, double[] qualities,
        ResultCollection results, IRandom random) {
      // ...
    }

    public IEnumerable<Individual> GetNeighbors(Individual individual, IRandom random) {
      // ...
      yield break;
    }
  }
}

Please bear in mind, that you have to use the ParallelEngine in your algorithms to make use of multiple clients at the same time. Any code that you add to Evaluate thus needs to be thread-safe.