Show / Hide Table of Contents

    Using the ModelScope

    The ModelScope provides a context for all Models, Variables/VariableCollections, Constraints, Objectives, etc. Within a ModelScope, all automatically generated identifiers will be unique. Additionally, the basic behavior of different Models and Solvers will be the same, since a ModelScope defines the fundamental rule set for automatizations, replacements, transformations, and more.
    If the user does not explicitly create a new ModelScope(), a default instance will be instanciated when one of the OPTANO.Modeling classes needs access to it. It is strongly recommended to define a ModelScope within a using-block.

    Important

    There can only exist a single ModelScope at one point of time. If you create your Model before creating a new ModelScope, you'll lose the ability to define and configure your scope. Trying to create a second ModelScope will result in a InvalidOperationException.

    Best Practice

    The best practice of using a ModelScope is to define it within a using-block. The model will use it and it is disposed afterwards.

    using (var scope = new ModelScope(someOptionalConfig)) 
    {
        var model = new Model();
        // Populate the Model
        // ...
    
        // Solve the Model
        var solution = solver.Solve(model);
    
        // Do something with and/or return the Solution
        // ...
        return solution;
    }
    

    Configuring the ModelScope

    The following section describes the different options that can be set via the ModelScope's Configuration. In order to keep Models (and their internal states) well-defined, most of the ModelScope's properties cannot be changed after it was created.

    Model Properties/Behavior

    With the ModelScope, you can control some fundamental behavior of your models.

    • ModelScope.ModelBehavior
      • In Automatic mode, the model takes care of adding variables to the model when constraints and objectives get added.
        • This is the default.
      • In Manual mode you need to take care of this yourself.
    • ModelScope.EPSILON
      • Defines the tolerance below which two (double-) values are considered to be equal.
      • Default is 1e-6.
    • ModelScope.NameHandling
      • Specifies how your Variable-, Constraint-, and Objective-Names will be generated/extended.
      • During implementation (and debugging), we recommend using NameHandlingStyle.UniqueLongNames.
        • Invokes the user-defined DebugNameGenerators in VariableCollections.
        • Appends a base64-index to the generated names to make sure they're unique.
      • In order to reduce memory overhead, you can activate NameHandlingStyle.UniqueShortNames.
        • Default setting.
        • Names will consist of a base64-index.
        • Hard to read for humans, but recommended in operational mode.
        • More Information can be found here.
      • If you want full control (and responsibility) over all names, you can use NameHandlingStyle.Manual.
        • Helpful, if you need to work with fixed, predefined names.
        • Keep in mind: "With great power comes great responsibility.":
          If names are not unique, bad things can and will happen!
    • Control the automatic update of your Variable.Values via ModelScope.CopySolutionToModel.
      • true (default): After every call to Solve() the solution values will be written to the model's variables (if a solution has been found).
      • false: Do not copy automatically, user code will /needs to do it explicitly.
    • Deflate your Model by working with ModelScope.ComputeRemovedVariables.
      • true: Finds variables which have been removed by the solver during pre-processing.
        • Supports model-tuning by giving advice / helps finding obsolete variables.
      • false (default): Does not compute removed variables (saves memory and cpu time).
      • The information is used within the Model.VariableStatistics report.
        • Alternatively, the set of removed variables can be accessed with Model.VariablesRemovedDuringPreProcessing.
        • An example for this is presented here.

    Solver Behavior

    OPTANO.Modeling provides access to a great variety of Solvers, each of which supporting a different set of features. In order to offer a pleasant user experience, we try to compensate the feature gaps by emulating more advanced features that are not available for all solvers. By default, a solver utilizes its native capabilities when solving a model. However, it is possible to override the native with the emulated behavior.
    For example, the GurobiSolver offers native support for MultiObjective optimization. If you want to override this, and always use the emulated method you can specify this in the ModelScope's Configuration. Let's say that you're satisfied with Gurobi's implementation of weighted Objectives, but you prefer the emulated behavior of multi-hierarchical Objectives:

    var customSettings = new Configuration()
        {
            // emulate multi-hierarchical objectives only:
            NativeMultiObjectiveSupport = NativeMultiObjectiveSupport.WeightedObjectives,
            // use long names
            NameHandling = NameHandlingStyle.UniqueLongNames,
            // ...
        };
    using (var scope = new ModelScope(customSettings))
    {
        // proceed as usual
    }
    

    Internally, we make sure to use the most advanced mode that is supported by a specific Solver-implementation. You do not need to worry about setting the Configuration.NativeMultiObjectiveSupport back to unsupported (or null) when switching Solvers.

    Similar things can be done with settings for dealing with Special Ordered Sets, advanced OperatorExpressions, such as Floor, Max, etc., and more.


    Dirty (never-to-be-used) Hacks

    AT YOUR OWN RISK

    Some legacy code requires a ModelScope to be defined at some time in the code and create models somewhere else. A using-block might be inappropriate in this case.
    (Although, this might be a sign of a bad architecture...)

    Warning:

    The following code might have side effects, when the ModelScope is created at a bad time. ModelScope is not thread safe. Neither is Model, Variable, VariableCollection, ...

    Workaround

    If you're still reading, we probably cannot convince you to not do this. Anyways, here we go:

    1. Set up the scope somewhere in your code (f.e. in the main-class)

      {
          // my ModelScope block, Variable 'scope' will be unknown outside of this block.
          // change some scope settings if required
          var scope = new ModelScope(scopeSettings);
      }
      
    2. Create a new ModelScope somewhere else without access to the scope variable that we set initially.

      // different time, different place --> renew your ModelScope
      var field = typeof(ModelScope).GetRuntimeProperties().Single(rp => rp.Name == "Current");
      ModelScope reflectedScope = (ModelScope)field.GetValue(null);
      if (reflectedScope != null)
      {
          // dispose registered ModelScope
          reflectedScope.Dispose();
      }
      // create a new ModelScope, it will register with ModelScope.Current itself
      var scope = new ModelScope(scopeSettings);
      

    Keep in mind, that this will reset all internal counters. Unless you work with NameHandlingStyle.Manual, this may cause name collisions and all sorts of other undefined behavior.

    Back to top Copyright © OPTANO GmbH generated with DocFX
    Privacy Policy | Impressum – Legal Notice