On fixing CPLEX variable names generated by OPL

You wrote your OPL model for your important mathematical programming problem. But the result looks weird: the model just cannot produce such solution. The most desperate approach consists in diving though the mathematical programming model generated by OPL, checking each (in)equation one by one.

Here I describe some issues that happened while using the OPL Java interface to create the CPLEX model and to print out the mathematical programming model as a .lp file. And after days of frustration with this .lp printout, I found myself writing my own Java class that renames all CPLEX variables generated by OPL…

The printout does not look that bad. OPL really does a good job: variable names are suffixed with numbers that match tuples used to index the variable. But wait, all other variables names are suddenly suffixed with number combination that are not the tuples anymore, but some information how OPL stores and represents the tuples internally. To bad, the printout is of no use anymore.

Instead of something sensible like:

Minimize
 obj: 0.5 varNoEnvio({0,0})(0) + 0.5 varNoEnvio({0,0})(1)
 + 0.5 varNoEnvio({0,0})(2) + 0.5 varNoEnvio({0,1})(0)
 + 0.5 varNoEnvio({0,1})(1) + 0.5 varNoEnvio({0,1})(2)
 + 0.5 varNoEnvio({1,0})(0) + 0.5 varNoEnvio({1,0})(1)
 + 0.5 varNoEnvio({1,0})(2) + 0.5 varNoEnvio({1,1})(0)
 + 0.5 varNoEnvio({1,1})(1) + 0.5 varNoEnvio({1,1})(2)</pre>
I got something misleading like:
<pre>Minimize
 obj: 0.5 varNoEnvio#0#0 + 0.5 varNoEnvio#0#1 + 0.5 varNoEnvio#1#0
 + 0.5 varNoEnvio#1#1 + 0.5 varNoEnvio#2#0 + 0.5 varNoEnvio#2#1
 + 0.5 varNoEnvio#3#0 + 0.5 varNoEnvio#3#1 + 0.5 varNoEnvio#4#0
 + 0.5 varNoEnvio#4#1 + 0.5 varNoEnvio#5#0 + 0.5 varNoEnvio#5#1

Really not easy to remember that #3#0 maps to the tuple, say, ({1,0})(0).

I could not yet understand what causes OPL to change how variable names are written to the printout. I suspect it could be related to decision variables involved in decision expressions.

Therefore, I have written my own CPLEX variables renamer. It is not complete, I only implemented the most important combination of variables declarations and left the other cases as an exercise for the reader. I would appreciate if you could send me back the missing combination.

At the first glance, the renamer should not look that complicated. But OPL Java interface makes every possible approach very complex. The Java interface does not allow to explore similarities by writing common shared code. Instead it requires to implement the renaming for each possible variable declaration.

A variable may be:

  • A single value.
  • An array (called “map” by the Java interface).

Also, the variable may be:

  • An integer.
  • A decimal number.

If the variable is declared by an array, the the index may be:

  • An integer
  • A symbol (string)
  • A tuple. The tuple might be:
    • Tuple of integers
    • Tuple of symbols
    • A mixed tuple

Last, but not least, the array may be bi-dimensional, tri-dimensional, etc. Each additional dimension is as complex as the first one.

Thats it: the variable renamer must handle every combination of the stated possibilities, and the OPL Java interface forces one to implement each one of them. I conclude that the API is not well designed for such purpose.

Performance has proven to be acceptable. For a 1.2k line OPL model, with about 50k variables and 50k (in)equations, variables were renamed in about one or two seconds. Nearly imperceptible considering that OPL takes one minute to generate the model, another two to solve.

The code:

/*
 * Copyright 2010 Daniel Felix Ferber. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
 * disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 * following disclaimer in the documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY Daniel Felix Ferber ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL Daniel Felix FerberBE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the authors and should not be
 * interpreted as representing official policies, either expressed or implied, of Daniel Felix Ferber.
 */
package dfferber.ilog.opl;

import ilog.concert.IloException;
import ilog.concert.IloIntVarMap;
import ilog.concert.IloNumVarMap;
import ilog.concert.IloTuple;
import ilog.concert.IloTupleSchema;
import ilog.opl.IloOplArrayDefinition;
import ilog.opl.IloOplElement;
import ilog.opl.IloOplElementDefinition;
import ilog.opl.IloOplElementType.Type;
import ilog.opl.IloOplModel;
import ilog.opl.IloOplModelDefinition;

import java.util.Iterator;

import dfferber.utils.Assert;

public class ModelVariablesRenamer {
	private final IloOplModel model;
	private final IloOplModelDefinition modelDefinition;
	private String prefix;

	public ModelVariablesRenamer(IloOplModel model) {
		super();
		this.model = model;
		this.modelDefinition = model.getModelDefinition();
	}

	public void setPrefix(String prefix) {
		this.prefix = prefix;
	}

