diff --git a/examples/C++/SimpleUnconstrProblem/main.cpp b/examples/C++/SimpleUnconstrProblem/main.cpp index 3d58ebbb13..d3a0cef1bb 100644 --- a/examples/C++/SimpleUnconstrProblem/main.cpp +++ b/examples/C++/SimpleUnconstrProblem/main.cpp @@ -7,12 +7,12 @@ // Problem specification // minimize (a - x)² + b(y - x²)² struct RosenbrockProblem : alpaqa::UnconstrProblem { + // Specify the number of unknowns + RosenbrockProblem() : UnconstrProblem{2} {} + // Problem parameters real_t a = 2, b = 100; - // Number of unknowns - length_t get_n() const { return 2; } - // Cost real_t eval_f(crvec xy) const { auto x = xy(0), y = xy(1); diff --git a/interfaces/python/src/problem/problems.py.cpp b/interfaces/python/src/problem/problems.py.cpp index 1ca819759d..f70399c17e 100644 --- a/interfaces/python/src/problem/problems.py.cpp +++ b/interfaces/python/src/problem/problems.py.cpp @@ -265,10 +265,10 @@ void register_problems(py::module_ &m) { py::cast(t[3]), }; })) - .def_readonly("n", &BoxConstrProblem::n, - "Number of decision variables, dimension of :math:`x`") - .def_readonly("m", &BoxConstrProblem::m, - "Number of general constraints, dimension of :math:`g(x)`") + .def_property_readonly("n", &BoxConstrProblem::get_n, + "Number of decision variables, dimension of :math:`x`") + .def_property_readonly("m", &BoxConstrProblem::get_m, + "Number of general constraints, dimension of :math:`g(x)`") .def("resize", &BoxConstrProblem::resize, "n"_a, "m"_a) .def_readwrite("C", &BoxConstrProblem::C, "Box constraints on :math:`x`") .def_readwrite("D", &BoxConstrProblem::D, "Box constraints on :math:`g(x)`") @@ -293,16 +293,22 @@ void register_problems(py::module_ &m) { m, "UnconstrProblem", "C++ documentation: :cpp:class:`alpaqa::UnconstrProblem`"); default_copy_methods(unconstr_problem); unconstr_problem // - .def(py::init<>()) + .def(py::init(), "n"_a, + ":param n: Number of unknowns") .def(py::pickle( - [](const UnconstrProblem &) { // __getstate__ - return py::make_tuple(); + [](const UnconstrProblem &self) { // __getstate__ + return py::make_tuple(self.n); }, [](py::tuple t) { // __setstate__ - if (t.size() != 0) + if (t.size() != 1) throw std::runtime_error("Invalid state!"); - return UnconstrProblem{}; + return UnconstrProblem{py::cast(t[0])}; })) + .def_property_readonly("n", &UnconstrProblem::get_n, + "Number of decision variables, dimension of :math:`x`") + .def_property_readonly("m", &UnconstrProblem::get_m, + "Number of general constraints, dimension of :math:`g(x)`") + .def("resize", &UnconstrProblem::resize, "n"_a) .def("eval_g", &UnconstrProblem::eval_g, "x"_a, "g"_a) .def("eval_grad_g_prod", &UnconstrProblem::eval_grad_g_prod, "x"_a, "y"_a, "grad_gxy"_a) .def("eval_jac_g", &UnconstrProblem::eval_jac_g, "x"_a, "inner_idx"_a, "outer_ptr"_a, diff --git a/src/alpaqa/include/alpaqa/problem/box-constr-problem.hpp b/src/alpaqa/include/alpaqa/problem/box-constr-problem.hpp index 31d49bdbb2..70a275a473 100644 --- a/src/alpaqa/include/alpaqa/problem/box-constr-problem.hpp +++ b/src/alpaqa/include/alpaqa/problem/box-constr-problem.hpp @@ -3,11 +3,14 @@ #include #include +#include + namespace alpaqa { /// Implements common problem functions for minimization problems with box /// constraints. Meant to be used as a base class for custom problem -/// implementations. +/// implementations. +/// Supports optional @f$ \ell_1 @f$-regularization. /// @ingroup grp_Problems template class BoxConstrProblem { @@ -20,6 +23,9 @@ class BoxConstrProblem { /// Number of constraints, dimension of g(x) and z length_t m; + /// Create a problem with inactive boxes @f$ (-\infty, +\infty) @f$, with + /// no @f$ \ell_1 @f$-regularization, and all general constraints handled + /// using ALM. BoxConstrProblem(length_t n, ///< Number of decision variables length_t m) ///< Number of constraints : n{n}, m{m} {} @@ -28,11 +34,20 @@ class BoxConstrProblem { : n{C.lowerbound.size()}, m{D.lowerbound.size()}, C{std::move(C)}, D{std::move(D)}, l1_reg{std::move(l1_reg)}, penalty_alm_split{penalty_alm_split} {} + /// Change the dimensions of the problem (number of decision variables and + /// number of constaints). + /// Destructive: resizes and/or resets the members @ref C, @ref D, + /// @ref l1_reg and @ref penalty_alm_split. void resize(length_t n, length_t m) { - this->n = n; - this->m = m; - C = Box{n}; - D = Box{m}; + if (std::exchange(this->n, n) != n) { + C = Box{n}; + if (l1_reg.size() > 1) + l1_reg.resize(0); + } + if (std::exchange(this->m, m) != m) { + D = Box{m}; + penalty_alm_split = 0; + } } BoxConstrProblem(const BoxConstrProblem &) = default; diff --git a/src/alpaqa/include/alpaqa/problem/unconstr-problem.hpp b/src/alpaqa/include/alpaqa/problem/unconstr-problem.hpp index 764fc9c498..955fa0210d 100644 --- a/src/alpaqa/include/alpaqa/problem/unconstr-problem.hpp +++ b/src/alpaqa/include/alpaqa/problem/unconstr-problem.hpp @@ -14,14 +14,40 @@ template class UnconstrProblem { public: USING_ALPAQA_CONFIG(Conf); - /// Number of constraints + + /// Number of decision variables, dimension of x + length_t n; + + /// @param n Number of decision variables + UnconstrProblem(length_t n) : n{n} {} + + /// Change the number of decision variables. + void resize(length_t n) { this->n = n; } + + UnconstrProblem(const UnconstrProblem &) = default; + UnconstrProblem &operator=(const UnconstrProblem &) = default; + UnconstrProblem(UnconstrProblem &&) noexcept = default; + UnconstrProblem &operator=(UnconstrProblem &&) noexcept = default; + + /// Number of decision variables, @ref n + length_t get_n() const { return n; } + /// Number of constraints (always zero) length_t get_m() const { return 0; } + /// No-op, no constraints. + /// @see @ref TypeErasedProblem::eval_g void eval_g(crvec, rvec) const {} + /// Constraint gradient is always zero. + /// @see @ref TypeErasedProblem::eval_grad_g_prod void eval_grad_g_prod(crvec, crvec, rvec grad) const { grad.setZero(); } + /// Constraint Jacobian is always empty. + /// @see @ref TypeErasedProblem::eval_jac_g void eval_jac_g(crvec, rindexvec, rindexvec, rvec) const {} + /// Constraint gradient is always zero. + /// @see @ref TypeErasedProblem::eval_grad_gi void eval_grad_gi(crvec, index_t, rvec grad_gi) const { grad_gi.setZero(); } + /// No proximal mapping, just a forward (gradient) step. /// @see @ref TypeErasedProblem::eval_prox_grad_step real_t eval_prox_grad_step(real_t γ, crvec x, crvec grad_ψ, rvec x̂, rvec p) const { p = -γ * grad_ψ; diff --git a/test_package/src/example.cpp b/test_package/src/example.cpp index 466fa67491..392f6bc810 100644 --- a/test_package/src/example.cpp +++ b/test_package/src/example.cpp @@ -5,7 +5,7 @@ int main() { USING_ALPAQA_CONFIG(alpaqa::DefaultConfig); struct Problem : alpaqa::UnconstrProblem { - length_t get_n() const { return 1; } + Problem() : UnconstrProblem{1} {} real_t eval_f(crvec) const { return 0; } void eval_grad_f(crvec, rvec gr) const { gr.setZero(); } };