org.ojalgo.optimisation.Expression Maven / Gradle / Ivy
Show all versions of ojalgo Show documentation
/*
* Copyright 1997-2022 Optimatika
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.ojalgo.optimisation;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.ojalgo.ProgrammingError;
import org.ojalgo.function.BinaryFunction;
import org.ojalgo.function.UnaryFunction;
import org.ojalgo.function.aggregator.AggregatorFunction;
import org.ojalgo.function.aggregator.AggregatorSet;
import org.ojalgo.function.aggregator.BigAggregator;
import org.ojalgo.function.constant.BigMath;
import org.ojalgo.function.constant.PrimitiveMath;
import org.ojalgo.function.multiary.AffineFunction;
import org.ojalgo.function.multiary.ConstantFunction;
import org.ojalgo.function.multiary.MultiaryFunction;
import org.ojalgo.function.multiary.PureQuadraticFunction;
import org.ojalgo.function.multiary.QuadraticFunction;
import org.ojalgo.matrix.store.MatrixStore;
import org.ojalgo.matrix.store.Primitive64Store;
import org.ojalgo.structure.Access1D;
import org.ojalgo.structure.Access2D;
import org.ojalgo.structure.Structure1D;
import org.ojalgo.structure.Structure1D.IntIndex;
import org.ojalgo.structure.Structure2D.IntRowColumn;
/**
*
* Think of an Expression as one constraint or a component to the objective function. An expression becomes a
* linear expression as soon as you set a linear factor. Setting a quadratic factor turns it into a quadratic
* expression. If you set both linear and quadratic factors it is a compound expression, and if you set
* neither it is an empty expression. Currently the solvers supplied by ojAlgo can only handle linear
* constraint expressions. The objective function can be linear, quadratic or compound. Empty expressions
* makes no sense...
*
*
* An expression is turned into a constraint by setting a lower and/or upper limit. Use
* {@linkplain Expression#lower(Comparable)}, {@linkplain Expression#upper(Comparable)} or
* {@linkplain Expression#level(Comparable)}. An expression is made part of (contributing to) the objective
* function by setting a contribution weight. Use {@linkplain Expression#weight(Comparable)}. The contribution
* weight can be set to anything except zero (0.0). Often you may just want to set it to one (1.0). Other
* values can be used to balance multiple expressions contributing to the objective function.
*
*
* @author apete
*/
public final class Expression extends ModelEntity {
private BigDecimal myConstant = null;
private transient boolean myInfeasible = false;
private transient Boolean myInteger = null;
private final Map myLinear;
private final ExpressionsBasedModel myModel;
private final Map myQuadratic;
private transient boolean myRedundant = false;
/**
* A shallow copy (typically created by presolver or integer solver) shares the Map:s holding the
* paramaters with other Expressions. They will only differ on the lower/upper limits and on meta data
* like flags indicating redundancy or infeasibility.
*/
private final boolean myShallowCopy;
@SuppressWarnings("unused")
private Expression(final Expression entityToCopy) {
this(entityToCopy, null, false);
ProgrammingError.throwForIllegalInvocation();
}
protected Expression(final Expression expressionToCopy, final ExpressionsBasedModel destinationModel, final boolean deep) {
super(expressionToCopy);
myModel = destinationModel;
myConstant = expressionToCopy.getConstant();
if (deep) {
myShallowCopy = false;
myLinear = new HashMap<>();
myLinear.putAll(expressionToCopy.getLinear());
myQuadratic = new HashMap<>();
myQuadratic.putAll(expressionToCopy.getQuadratic());
} else {
myShallowCopy = true;
myLinear = expressionToCopy.getLinear();
myQuadratic = expressionToCopy.getQuadratic();
}
if (expressionToCopy.isInteger()) {
myInteger = Boolean.TRUE;
} else {
myInteger = null;
}
}
Expression(final String name, final ExpressionsBasedModel model) {
super(name);
ProgrammingError.throwIfNull(name, model);
myModel = model;
myShallowCopy = false;
myLinear = new HashMap<>();
myQuadratic = new HashMap<>();
}
/**
* @see #add(Variable, Comparable)
*/
public Expression add(final int index, final Comparable> value) {
return this.add(myModel.getVariable(index), value);
}
/**
* @see #add(Variable, Comparable)
*/
public Expression add(final int index, final double value) {
return this.add(index, BigDecimal.valueOf(value));
}
/**
* @see #add(Variable, Comparable)
*/
public Expression add(final int row, final int column, final Comparable> value) {
return this.add(new IntRowColumn(row, column), value);
}
/**
* @see #add(Variable, Comparable)
*/
public Expression add(final int row, final int column, final double value) {
return this.add(row, column, BigDecimal.valueOf(value));
}
/**
* @see #add(Variable, Comparable)
*/
public Expression add(final int row, final int column, final long value) {
return this.add(row, column, BigDecimal.valueOf(value));
}
/**
* @see #add(Variable, Comparable)
*/
public Expression add(final int index, final long value) {
return this.add(index, BigDecimal.valueOf(value));
}
/**
* @deprecated Use the alternatives with a {@link Variable} or an int as the key/index instead.
*/
@Deprecated
public Expression add(final IntIndex key, final Comparable> value) {
BigDecimal existing = myLinear.get(key);
if (existing != null) {
this.set(key, ModelEntity.toBigDecimal(value).add(existing));
} else {
this.set(key, value);
}
return this;
}
/**
* @deprecated Use the alternatives with a {@link Variable} or an int as the key/index instead.
*/
@Deprecated
public Expression add(final IntIndex key, final double value) {
return this.add(key, BigDecimal.valueOf(value));
}
/**
* @deprecated Use the alternatives with a {@link Variable} or an int as the key/index instead.
*/
@Deprecated
public Expression add(final IntIndex row, final IntIndex column, final Comparable> value) {
return this.add(new IntRowColumn(row, column), value);
}
/**
* @deprecated Use the alternatives with a {@link Variable} or an int as the key/index instead.
*/
@Deprecated
public Expression add(final IntIndex row, final IntIndex column, final double value) {
return this.add(row, column, BigDecimal.valueOf(value));
}
/**
* @deprecated Use the alternatives with a {@link Variable} or an int as the key/index instead.
*/
@Deprecated
public Expression add(final IntIndex row, final IntIndex column, final long value) {
return this.add(row, column, BigDecimal.valueOf(value));
}
/**
* @deprecated Use the alternatives with a {@link Variable} or an int as the key/index instead.
*/
@Deprecated
public Expression add(final IntIndex key, final long value) {
return this.add(key, BigDecimal.valueOf(value));
}
/**
* @deprecated Use the alternatives with {@link Variable}:s or int:s as the key instead.
*/
@Deprecated
public Expression add(final IntRowColumn key, final Comparable> value) {
BigDecimal existing = myQuadratic.get(key);
if (existing != null) {
this.set(key, ModelEntity.toBigDecimal(value).add(existing));
} else {
this.set(key, value);
}
return this;
}
/**
* @deprecated Use the alternatives with {@link Variable}:s or int:s as the key instead.
*/
@Deprecated
public Expression add(final IntRowColumn key, final double value) {
return this.add(key, BigDecimal.valueOf(value));
}
/**
* @deprecated Use the alternatives with {@link Variable}:s or int:s as the key instead.
*/
@Deprecated
public Expression add(final IntRowColumn key, final long value) {
return this.add(key, BigDecimal.valueOf(value));
}
/**
* Will add the value to this variable's factor.
*/
public Expression add(final Variable variable, final Comparable> value) {
return this.add(variable.getIndex(), value);
}
/**
* @see #add(Variable, Comparable)
*/
public Expression add(final Variable variable, final double value) {
return this.add(variable, BigDecimal.valueOf(value));
}
/**
* @see #add(Variable, Comparable)
*/
public Expression add(final Variable variable, final long value) {
return this.add(variable, BigDecimal.valueOf(value));
}
/**
* @see #add(Variable, Comparable)
*/
public Expression add(final Variable variable1, final Variable variable2, final Comparable> value) {
return this.add(variable1.getIndex().index, variable2.getIndex().index, value);
}
/**
* @see #add(Variable, Comparable)
*/
public Expression add(final Variable variable1, final Variable variable2, final double value) {
return this.add(variable1, variable2, BigDecimal.valueOf(value));
}
/**
* @see #add(Variable, Comparable)
*/
public Expression add(final Variable variable1, final Variable variable2, final long value) {
return this.add(variable1, variable2, BigDecimal.valueOf(value));
}
@Override
public void addTo(final Expression target, final BigDecimal scale) {
for (Entry entry : myLinear.entrySet()) {
BigDecimal value = entry.getValue().multiply(scale);
target.add(entry.getKey(), value);
}
for (Entry entry : myQuadratic.entrySet()) {
BigDecimal value = entry.getValue().multiply(scale);
target.add(entry.getKey(), value);
}
}
public int compareTo(final Expression obj) {
return this.getName().compareTo(obj.getName());
}
/**
* Will return an Expression with factors corresponding to fixed variables removed, and lower/upper limits
* compensated for the fixed part of the expression. Factors corresponding to bilinear variables, where
* one is fixed and the other is not, are linearized.
*
* @param fixedVariables A set of (by the presolver) fixed variable indices
* @return The reduced/modified expression
*/
public Expression compensate(final Set fixedVariables) {
if (fixedVariables.size() == 0 || !this.isAnyQuadraticFactorNonZero() && Collections.disjoint(fixedVariables, this.getLinearKeySet())) {
return this; // No need to copy/compensate anything
}
ExpressionsBasedModel tmpModel = this.getModel();
Expression retVal = new Expression(this.getName(), tmpModel);
BigDecimal tmpFixedValue = BigMath.ZERO;
for (Entry tmpEntry : myLinear.entrySet()) {
IntIndex tmpKey = tmpEntry.getKey();
BigDecimal tmpFactor = tmpEntry.getValue();
if (fixedVariables.contains(tmpKey)) {
// Fixed
Variable variable = tmpModel.getVariable(tmpKey.index);
BigDecimal tmpValue = variable.getValue();
tmpFixedValue = tmpFixedValue.add(tmpFactor.multiply(tmpValue));
} else {
// Not fixed
retVal.set(tmpKey, tmpFactor);
}
}
for (Entry tmpEntry : myQuadratic.entrySet()) {
IntRowColumn tmpKey = tmpEntry.getKey();
BigDecimal tmpFactor = tmpEntry.getValue();
Variable tmpRowVariable = tmpModel.getVariable(tmpKey.row);
Variable tmpColVariable = tmpModel.getVariable(tmpKey.column);
IntIndex tmpRowKey = tmpRowVariable.getIndex();
IntIndex tmpColKey = tmpColVariable.getIndex();
if (fixedVariables.contains(tmpRowKey)) {
BigDecimal tmpRowValue = tmpRowVariable.getValue();
if (fixedVariables.contains(tmpColKey)) {
// Both fixed
BigDecimal tmpColValue = tmpColVariable.getValue();
tmpFixedValue = tmpFixedValue.add(tmpFactor.multiply(tmpRowValue).multiply(tmpColValue));
} else {
// Row fixed
retVal.add(tmpColKey, tmpFactor.multiply(tmpRowValue));
}
} else if (fixedVariables.contains(tmpColKey)) {
// Column fixed
BigDecimal tmpColValue = tmpColVariable.getValue();
retVal.add(tmpRowKey, tmpFactor.multiply(tmpColValue));
} else {
// Neither fixed
retVal.set(tmpKey, tmpFactor);
}
}
if (this.isLowerLimitSet()) {
retVal.lower(this.getLowerLimit().subtract(tmpFixedValue));
}
if (this.isUpperLimitSet()) {
retVal.upper(this.getUpperLimit().subtract(tmpFixedValue));
}
if (this.isInteger()) {
retVal.setInteger();
}
return retVal;
}
public BigDecimal evaluate(final Access1D point) {
BigDecimal retVal = this.getConstant();
BigDecimal factor;
for (IntRowColumn quadKey : this.getQuadraticKeySet()) {
factor = this.get(quadKey);
retVal = retVal.add(factor.multiply(point.get(quadKey.row)).multiply(point.get(quadKey.column)));
}
for (IntIndex linKey : this.getLinearKeySet()) {
factor = this.get(linKey);
retVal = retVal.add(factor.multiply(point.get(linKey.index)));
}
return retVal;
}
public BigDecimal get(final IntIndex key) {
return this.getLinearFactor(key, false);
}
public BigDecimal get(final IntRowColumn key) {
return this.getQuadraticFactor(key, false);
}
public BigDecimal get(final Variable variable) {
IntIndex tmpIndex = variable.getIndex();
if (tmpIndex != null) {
return this.get(tmpIndex);
}
throw new IllegalStateException("Variable not part of (this) model!");
}
public MatrixStore getAdjustedGradient(final Access1D> point) {
Primitive64Store retVal = Primitive64Store.FACTORY.make(myModel.countVariables(), 1);
BinaryFunction tmpBaseFunc = PrimitiveMath.ADD;
double tmpAdjustedFactor;
UnaryFunction tmpModFunc;
for (IntRowColumn tmpKey : this.getQuadraticKeySet()) {
tmpAdjustedFactor = this.getAdjustedQuadraticFactor(tmpKey);
tmpModFunc = tmpBaseFunc.second(tmpAdjustedFactor * point.doubleValue(tmpKey.column));
retVal.modifyOne(tmpKey.row, 0, tmpModFunc);
tmpModFunc = tmpBaseFunc.second(tmpAdjustedFactor * point.doubleValue(tmpKey.row));
retVal.modifyOne(tmpKey.column, 0, tmpModFunc);
}
for (IntIndex tmpKey : this.getLinearKeySet()) {
tmpAdjustedFactor = this.getAdjustedLinearFactor(tmpKey);
tmpModFunc = tmpBaseFunc.second(tmpAdjustedFactor);
retVal.modifyOne(tmpKey.index, 0, tmpModFunc);
}
return retVal;
}
public MatrixStore getAdjustedHessian() {
int tmpCountVariables = myModel.countVariables();
Primitive64Store retVal = Primitive64Store.FACTORY.make(tmpCountVariables, tmpCountVariables);
BinaryFunction tmpBaseFunc = PrimitiveMath.ADD;
UnaryFunction tmpModFunc;
for (IntRowColumn tmpKey : this.getQuadraticKeySet()) {
tmpModFunc = tmpBaseFunc.second(this.getAdjustedQuadraticFactor(tmpKey));
retVal.modifyOne(tmpKey.row, tmpKey.column, tmpModFunc);
retVal.modifyOne(tmpKey.column, tmpKey.row, tmpModFunc);
}
return retVal;
}
public double getAdjustedLinearFactor(final int aVar) {
return this.getAdjustedLinearFactor(new IntIndex(aVar));
}
public double getAdjustedLinearFactor(final IntIndex key) {
return this.getLinearFactor(key, true).doubleValue();
}
public double getAdjustedLinearFactor(final Variable aVar) {
return this.getAdjustedLinearFactor(aVar.getIndex());
}
public double getAdjustedQuadraticFactor(final int aVar1, final int aVar2) {
return this.getAdjustedQuadraticFactor(new IntRowColumn(aVar1, aVar2));
}
public double getAdjustedQuadraticFactor(final IntRowColumn key) {
return this.getQuadraticFactor(key, true).doubleValue();
}
public double getAdjustedQuadraticFactor(final Variable aVar1, final Variable aVar2) {
return this.getAdjustedQuadraticFactor(myModel.indexOf(aVar1), myModel.indexOf(aVar2));
}
public Set> getLinearEntrySet() {
return myLinear.entrySet();
}
public Set getLinearKeySet() {
return myLinear.keySet();
}
public Set> getQuadraticEntrySet() {
return myQuadratic.entrySet();
}
public Set getQuadraticKeySet() {
return myQuadratic.keySet();
}
public boolean isAnyLinearFactorNonZero() {
return myLinear.size() > 0;
}
public boolean isAnyQuadraticFactorNonZero() {
return myQuadratic.size() > 0;
}
public boolean isFunctionConstant() {
return !this.isAnyQuadraticFactorNonZero() && !this.isAnyLinearFactorNonZero();
}
public boolean isFunctionLinear() {
return !this.isAnyQuadraticFactorNonZero() && this.isAnyLinearFactorNonZero();
}
public boolean isFunctionPureQuadratic() {
return this.isAnyQuadraticFactorNonZero() && !this.isAnyLinearFactorNonZero();
}
public boolean isFunctionQuadratic() {
return this.isAnyQuadraticFactorNonZero() && this.isAnyLinearFactorNonZero();
}
@Override
public boolean isInteger() {
if (myInteger == null) {
this.doIntegerRounding();
}
return myInteger.booleanValue();
}
/**
* @return Are all the (linear) variables binary
*/
public boolean isLinearAndAllBinary() {
return myQuadratic.size() == 0 && myLinear.size() > 0 && myLinear.keySet().stream().allMatch(i -> myModel.getVariable(i).isBinary());
}
/**
* @return Are all the (linear) variables integer
*/
public boolean isLinearAndAllInteger() {
return myQuadratic.size() == 0 && myLinear.size() > 0 && myLinear.keySet().stream().allMatch(i -> myModel.getVariable(i).isInteger());
}
/**
* @return Are any of the (linear) variables binary
*/
public boolean isLinearAndAnyBinary() {
return myQuadratic.size() == 0 && myLinear.size() > 0 && myLinear.keySet().stream().anyMatch(i -> myModel.getVariable(i).isBinary());
}
/**
* @return Are any of the (linear) variables integer
*/
public boolean isLinearAndAnyInteger() {
return myQuadratic.size() == 0 && myLinear.size() > 0 && myLinear.keySet().stream().anyMatch(i -> myModel.getVariable(i).isInteger());
}
/**
* @see #set(Variable, Comparable)
*/
public Expression set(final int index, final Comparable> value) {
return this.set(myModel.getVariable(index), value);
}
/**
* @see #set(Variable, Comparable)
*/
public Expression set(final int index, final double value) {
return this.set(index, BigDecimal.valueOf(value));
}
/**
* @see #set(Variable, Comparable)
*/
public Expression set(final int row, final int column, final Comparable> value) {
return this.set(new IntRowColumn(row, column), value);
}
/**
* @see #set(Variable, Comparable)
*/
public Expression set(final int row, final int column, final double value) {
return this.set(row, column, BigDecimal.valueOf(value));
}
/**
* @see #set(Variable, Comparable)
*/
public Expression set(final int row, final int column, final long value) {
return this.set(row, column, BigDecimal.valueOf(value));
}
/**
* @see #set(Variable, Comparable)
*/
public Expression set(final int index, final long value) {
return this.set(index, BigDecimal.valueOf(value));
}
/**
* @deprecated Use the alternatives with a {@link Variable} or an int as the key/index instead.
*/
@Deprecated
public Expression set(final IntIndex key, final Comparable> value) {
if (key == null) {
throw new IllegalArgumentException();
}
BigDecimal tmpValue = ModelEntity.toBigDecimal(value);
if (tmpValue.signum() != 0) {
myLinear.put(key, tmpValue);
myModel.addReference(key);
} else {
myLinear.remove(key);
}
return this;
}
/**
* @deprecated Use the alternatives with a {@link Variable} or an int as the key/index instead.
*/
@Deprecated
public Expression set(final IntIndex key, final double value) {
return this.set(key, BigDecimal.valueOf(value));
}
/**
* @deprecated Use the alternatives with a {@link Variable} or an int as the key/index instead.
*/
@Deprecated
public Expression set(final IntIndex row, final IntIndex column, final Comparable> value) {
return this.set(new IntRowColumn(row, column), value);
}
/**
* @deprecated Use the alternatives with a {@link Variable} or an int as the key/index instead.
*/
@Deprecated
public Expression set(final IntIndex row, final IntIndex column, final double value) {
return this.set(row, column, BigDecimal.valueOf(value));
}
/**
* @deprecated Use the alternatives with a {@link Variable} or an int as the key/index instead.
*/
@Deprecated
public Expression set(final IntIndex row, final IntIndex column, final long value) {
return this.set(row, column, BigDecimal.valueOf(value));
}
/**
* @deprecated Use the alternatives with a {@link Variable} or an int as the key/index instead.
*/
@Deprecated
public Expression set(final IntIndex key, final long value) {
return this.set(key, BigDecimal.valueOf(value));
}
/**
* @deprecated Use the alternatives with {@link Variable}:s or int:s as the key instead.
*/
@Deprecated
public Expression set(final IntRowColumn key, final Comparable> value) {
if (key == null) {
throw new IllegalArgumentException();
}
BigDecimal tmpValue = ModelEntity.toBigDecimal(value);
if (tmpValue.signum() != 0) {
myQuadratic.put(key, tmpValue);
myModel.addReference(key.row());
myModel.addReference(key.column());
} else {
myQuadratic.remove(key);
}
return this;
}
/**
* @deprecated Use the alternatives with {@link Variable}:s or int:s as the key instead.
*/
@Deprecated
public Expression set(final IntRowColumn key, final double value) {
return this.set(key, BigDecimal.valueOf(value));
}
/**
* @deprecated Use the alternatives with {@link Variable}:s or int:s as the key instead.
*/
@Deprecated
public Expression set(final IntRowColumn key, final long value) {
return this.set(key, BigDecimal.valueOf(value));
}
/**
* Will set (replace) the variable's factor to this value
*/
public Expression set(final Variable variable, final Comparable> value) {
return this.set(variable.getIndex(), value);
}
/**
* @see #set(Variable, Comparable)
*/
public Expression set(final Variable variable, final double value) {
return this.set(variable, BigDecimal.valueOf(value));
}
/**
* @see #set(Variable, Comparable)
*/
public Expression set(final Variable variable, final long value) {
return this.set(variable, BigDecimal.valueOf(value));
}
/**
* @see #set(Variable, Comparable)
*/
public Expression set(final Variable variable1, final Variable variable2, final Comparable> value) {
return this.set(variable1.getIndex().index, variable2.getIndex().index, value);
}
/**
* @see #set(Variable, Comparable)
*/
public Expression set(final Variable variable1, final Variable variable2, final double value) {
return this.set(variable1, variable2, BigDecimal.valueOf(value));
}
/**
* @see #set(Variable, Comparable)
*/
public Expression set(final Variable variable1, final Variable variable2, final long value) {
return this.set(variable1, variable2, BigDecimal.valueOf(value));
}
/**
* Will set the quadratic and linear factors to an expression that measures (the square of) the distance
* from the given point.
*
* @param variables The relevant variables
* @param point The point to measure from
*/
public void setCompoundFactorsOffset(final List variables, final Access1D> point) {
int tmpLength = variables.size();
if (point.count() != tmpLength) {
throw new IllegalArgumentException();
}
BigDecimal tmpLinearWeight = BigMath.TWO.negate();
Variable tmpVariable;
BigDecimal tmpVal;
for (int ij = 0; ij < tmpLength; ij++) {
tmpVariable = variables.get(ij);
tmpVal = ModelEntity.toBigDecimal(point.get(ij));
this.set(tmpVariable, tmpVariable, BigMath.ONE);
this.set(tmpVariable, tmpVal.multiply(tmpLinearWeight));
}
}
public void setLinearFactors(final List variables, final Access1D> factors) {
int tmpLimit = variables.size();
if (factors.count() != tmpLimit) {
throw new IllegalArgumentException();
}
for (int i = 0; i < tmpLimit; i++) {
this.set(variables.get(i), factors.get(i));
}
}
/**
* Will set the linear factors to a simple sum expression - all factors equal 1.0.
*
* @param variables The relevant variables
*/
public void setLinearFactorsSimple(final List variables) {
for (Variable tmpVariable : variables) {
this.set(tmpVariable, BigMath.ONE);
}
}
public void setQuadraticFactors(final List variables, final Access2D> factors) {
int tmpLimit = variables.size();
if (factors.countRows() != tmpLimit || factors.countColumns() != tmpLimit) {
throw new IllegalArgumentException();
}
for (int j = 0; j < tmpLimit; j++) {
Variable tmpVar2 = variables.get(j);
for (int i = 0; i < tmpLimit; i++) {
this.set(variables.get(i), tmpVar2, factors.get(i, j));
}
}
}
/**
* Will attempt to exploit integer property to tighten the lower and/or upper limits (integer rounding).
*/
public void tighten() {
if (this.isConstraint()) {
this.isInteger();
}
}
public MultiaryFunction.TwiceDifferentiable toFunction() {
if (this.isFunctionQuadratic()) {
return this.makeQuadraticFunction();
}
if (this.isFunctionPureQuadratic()) {
return this.makePureQuadraticFunction();
}
if (this.isFunctionLinear()) {
return this.makeAffineFunction();
}
return this.makeConstantFunction();
}
private BigDecimal convert(final BigDecimal value, final boolean adjusted) {
if (value == null) {
return BigMath.ZERO;
}
if (!adjusted) {
return value;
}
int tmpAdjExp = this.getAdjustmentExponent();
if (tmpAdjExp != 0) {
return value.movePointRight(tmpAdjExp);
}
return value;
}
private BigDecimal getConstant() {
return myConstant != null ? myConstant : BigMath.ZERO;
}
private AffineFunction makeAffineFunction() {
AffineFunction retVal = AffineFunction.makePrimitive(myModel.countVariables());
if (this.isAnyLinearFactorNonZero()) {
for (Entry entry : myLinear.entrySet()) {
retVal.linear().set(entry.getKey().index, entry.getValue().doubleValue());
}
}
retVal.setConstant(this.getConstant());
return retVal;
}
private ConstantFunction makeConstantFunction() {
return ConstantFunction.makePrimitive(myModel.countVariables(), this.getConstant());
}
private PureQuadraticFunction makePureQuadraticFunction() {
PureQuadraticFunction retVal = PureQuadraticFunction.makePrimitive(myModel.countVariables());
if (this.isAnyQuadraticFactorNonZero()) {
for (Entry entry : myQuadratic.entrySet()) {
retVal.quadratic().set(entry.getKey().row, entry.getKey().column, entry.getValue().doubleValue());
}
}
retVal.setConstant(this.getConstant());
return retVal;
}
private QuadraticFunction makeQuadraticFunction() {
QuadraticFunction retVal = QuadraticFunction.makePrimitive(myModel.countVariables());
if (this.isAnyQuadraticFactorNonZero()) {
for (Entry entry : myQuadratic.entrySet()) {
retVal.quadratic().set(entry.getKey().row, entry.getKey().column, entry.getValue().doubleValue());
}
}
if (this.isAnyLinearFactorNonZero()) {
for (Entry entry : myLinear.entrySet()) {
retVal.linear().set(entry.getKey().index, entry.getValue().doubleValue());
}
}
retVal.setConstant(this.getConstant());
return retVal;
}
private BigDecimal toPositiveFraction(final BigDecimal noninteger) {
BigDecimal intPart = noninteger.setScale(0, RoundingMode.FLOOR);
return noninteger.subtract(intPart);
}
protected void appendMiddlePart(final StringBuilder builder, final Access1D currentSolution) {
builder.append(this.getName());
builder.append(": ");
builder.append(ModelEntity.DISPLAY.enforce(this.toFunction().invoke(Access1D.asPrimitive1D(currentSolution))));
if (this.isObjective()) {
builder.append(" (");
builder.append(ModelEntity.DISPLAY.enforce(this.getContributionWeight()));
builder.append(")");
}
}
@Override
protected void destroy() {
super.destroy();
if (!myShallowCopy) {
myLinear.clear();
myQuadratic.clear();
}
}
void addObjectiveConstant(final BigDecimal value) {
BigDecimal weight = this.getContributionWeight();
if (weight != null && weight.signum() != 0) {
myModel.addObjectiveConstant(value.multiply(weight));
} else {
myModel.addObjectiveConstant(value);
}
}
void appendToString(final StringBuilder aStringBuilder, final Access1D aCurrentState) {
this.appendLeftPart(aStringBuilder);
if (aCurrentState != null) {
this.appendMiddlePart(aStringBuilder, aCurrentState);
} else {
this.appendMiddlePart(aStringBuilder);
}
this.appendRightPart(aStringBuilder);
}
/**
* Calculates this expression's value - the subset variables' part of this expression. Will never return
* null.
*/
BigDecimal calculateSetValue(final Collection subset) {
BigDecimal retVal = BigMath.ZERO;
if (subset.size() > 0) {
for (IntIndex linKey : myLinear.keySet()) {
if (subset.contains(linKey)) {
BigDecimal coefficient = this.get(linKey);
BigDecimal value = myModel.getVariable(linKey.index).getValue();
retVal = retVal.add(coefficient.multiply(value));
}
}
for (IntRowColumn quadKey : myQuadratic.keySet()) {
if (subset.contains(quadKey.row()) && subset.contains(quadKey.column())) {
BigDecimal coefficient = this.get(quadKey);
BigDecimal rowValue = myModel.getVariable(quadKey.row).getValue();
BigDecimal colValue = myModel.getVariable(quadKey.column).getValue();
retVal = retVal.add(coefficient.multiply(rowValue).multiply(colValue));
}
}
}
return retVal;
}
Expression copy(final ExpressionsBasedModel destinationModel, final boolean deep) {
return new Expression(this, destinationModel, deep);
}
long countIntegerFactors() {
return myLinear.keySet().stream().map(this::resolve).filter(Variable::isInteger).count();
}
int countLinearFactors() {
return myLinear.size();
}
int countQuadraticFactors() {
return myQuadratic.size();
}
@Override
int deriveAdjustmentExponent() {
if (this.isInteger()) {
return 0;
}
AggregatorSet aggregators = BigAggregator.getSet();
AggregatorFunction largest = aggregators.largest();
AggregatorFunction smallest = aggregators.smallest();
if (this.isAnyQuadraticFactorNonZero()) {
for (BigDecimal quadraticFactor : myQuadratic.values()) {
largest.invoke(quadraticFactor);
smallest.invoke(quadraticFactor);
}
return ModelEntity.deriveAdjustmentExponent(largest, smallest, 6);
}
if (!this.isAnyLinearFactorNonZero()) {
return 0;
}
for (BigDecimal linearFactor : myLinear.values()) {
largest.invoke(linearFactor);
smallest.invoke(linearFactor);
}
return ModelEntity.deriveAdjustmentExponent(largest, smallest, 16);
}
/**
* Assumes at least 1 variable, and all variables integer!
*
* @see org.ojalgo.optimisation.ModelEntity#doIntegerRounding()
*/
@Override
void doIntegerRounding() {
this.doIntegerRounding(this.getLinearKeySet(), this.getLowerLimit(), this.getUpperLimit());
}
void doIntegerRounding(final Set remaining, final BigDecimal lower, final BigDecimal upper) {
if (myInteger != null) {
return;
}
if (remaining.size() == 0 || !myModel.isInteger(remaining) || myQuadratic.size() > 0) {
myInteger = Boolean.FALSE;
return;
}
BigInteger gcd = null;
int maxScale = Integer.MIN_VALUE;
for (IntIndex index : remaining) {
BigDecimal coeff = myLinear.get(index);
BigDecimal abs = coeff.stripTrailingZeros().abs();
maxScale = Math.max(maxScale, abs.scale());
if (gcd != null) {
gcd = gcd.gcd(abs.unscaledValue());
} else {
gcd = abs.unscaledValue();
}
if (maxScale > 8 || (gcd.equals(BigInteger.ONE) && maxScale > 0)) {
myInteger = Boolean.FALSE;
return;
}
}
BigDecimal divisor = new BigDecimal(gcd, maxScale);
boolean full = myLinear.size() == remaining.size();
BigDecimal newLower = null, newUpper = null;
if (lower != null) {
BigDecimal tmpVal = lower.divide(divisor, 0, RoundingMode.CEILING);
newLower = tmpVal.multiply(divisor);
if (full) {
this.lower(newLower);
}
}
if (upper != null) {
BigDecimal tmpVal = upper.divide(divisor, 0, RoundingMode.FLOOR);
newUpper = tmpVal.multiply(divisor);
if (full) {
this.upper(newUpper);
}
}
if (ModelEntity.isInfeasible(newLower, newUpper)) {
this.setInfeasible();
}
myInteger = Boolean.TRUE;
}
Expression doMixedIntegerRounding() {
if (!this.isEqualityConstraint()) {
return null;
}
BigDecimal posFracLevel = this.toPositiveFraction(this.getLowerLimit());
if (posFracLevel.signum() <= 0) {
return null;
}
BigDecimal cmpFracLevel = BigMath.ONE.subtract(posFracLevel);
Expression retVal = myModel.addExpression(this.getName() + "(MIR)");
for (Entry entry : myLinear.entrySet()) {
Variable variable = this.resolve(entry.getKey());
if (!variable.isLowerLimitSet() || variable.getLowerLimit().compareTo(BigMath.ZERO) < 0) {
return null;
}
BigDecimal coeff = entry.getValue();
if (variable.isInteger()) {
BigDecimal posFracCoeff = this.toPositiveFraction(coeff);
if (posFracCoeff.compareTo(posFracLevel) <= 0) {
retVal.set(variable, BigMath.DIVIDE.invoke(posFracCoeff, posFracLevel));
} else {
BigDecimal cmpFracCoeff = BigMath.ONE.subtract(posFracCoeff);
retVal.set(variable, BigMath.DIVIDE.invoke(cmpFracCoeff, cmpFracLevel));
}
} else if (coeff.signum() == 1) {
retVal.set(variable, BigMath.DIVIDE.invoke(coeff, posFracLevel));
} else if (coeff.signum() == -1) {
BigDecimal negCoeff = coeff.negate();
retVal.set(variable, BigMath.DIVIDE.invoke(negCoeff, cmpFracLevel));
}
}
return retVal.lower(BigMath.ONE);
}
Set getBinaryVariables(final Set subset) {
HashSet retVal = new HashSet<>();
for (IntIndex varInd : myLinear.keySet()) {
if (subset.contains(varInd)) {
Variable variable = myModel.getVariable(varInd.index);
if (variable.isBinary()) {
retVal.add(variable);
}
}
}
return retVal;
}
Map getLinear() {
return myLinear;
}
BigDecimal getLinearFactor(final IntIndex key, final boolean adjusted) {
return this.convert(myLinear.get(key), adjusted);
}
ExpressionsBasedModel getModel() {
return myModel;
}
Map getQuadratic() {
return myQuadratic;
}
BigDecimal getQuadraticFactor(final IntRowColumn key, final boolean adjusted) {
return this.convert(myQuadratic.get(key), adjusted);
}
boolean includes(final Variable variable) {
IntIndex tmpVarInd = variable.getIndex();
return myLinear.containsKey(tmpVarInd)
|| myQuadratic.size() > 0 && myQuadratic.keySet().stream().anyMatch(k -> (k.row == tmpVarInd.index || k.column == tmpVarInd.index));
}
boolean isConstantSet() {
return myConstant != null && myConstant.signum() != 0;
}
@Override
boolean isInfeasible() {
return myInfeasible || super.isInfeasible();
}
/**
* @param subset The indices of a variable subset
* @return true if none of the variables in the subset can make a positve contribution to the expression
* value
*/
boolean isNegativeOn(final Set subset) {
if (!this.isAnyQuadraticFactorNonZero()) {
for (IntIndex index : subset) {
Variable setVar = myModel.getVariable(index);
int signum = myLinear.get(index).signum();
if (signum < 0 && setVar.isLowerLimitSet() && setVar.getLowerLimit().signum() >= 0) {
} else if (signum > 0 && setVar.isUpperLimitSet() && setVar.getUpperLimit().signum() <= 0) {
} else {
return false;
}
}
}
return true;
}
/**
* @param subset The indices of a variable subset
* @return true if none of the variables in the subset can make a negative contribution to the expression
* value
*/
boolean isPositiveOn(final Set subset) {
if (!this.isAnyQuadraticFactorNonZero()) {
for (IntIndex index : subset) {
Variable setVar = myModel.getVariable(index);
int signum = myLinear.get(index).signum();
if (signum > 0 && setVar.isLowerLimitSet() && setVar.getLowerLimit().signum() >= 0) {
} else if (signum < 0 && setVar.isUpperLimitSet() && setVar.getUpperLimit().signum() <= 0) {
} else {
return false;
}
}
}
return true;
}
boolean isRedundant() {
return myRedundant;
}
Variable resolve(final Structure1D.IntIndex index) {
return myModel.getVariable(index);
}
void setConstant(final Comparable> value) {
myConstant = ModelEntity.toBigDecimal(value);
}
void setConstant(final double value) {
myConstant = BigDecimal.valueOf(value);
}
void setConstant(final long value) {
myConstant = BigDecimal.valueOf(value);
}
void setInfeasible() {
myInfeasible = true;
myModel.setInfeasible();
}
void setInteger() {
myInteger = Boolean.TRUE;
}
void setRedundant() {
myRedundant = true;
}
}