	public void run() {
		@SuppressWarnings("unchecked")
		Iterator iterator = model.getElementIterator();
		while (iterator.hasNext()) {
			IloOplElement element = iterator.next();
			if (element.isDecisionVariable()) {
				@SuppressWarnings("unused")
				IloOplElementDefinition definition = modelDefinition.getElementDefinition(element.getName());
				/*
				 * As variáveis de decisão suportadas podem ser variáveis numéricas ou arrays
				 * de variáveis numéricas.
				 */
				if (element.getElementType().equals(Type.NUM)) {
					renameNumVariable(element);
				} else if (element.getElementType().equals(Type.MAP_NUM)) {
					renameNumArrayVariable(element);
				} else if (element.getElementType().equals(Type.INT)) {
					renameIntVariable(element);
				} else if (element.getElementType().equals(Type.MAP_INT)) {
					renameIntArrayVariable(element.asIntVarMap(), element.getName());
				} else {
					throw new Assert.UnsupportedConditionException();
				}
			}
		}
	}

	private void renameNumVariable(IloOplElement element) {
		String newName = fixedName(element);
		element.asNumVar().setName(newName);
	}

	private void renameIntVariable(IloOplElement element) {
		String newName = fixedName(element);
		element.asIntVar().setName(newName);
	}

	private void renameIntArrayVariable(IloIntVarMap asIntVarMap, String name) {
		throw new Assert.UnimplementedOperationException();
	}

	private void renameNumArrayVariable(IloOplElement element) {
		String newName = fixedName(element);
		IloNumVarMap map = element.asNumVarMap();
		IloOplArrayDefinition mapDefinition = modelDefinition.getElementDefinition(element.getName()).asArray();
		recurseIntoNumArray(newName, map, mapDefinition, 0);
	}

	private void recurseIntoNumArray(String prefixoNome, IloNumVarMap mapElement, IloOplArrayDefinition mapDefinition, int currentIndexer) {
		try {
			IloOplElementDefinition indexer = mapDefinition.getIndexer(currentIndexer);
			if (indexer.getName() == null) throw new Assert.UnsupportedDataException(prefixoNome);
			IloOplElement indexElement = model.getElement(indexer.getName());

			if (indexElement.getElementType().equals(Type.SET_TUPLE)) {
				@SuppressWarnings("unchecked")
				Iterator iterator = indexElement.asTupleSet().iterator();
				if (currentIndexer &lt; mapDefinition.getDimensions()-1) {
					while (iterator.hasNext()) {
						IloTuple tuple = iterator.next();
						recurseIntoNumArray(prefixoNome+tupleToStr(tuple), mapElement.getSub(tuple), mapDefinition, currentIndexer+1);
					}
				} else {
					while (iterator.hasNext()) {
						IloTuple tuple = iterator.next();
						mapElement.get(tuple).setName(prefixoNome+tupleToStr(tuple));
					}
				}
			}
			else if (indexElement.getElementType().equals(Type.RANGE_INT)) {
				@SuppressWarnings(&quot;unchecked&quot;)
				Iterator iterator = indexElement.asIntRange().iterator();
				if (currentIndexer &lt; mapDefinition.getDimensions()-1) {
					while (iterator.hasNext()) {
						Integer index = iterator.next();
						recurseIntoNumArray(prefixoNome+intToStr(index), mapElement.getSub(index), mapDefinition, currentIndexer+1);
					}
				} else {
					while (iterator.hasNext()) {
						Integer index = iterator.next();
						mapElement.get(index).setName(prefixoNome+intToStr(index));
					}
				}
			} else {
				throw new Assert.UnsupportedConditionException();
			}
		} catch (IloException e) {
			throw new Assert.UnexpectedException(e);
		}
	}

	private String intToStr(Integer index) {
		return &quot;(&quot;+index.toString()+&quot;)&quot;;
	}

	private String tupleToStr(IloTuple tuple) {
		IloTupleSchema schema = tuple.getSchema();
		StringBuilder result = new StringBuilder();
		result.append(&quot;(&quot;);
		for (int i = 0; i &lt; schema.getSize(); i++) {
			if (i != 0) result.append(',');
			if (schema.isInt(i)) result.append(Integer.toString(tuple.getIntValue(i)));
			else if (schema.isSymbol(i)) result.append(tuple.getStringValue(i));
			else throw new Assert.UnsupportedConditionException();
		}
		result.append(&quot;)&quot;);
		return result.toString();
	}

	private String fixedName(IloOplElement element) {
		String nome = element.getName();
		String newName = null;
		if (prefix != null &amp;&amp; nome.startsWith(prefix)) newName = nome.substring(prefix.length());
		else newName = nome.toLowerCase();
		return newName;
	}
}

One Response to On fixing CPLEX variable names generated by OPL

  1. Mustapha says:

    Hi,
    I think there is a parameter that you can change and get the whole name of your decison variables and constraints, called bigmapthreshold. by default it’s 100 but you can increase it if your model is large. Thanks.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: