wiki:UsersHowtosImplementCustomProblems

How to ... create custom problems

HeuristicLab is shipped with a set of standard Problem implementations, such as the TSP or the Knapsack Problem. See a full list of currently implemented problems on the Features page. However, you may wish to extend HeuristicLab and program your own, custom problems. This is of course possible, but requires some programming effort. On this page we try to give a step-by-step guide how to create a CustomProblem for HeuristicLab 3.3. This guide was inspired by a presentation by Stefan Wagner.

1. Create new plugins

We always strive to seperate code and presentation, therefore you should implement two plugins for each problem, one for the views and one for the code providing the actual functionality. Create two projects

  • HeuristicLab.Problems.Test-Major#-Minor# and
  • HeuristicLab.Problems.Test.Views-Major#-Minor# with a reference to HeuristicLab.MainForm.WindowsForms.

Next configure your projects as HeuristicLab plugins. If you don't know how to create HeuristicLab plugins (or don't remember) please refer to the How to ... create plugins guide.

2. Create a TestProblem class

You will need to derive your TestProblem class from the IProblem interface or one of its derived classes

  • ISingleObjectiveProblem or
  • IMultiObjectiveProblem.

IProblem

Interface to represent an optimization problem.

namespace HeuristicLab.Optimization {
  public interface IProblem : IParameterizedNamedItem {
    IParameter SolutionCreatorParameter { get; }
    ISolutionCreator SolutionCreator { get; }
    IParameter EvaluatorParameter { get; }
    IEvaluator Evaluator { get; }
    IEnumerable<IOperator> Operators { get; }

    event EventHandler SolutionCreatorChanged;
    event EventHandler EvaluatorChanged;
    event EventHandler OperatorsChanged;
    event EventHandler Reset;
  }
}

ISingleObjectiveProblem

An interface to represent a single-objective optimization problem.

namespace HeuristicLab.Optimization {
  public interface ISingleObjectiveProblem : IProblem {
    IParameter MaximizationParameter { get; }
    IParameter BestKnownQualityParameter { get; }
    new ISingleObjectiveEvaluator Evaluator { get; }
  }
}

IMultiObjectiveProblem

An interface to represent a multi-objective optimization problem.

namespace HeuristicLab.Optimization {
  public interface IMultiObjectiveProblem : IProblem {
    new IMultiObjectiveEvaluator Evaluator { get; }
  }
}

Seal the problem class if in doubt if you should seal it or not.

3. Problem parameters

  • All data elements of a problem should be found in the parameters collection.
  • For easier access, create parameter properties for each parameter (only Getter).
  • For Value Parameters you should also implement properties to directly access the value.

4. Wiring

To correctly parameterize a problem and make it work in conjunction with different algorithms, you need to register a couple of events and update operators. This is a process that we call wiring. Wiring is a bit complicated to explain. We suggest to model your problem after the TSP Problem and go from there. A few remarks, though:

  • Every problem must provide an IEnumerable<IOperator> Operators with all problem or encoding specific operators. The algorithm can retrieve required operators from the [Storable] operators list. Don't forget to create and instantiate the operators collection.
  • Have a look at the Paramterize* methods in TSP. These methods ensure that actual parameter names are properly matched between problem and algorithm etc.
  • There's usually just one dedicated workflow: Change a name of a parameter in the solution creator --> Parameterize*-mathods ensure that e.g. crossover still works. The other direction is not really necessary. (Zitat: "Sonst wird ma do zum Schwammerl")
  • Wiring is only done in the default constructor. Deserialization does not use the default constructor, however. To ensure that your wiring does not break after serializing and deserializing, use the AfterDeserialization hook and call your Paramterize*-methods.

5. Remarks

  • Don't forget to write a default constructor.
  • The StorableConstructor should be implemented in every Item, usually as an empty method. It may be private in sealed classes (but will throw compiler warnings), otherwise implement it as a private method. It must call base(deserializing) (!)
  • Correctly implement cloning. Every HeuristicLab item must be deep-clonable, so overwrite the Clone-method in every Item. Ensure that all enclosed entities are correctly cloned. There are two possible ways to implement cloning correctly:
    • Call base.clone(clone) to get the cloned object created with the default constructor. Downside: SLOW, so avoid if you need to clone frequently!
    • Variant 2: Use new to create a new object. But attention: You will need to call Cloner.RegisterClonedObject to tell cloner that you have now created a clone.
  • If class behaves strangely take a look at the cloning mechanism. Cloning happens all the time, even when you wouldn't expect it (say: on saving).