Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ipopt preset: reduce the number of symbolic factorizations #103

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions .github/julia/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,15 @@ import Uno_jll
Create a new `AmplNLWriter.Optimizer` object that uses Uno as the backing
solver.
"""

function Optimizer(options = String["logger=SILENT"])
return AmplNLWriter.Optimizer(Uno_jll.amplexe, options)
end

# by default, ipopt preset
Optimizer_barrier() = Optimizer(["logger=SILENT", "max_iterations=10000"])

# filterslp preset
Optimizer_LP() = Optimizer(["logger=SILENT", "preset=filterslp", "max_iterations=10000"])

# This testset runs https://github.com/jump-dev/MINLPTests.jl
Expand All @@ -36,7 +41,7 @@ Optimizer_LP() = Optimizer(["logger=SILENT", "preset=filterslp", "max_iterations
# are meant to be "easy" in the sense that most NLP solvers can find the
# same global minimum, but a test failure can sometimes be allowed.
MINLPTests.test_nlp_expr(
Optimizer;
Optimizer_barrier;
exclude = [
# Remove once https://github.com/cvanaret/Uno/issues/39 is fixed
"005_010",
Expand Down Expand Up @@ -69,7 +74,12 @@ Optimizer_LP() = Optimizer(["logger=SILENT", "preset=filterslp", "max_iterations
# This function tests convex nonlinear programs. Test failures here should
# never be allowed, because even local NLP solvers should find the global
# optimum.
MINLPTests.test_nlp_cvx_expr(Optimizer; primal_target)
MINLPTests.test_nlp_cvx_expr(Optimizer_barrier;
exclude = [
# unidentified failure
"204_010"
],
primal_target)
MINLPTests.test_nlp_cvx_expr(
Optimizer_LP;
primal_target,
Expand All @@ -83,7 +93,7 @@ end
# tests in here with weird edge cases, so a variety of exclusions are expected.
@testset "MathOptInterface.test" begin
optimizer = MOI.instantiate(
Optimizer;
Optimizer_barrier;
with_cache_type = Float64,
with_bridge_type = Float64,
)
Expand Down
19 changes: 4 additions & 15 deletions bindings/AMPL/AMPLModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -270,31 +270,20 @@ namespace uno {

void AMPLModel::evaluate_lagrangian_hessian(const Vector<double>& x, double objective_multiplier, const Vector<double>& multipliers,
SymmetricMatrix<size_t, double>& hessian) const {
assert(hessian.capacity() >= this->number_asl_hessian_nonzeros);

// register the vector of variables
(*(this->asl)->p.Xknown)(this->asl, const_cast<double*>(x.data()), nullptr);

// scale by the objective sign
objective_multiplier *= this->objective_sign;

// compute the number of nonzeros
[[maybe_unused]] const size_t number_nonzeros = this->fixed_hessian_sparsity ? this->number_asl_hessian_nonzeros :
this->compute_hessian_number_nonzeros(objective_multiplier, multipliers);
assert(hessian.capacity() >= number_nonzeros);

// evaluate the Hessian: store the matrix in a preallocated array this->asl_hessian
const int objective_number = -1;
// flip the signs of the multipliers: in AMPL, the Lagrangian is f + lambda.g, while Uno uses f - lambda.g
this->multipliers_with_flipped_sign = -multipliers;
if (this->fixed_hessian_sparsity) {
(*(this->asl)->p.Sphes)(this->asl, nullptr, const_cast<double*>(this->asl_hessian.data()), objective_number, &objective_multiplier,
const_cast<double*>(this->multipliers_with_flipped_sign.data()));
}
else {
double* objective_multiplier_pointer = (objective_multiplier != 0.) ? &objective_multiplier : nullptr;
const bool all_zeros_multipliers = are_all_zeros(multipliers);
(*(this->asl)->p.Sphes)(this->asl, nullptr, const_cast<double*>(this->asl_hessian.data()), objective_number, objective_multiplier_pointer,
all_zeros_multipliers ? nullptr : const_cast<double*>(this->multipliers_with_flipped_sign.data()));
}
(*(this->asl)->p.Sphes)(this->asl, nullptr, const_cast<double*>(this->asl_hessian.data()), objective_number, &objective_multiplier,
const_cast<double*>(this->multipliers_with_flipped_sign.data()));

// generate the sparsity pattern in the right sparse format
const fint* asl_column_start = this->asl->i.sputinfo_->hcolstarts;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,11 @@ namespace uno {
// set the bound multipliers
for (const size_t variable_index: problem.get_lower_bounded_variables()) {
initial_iterate.multipliers.lower_bounds[variable_index] = this->default_multiplier;
//initial_iterate.feasibility_multipliers.lower_bounds[variable_index] = this->default_multiplier;
}
for (const size_t variable_index: problem.get_upper_bounded_variables()) {
initial_iterate.multipliers.upper_bounds[variable_index] = -this->default_multiplier;
//initial_iterate.feasibility_multipliers.upper_bounds[variable_index] = -this->default_multiplier;
}

// compute least-square multipliers
Expand Down Expand Up @@ -199,7 +201,7 @@ namespace uno {
this->evaluate_functions(statistics, problem, current_iterate, current_multipliers, warmstart_information);

// compute the primal-dual solution
this->assemble_augmented_system(statistics, problem, current_multipliers);
this->assemble_augmented_system(statistics, problem, current_multipliers, warmstart_information);
this->augmented_system.solve(*this->linear_solver);
assert(direction.status == SubproblemStatus::OPTIMAL && "The primal-dual perturbed subproblem was not solved to optimality");
this->number_subproblems_solved++;
Expand All @@ -209,12 +211,12 @@ namespace uno {
}

void PrimalDualInteriorPointSubproblem::assemble_augmented_system(Statistics& statistics, const OptimizationProblem& problem,
const Multipliers& current_multipliers) {
const Multipliers& current_multipliers, const WarmstartInformation& warmstart_information) {
// assemble, factorize and regularize the augmented matrix
this->augmented_system.assemble_matrix(this->hessian_model->hessian, this->constraint_jacobian, problem.number_variables, problem.number_constraints);
this->augmented_system.factorize_matrix(problem.model, *this->linear_solver);
this->augmented_system.factorize_matrix(*this->linear_solver, warmstart_information);
const double dual_regularization_parameter = std::pow(this->barrier_parameter(), this->parameters.regularization_exponent);
this->augmented_system.regularize_matrix(statistics, problem.model, *this->linear_solver, problem.number_variables, problem.number_constraints,
this->augmented_system.regularize_matrix(statistics, *this->linear_solver, problem.number_variables, problem.number_constraints,
dual_regularization_parameter);

// check the inertia
Expand Down Expand Up @@ -525,4 +527,4 @@ namespace uno {
void PrimalDualInteriorPointSubproblem::set_initial_point(const Vector<double>& /*point*/) {
// do nothing
}
} // namespace
} // namespace
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ namespace uno {
const Vector<double>& primal_direction, double tau);
[[nodiscard]] static double dual_fraction_to_boundary(const OptimizationProblem& problem, const Multipliers& current_multipliers,
Multipliers& direction_multipliers, double tau);
void assemble_augmented_system(Statistics& statistics, const OptimizationProblem& problem, const Multipliers& current_multipliers);
void assemble_augmented_system(Statistics& statistics, const OptimizationProblem& problem, const Multipliers& current_multipliers, const WarmstartInformation& warmstart_information);
void assemble_augmented_rhs(const OptimizationProblem& problem, const Multipliers& current_multipliers);
void assemble_primal_dual_direction(const OptimizationProblem& problem, const Vector<double>& current_primals, const Multipliers& current_multipliers,
Vector<double>& direction_primals, Multipliers& direction_multipliers);
Expand All @@ -89,4 +89,4 @@ namespace uno {
};
} // namespace

#endif // UNO_INFEASIBLEINTERIORPOINTSUBPROBLEM_H
#endif // UNO_INFEASIBLEINTERIORPOINTSUBPROBLEM_H
27 changes: 14 additions & 13 deletions uno/linear_algebra/SymmetricIndefiniteLinearSystem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
#include "SparseStorageFactory.hpp"
#include "RectangularMatrix.hpp"
#include "ingredients/hessian_models/UnstableRegularization.hpp"
#include "model/Model.hpp"
#include "solvers/DirectSymmetricIndefiniteLinearSolver.hpp"
#include "optimization/WarmstartInformation.hpp"
#include "options/Options.hpp"
#include "solvers/DirectSymmetricIndefiniteLinearSolver.hpp"
#include "tools/Statistics.hpp"

namespace uno {
Expand All @@ -26,8 +26,8 @@ namespace uno {
const Options& options);
void assemble_matrix(const SymmetricMatrix<size_t, double>& hessian, const RectangularMatrix<double>& constraint_jacobian,
size_t number_variables, size_t number_constraints);
void factorize_matrix(const Model& model, DirectSymmetricIndefiniteLinearSolver<size_t, ElementType>& linear_solver);
void regularize_matrix(Statistics& statistics, const Model& model, DirectSymmetricIndefiniteLinearSolver<size_t, ElementType>& linear_solver,
void factorize_matrix(DirectSymmetricIndefiniteLinearSolver<size_t, ElementType>& linear_solver, const WarmstartInformation& warmstart_information);
void regularize_matrix(Statistics& statistics, DirectSymmetricIndefiniteLinearSolver<size_t, ElementType>& linear_solver,
size_t size_primal_block, size_t size_dual_block, ElementType dual_regularization_parameter);
void solve(DirectSymmetricIndefiniteLinearSolver<size_t, ElementType>& linear_solver);
// [[nodiscard]] T get_primal_regularization() const;
Expand Down Expand Up @@ -89,33 +89,34 @@ namespace uno {
}

template <typename ElementType>
void SymmetricIndefiniteLinearSystem<ElementType>::factorize_matrix(const Model& model,
DirectSymmetricIndefiniteLinearSolver<size_t, ElementType>& linear_solver) {
void SymmetricIndefiniteLinearSystem<ElementType>::factorize_matrix(DirectSymmetricIndefiniteLinearSolver<size_t, ElementType>& linear_solver, const WarmstartInformation& warmstart_information) {
// compute the symbolic factorization only when:
// the problem has a non-constant augmented system (ie is not an LP or a QP) or it is the first factorization
if (true || this->number_factorizations == 0 || not model.fixed_hessian_sparsity) {
if (warmstart_information.problem_changed) {
DEBUG << "Symbolic factorization of the indefinite linear system\n";
linear_solver.do_symbolic_factorization(this->matrix);
}
DEBUG << "Numerical factorization of the indefinite linear system\n";
linear_solver.do_numerical_factorization(this->matrix);
this->number_factorizations++;
}

template <typename ElementType>
void SymmetricIndefiniteLinearSystem<ElementType>::regularize_matrix(Statistics& statistics, const Model& model,
DirectSymmetricIndefiniteLinearSolver<size_t, ElementType>& linear_solver, size_t size_primal_block, size_t size_dual_block,
void SymmetricIndefiniteLinearSystem<ElementType>::regularize_matrix(Statistics& statistics, DirectSymmetricIndefiniteLinearSolver<size_t, ElementType>& linear_solver, size_t size_primal_block, size_t size_dual_block,
ElementType dual_regularization_parameter) {
DEBUG2 << "Original matrix\n" << this->matrix << '\n';
this->primal_regularization = ElementType(0.);
this->dual_regularization = ElementType(0.);
size_t number_attempts = 1;
DEBUG << "Testing factorization with regularization factors (" << this->primal_regularization << ", " << this->dual_regularization << ")\n";

auto [number_pos_eigenvalues, number_neg_eigenvalues, number_zero_eigenvalues] = linear_solver.get_inertia();
DEBUG << "Inertia (" << number_pos_eigenvalues << ", " << number_neg_eigenvalues << ", " << number_zero_eigenvalues << ")\n";
if (not linear_solver.matrix_is_singular() && linear_solver.number_negative_eigenvalues() == size_dual_block) {
DEBUG << "Inertia is good\n";
DEBUG << "Inertia is correct\n";
statistics.set("regulariz", this->primal_regularization);
return;
}
auto [number_pos_eigenvalues, number_neg_eigenvalues, number_zero_eigenvalues] = linear_solver.get_inertia();
DEBUG << "Expected inertia (" << size_primal_block << ", " << size_dual_block << ", 0), ";
DEBUG << "got (" << number_pos_eigenvalues << ", " << number_neg_eigenvalues << ", " << number_zero_eigenvalues << ")\n";
DEBUG << "Number of attempts: " << number_attempts << "\n\n";
Expand Down Expand Up @@ -143,7 +144,7 @@ namespace uno {
while (not good_inertia) {
DEBUG << "Testing factorization with regularization factors (" << this->primal_regularization << ", " << this->dual_regularization << ")\n";
DEBUG2 << this->matrix << '\n';
this->factorize_matrix(model, linear_solver);
linear_solver.do_numerical_factorization(this->matrix);
number_attempts++;

if (not linear_solver.matrix_is_singular() && linear_solver.number_negative_eigenvalues() == size_dual_block) {
Expand Down Expand Up @@ -190,4 +191,4 @@ namespace uno {
*/
} // namespace

#endif // UNO_SYMMETRICINDEFINITELINEARSYSTEM_H
#endif // UNO_SYMMETRICINDEFINITELINEARSYSTEM_H
3 changes: 0 additions & 3 deletions uno/model/Model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@ namespace uno {
const size_t number_constraints; /*!< Number of constraints */
const double objective_sign; /*!< Sign of the objective function (1: minimization, -1: maximization) */

// Hessian
const bool fixed_hessian_sparsity{true};

[[nodiscard]] virtual double evaluate_objective(const Vector<double>& x) const = 0;
virtual void evaluate_objective_gradient(const Vector<double>& x, SparseVector<double>& gradient) const = 0;
virtual void evaluate_constraints(const Vector<double>& x, std::vector<double>& constraints) const = 0;
Expand Down
3 changes: 1 addition & 2 deletions uno/solvers/MUMPS/MUMPSSolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ namespace uno {
#if defined(HAS_MPI) && defined(MUMPS_PARALLEL)
// TODO load number of processes from option file
this->mumps_structure.par = 1;
#endif
#ifdef MUMPS_SEQUENTIAL
#else
this->mumps_structure.par = 1;
#endif
this->mumps_structure.job = MUMPSSolver::JOB_INIT;
Expand Down
Loading