Posts for the month of June 2017

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.

Update: Problems with the Windows 10 Creators Update

The issue related to the bluescreen on Windows 10 Creators Update is caused by a high nesting of controls (stackoverflow, microsoft support). Previously, exceeding the maximum nesting level was rarely encountered and a warning was issued in the worst case. Since the Windows 10 Creators Update, the maximum nesting level is much lower and exceeding the maximum immediately results in a bluescreen.

To avoid exceeding the maximum nesting level, HeuristicLab can intercept further nesting, by opening a new Tab instead, as soon as a defined nesting threshold is reached. This threshold can be changed by selecting View -> Change Nesting Level... in the menu bar of HeuristicLab (implemented in #2788). For users that have the Windows 10 Creators Update installed, we recommend a maximum nesting of 25. Additionally, HeuristicLab now detects whether the Creators Update is installed and issues a warning if the nesting threshold is too high.

To enable the user-configurable nesting threshold, please download the latest HeuristicLab version (HeuristicLab stable branch binaries).