Files
ortools-clone/ortools/glop/variables_info.h
Corentin Le Molgat b4b226801b update include guards
2025-11-05 11:54:02 +01:00

255 lines
11 KiB
C++

// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef ORTOOLS_GLOP_VARIABLES_INFO_H_
#define ORTOOLS_GLOP_VARIABLES_INFO_H_
#include "ortools/lp_data/lp_types.h"
#include "ortools/lp_data/sparse.h"
namespace operations_research {
namespace glop {
// Holds the statuses of all the variables, including slack variables. There
// is no point storing constraint statuses since internally all constraints are
// always fixed to zero.
//
// Note that this is the minimal amount of information needed to perform a "warm
// start". Using this information and the original linear program, the basis can
// be refactorized and all the needed quantities derived.
//
// TODO(user): Introduce another state class to store a complete state of the
// solver. Using this state and the original linear program, the solver can be
// restarted with as little time overhead as possible. This is especially useful
// for strong branching in a MIP context.
struct BasisState {
// TODO(user): A MIP solver will potentially store a lot of BasisStates so
// memory usage is important. It is possible to use only 2 bits for one
// VariableStatus enum. To achieve this, the FIXED_VALUE status can be
// converted to either AT_LOWER_BOUND or AT_UPPER_BOUND and decoded properly
// later since this will be used with a given linear program. This way we can
// even encode more information by using the reduced cost sign to choose to
// which bound the fixed status correspond.
VariableStatusRow statuses;
// Returns true if this state is empty.
bool IsEmpty() const { return statuses.empty(); }
};
// Class responsible for maintaining diverse information for each variable that
// depend on its bounds and status.
//
// Note(user): Not all information is needed at all time, but it is cheap to
// maintain it since it only requires a few calls to Update() per simplex
// iteration.
class VariablesInfo {
public:
// Takes references to the linear program data we need.
explicit VariablesInfo(const CompactSparseMatrix& matrix);
// This type is neither copyable nor movable.
VariablesInfo(const VariablesInfo&) = delete;
VariablesInfo& operator=(const VariablesInfo&) = delete;
// Updates the internal bounds and recomputes the variable types from the
// bounds (this is the only function that changes them).
//
// Returns true iff the existing bounds didn't change. Except if the bounds
// AND underlying matrix didn't change, one will need to call one of the two
// Initialize*() methods below before using this class.
bool LoadBoundsAndReturnTrueIfUnchanged(const DenseRow& new_lower_bounds,
const DenseRow& new_upper_bounds);
// Same for an LP not in equation form.
bool LoadBoundsAndReturnTrueIfUnchanged(
const DenseRow& variable_lower_bounds,
const DenseRow& variable_upper_bounds,
const DenseColumn& constraint_lower_bounds,
const DenseColumn& constraint_upper_bounds);
// Initializes the status according to the given BasisState. Incompatible
// statuses will be corrected, and we transform the state correctly if new
// columns / rows were added. Note however that one will need to update the
// BasisState with deletions to preserve the status of unchanged columns.
void InitializeFromBasisState(ColIndex first_slack, ColIndex num_new_cols,
const BasisState& state);
// Changes to the FREE status any column with a BASIC status not listed in
// the basis. Returns their number. Also makes sure all the columns listed in
// basis are marked as basic. Note that if a variable is fixed, we set its
// status to FIXED_VALUE not FREE.
int ChangeUnusedBasicVariablesToFree(const RowToColMapping& basis);
// Loops over all the free variables, and if such a variable has bounds and
// its starting value is closer to its closest bound than the given distance,
// change the status to move this variable to that bound. Returns the number
// of changes. The variable for which starting_values is not provided are
// considered at zero.
//
// This is mainly useful if non-zero starting values are provided. It allows
// to move all the variables close to their bounds at once instead of having
// to move them one by one with simplex pivots later. Of course, by doing that
// we usually introduce a small primal infeasibility that might need
// correction.
//
// If one uses a large distance, then all such variables will start at their
// bound if they have one.
int SnapFreeVariablesToBound(Fractional distance,
const DenseRow& starting_values);
// Sets all variables status to their lowest magnitude bounds. Note that there
// will be no basic variable after this is called.
void InitializeToDefaultStatus();
// Updates the information of the given variable. Note that it is not needed
// to call this if the status or the bound of a variable didn't change.
void UpdateToBasicStatus(ColIndex col);
void UpdateToNonBasicStatus(ColIndex col, VariableStatus status);
// Various getter, see the corresponding member declaration below for more
// information.
const VariableTypeRow& GetTypeRow() const;
const VariableStatusRow& GetStatusRow() const;
const DenseBitRow& GetCanIncreaseBitRow() const;
const DenseBitRow& GetCanDecreaseBitRow() const;
const DenseBitRow& GetIsRelevantBitRow() const;
const DenseBitRow& GetIsBasicBitRow() const;
const DenseBitRow& GetNotBasicBitRow() const;
const DenseBitRow& GetNonBasicBoxedVariables() const;
// Returns the variable bounds.
const DenseRow& GetVariableLowerBounds() const { return lower_bounds_; }
const DenseRow& GetVariableUpperBounds() const { return upper_bounds_; }
ColIndex GetNumberOfColumns() const { return matrix_.num_cols(); }
// Changes whether or not a non-basic boxed variable is 'relevant' and will be
// returned as such by GetIsRelevantBitRow().
void MakeBoxedVariableRelevant(bool value);
// This is used in UpdateRow to decide whether to compute it using the
// row-wise or column-wise representation.
EntryIndex GetNumEntriesInRelevantColumns() const;
// Returns the distance between the upper and lower bound of the given column.
Fractional GetBoundDifference(ColIndex col) const {
return upper_bounds_[col] - lower_bounds_[col];
}
// This is used for the (SP) method of "Progress in the dual simplex method
// for large scale LP problems: practical dual phase I algorithms". Achim
// Koberstein & Uwe H. Suhl.
//
// This just set the bounds according to the variable types:
// - Boxed variables get fixed at [0,0].
// - Upper bounded variables get [-1, 0] bounds
// - Lower bounded variables get [0, 1] bounds
// - Free variables get [-1000, 1000] to heuristically move them to the basis.
// I.e. they cost in the dual infeasibility minimization problem is
// multiplied by 1000.
//
// It then update the status to get an initial dual feasible solution, and
// then one just have to apply the phase II algo on this problem to try to
// find a feasible solution to the original problem.
//
// Optimization: When a variable become basic, its non-zero bounds are
// relaxed. This is a bit hacky as it requires that the status is updated
// before the bounds are read (which is the case). It is however an important
// optimization.
//
// TODO(user): Shall we re-add the bound when the variable is moved out of
// the base? it is not needed, but might allow for more bound flips?
void TransformToDualPhaseIProblem(Fractional dual_feasibility_tolerance,
DenseRow::ConstView reduced_costs);
void EndDualPhaseI(Fractional dual_feasibility_tolerance,
DenseRow::ConstView reduced_costs);
// Advanced incremental API to reuse directly the internal storage.
// This saves two copy per solves, and only matter on large easy problems.
void InitializeFromMutatedState();
DenseRow* MutableLowerBounds() { return &lower_bounds_; }
DenseRow* MutableUpperBounds() { return &upper_bounds_; }
private:
// Computes the initial/default variable status from its type. A constrained
// variable is set to the lowest of its 2 bounds in absolute value.
VariableStatus DefaultVariableStatus(ColIndex col) const;
// Resizes all status related vectors.
void ResetStatusInfo();
// Computes the variable type from its lower and upper bound.
VariableType ComputeVariableType(ColIndex col) const;
// Sets the column relevance and updates num_entries_in_relevant_columns_.
void SetRelevance(ColIndex col, bool relevance);
// Used by TransformToDualPhaseIProblem()/EndDualPhaseI().
void UpdateStatusForNewType(ColIndex col);
// Problem data that should be updated from outside.
const CompactSparseMatrix& matrix_;
// The variables bounds of the current problem. Like everything here, it
// include the slacks.
DenseRow lower_bounds_;
DenseRow upper_bounds_;
// This is just used temporarily by the dual phase I algo to save the original
// bounds.
DenseRow saved_lower_bounds_;
DenseRow saved_upper_bounds_;
// Array of variable statuses, indexed by column index.
VariableStatusRow variable_status_;
// Array of variable types, indexed by column index.
VariableTypeRow variable_type_;
// Indicates if a non-basic variable can move up or down while not increasing
// the primal infeasibility. Note that all combinaisons are possible for a
// variable according to its status: fixed, free, upper or lower bounded. This
// is always false for basic variable.
DenseBitRow can_increase_;
DenseBitRow can_decrease_;
// Indicates if we should consider this variable for entering the basis during
// the simplex algorithm. We never consider fixed variables and in the dual
// feasibility phase, we don't consider boxed variable.
DenseBitRow relevance_;
// Indicates if a variable is BASIC or not. There are currently two members
// because the DenseBitRow class only supports a nice range-based iteration on
// the non-zero positions and not on the others.
DenseBitRow is_basic_;
DenseBitRow not_basic_;
// Set of boxed variables that are non-basic.
DenseBitRow non_basic_boxed_variables_;
// Number of entries for the relevant matrix columns (see relevance_).
EntryIndex num_entries_in_relevant_columns_;
// Whether or not a boxed variable should be considered relevant.
bool boxed_variables_are_relevant_ = true;
// Whether we are between the calls TransformToDualPhaseIProblem() and
// EndDualPhaseI().
bool in_dual_phase_one_ = false;
};
} // namespace glop
} // namespace operations_research
#endif // ORTOOLS_GLOP_VARIABLES_INFO_H_