Deep Clones
The Model.DeepClone method produces a deep clone of the selected model instance. This means that (almost) all properties, objects (Variables, Constraints, Objectives, ...) that are referenced/stored in that model are deeply cloned as well.
Clearing Relations to other Models and Solvers
As there is no rule without exception, some refinements need to be made. Internally, some objects (such as Variables) keep track of, e.g., the list of Models that they were used in. That information is used to relay information about property changes to those models (and ultimatively to the solvers that solved those models) in order to update the model representation that is loaded in the native Solver's memory.
All of those relations, except for the direct link between "cloned variables" that are used in the "cloned model" (and those of constraints, objectives, etc., respectively) are removed from the cloned model. This is done by excluding those collections from the serialization process, since the binary serializer would otherwise also create a deep clone of all models that are referenced by a variable. This would lead to a massive overhead from "shadow clones" that the user does not know about (and that he will most likely never use/see).
Another exception from this rule are the index sets that are used in the variable collections that were created for the "original model". Since the index sets can contain "complex" objects that may not necessarily implement/override the GetHashcode/Equals-methods. If these objects are deeply cloned, the user can no longeruse "his" objects as indices to for accessing cloned variable collections. Thus, the index sets, and the mapping of "existing index-combination" to "cloned variable" are restored in such a way that accessing a cloned variable collection with an index that was used on the "original" variable collection (before cloning) will yield the cloned variable that is associated to the variable in the "original" variable collection.
Obtaining Access to a "new" generically typed VariableCollection after Cloning
It is important that you do not use your old VariableCollections to access/create Variables (or Constraints, or Objectives) for the new Model.
Instead, you can make use of the GenericVariableCollection.CreateCopyFromClonedModel(clonedModel) method. This will create a new generic VariableCollection that can/should/needs to be used in place of the "original" (generic) variable collection, when working on the cloned model. This new collection will use the same generator delegates (such as the debugNameGenerator
) and index sets as the "original" variable collection. Additionally, the cloned versions of all variables that exist in the "original" collection are contained in the new collection and can be accessed with the same index objects as their "uncloned/original" counterparts.
Clarifying our Intent
Since all of this may sound confusing, we hope this snippet clarifies our intentions:
public void CloneModelAndCopyGenericCollections()
{
var cfg = new Configuration() { NameHandling = NameHandlingStyle.Manual };
this.SetupScope(cfg);
var index = Enumerable.Repeat(0, 10).Select(i => new object()).ToArray();
var k = 0;
var objToInt = index.ToDictionary(o => o, o => k++);
var originalModel = new Model();
var collection = new VariableCollection<object>(
originalModel,
index,
"xCol",
x => $"x_{objToInt[x]}",
upperBoundGenerator: x => 10);
// register original variables in more than 1 model.
var model2 = new Model();
model2.AddVariables(index.Select(i => collection[i]));
// just some random expression
var conExp = Expression.Sum(0, 10, i => (i - 3) * collection[index[i]]);
originalModel.AddConstraint(conExp <= 50, "con1");
var objExp = Expression.Sum(index.Select(i => collection[i]));
originalModel.AddObjective(new Objective(objExp, "z", ObjectiveSense.Maximize));
// clone the model and get a "new" generic collection that was cloned from "collection".
var clonedModel = originalModel.DeepClone();
var clonedCollection = collection.CreateCopyFromClonedModel(clonedModel);
foreach (var i in index)
{
// "old" index works on original + cloned VC
var oVar = collection[i];
var cVar = clonedCollection[i];
Assert.AreEqual(oVar.Name, cVar.Name);
Assert.AreNotSame(oVar, cVar);
// old variable still "knows" 2 models (originalModel + model2).
Assert.AreEqual(2, oVar.RegisteredModels.Count);
Assert.IsFalse(oVar.RegisteredModels.Contains(clonedModel));
// cloned variable only knows the clonedModel
Assert.AreEqual(1, cVar.RegisteredModels.Count);
Assert.IsTrue(cVar.RegisteredModels.Contains(clonedModel));
// the model holds a reference to the cloned variable
Assert.IsTrue(clonedModel.ContainsVariable(cVar.Name));
Assert.AreSame(cVar, clonedModel.GetVariable(cVar.Name));
}
}
Note: Since some of the accessed properties (e.g. Variable.RegisteredModels) are internal
, you cannot compile/run that snippet by yourself. Rest assured that all asserts hold. :)