13#ifndef OPTIMIST_SOLVER_HH
14#define OPTIMIST_SOLVER_HH
42 template <
typename Real, Integer SolInDim, Integer SolOutDim,
typename DerivedSolver,
bool ForceEigen = false>
47 static_assert(SolInDim > 0 && SolOutDim > 0,
48 "Negative-dimensional optimization problem? Are you serious?");
53 using
InputType = typename std::conditional_t<ForceEigen || (SolInDim > 1),
54 Eigen::Vector<Real, SolInDim>, Real>;
55 using
OutputType = typename std::conditional_t<ForceEigen || (SolOutDim > 1),
56 Eigen::Vector<Real, SolOutDim>, Real>;
63 Eigen::Matrix<Real, SolOutDim, SolInDim>, Real>;
65 std::conditional_t<SolInDim == 1 || SolOutDim == 1, Eigen::Matrix<Real, SolInDim, SolInDim>,
66 std::vector<Eigen::Matrix<Real, SolInDim, SolInDim>>>, Real>;
106 if constexpr (ForceEigen || SolInDim > 1) {
113 this->m_trace.reserve(this->m_max_iterations * this->m_max_relaxations);
123 template <
typename FunctionLambda>
125 static_cast<const DerivedSolver *
>(
this)->solve_impl(
126 std::forward<FunctionLambda>(function),
139 template <
typename FunctionLambda,
typename FirstDerivativeLambda>
140 SolverBase(FunctionLambda && function, FirstDerivativeLambda && first_derivative,
InputType const & x_ini,
143 static_cast<const DerivedSolver *
>(
this)->solve_impl(
144 std::forward<FunctionLambda>(function),
145 std::forward<FirstDerivativeLambda>(first_derivative),
160 template <
typename FunctionLambda,
typename FirstDerivativeLambda,
typename SecondDerivativeLambda>
161 SolverBase(FunctionLambda && function, FirstDerivativeLambda && first_derivative, SecondDerivativeLambda
164 static_cast<const DerivedSolver *
>(
this)->solve_impl(
165 std::forward<FunctionLambda>(function),
166 std::forward<FirstDerivativeLambda>(first_derivative),
167 std::forward<SecondDerivativeLambda>(second_derivative),
182 #define CMD "Optimist::Solver::bounds(...): "
184 if constexpr (ForceEigen || SolInDim > 1) {
186 CMD "invalid or degenarate bounds detected.");
189 CMD "invalid or degenarate bounds detected.");
207 #define CMD "Optimist::Solver::bounds(...): "
209 if constexpr (ForceEigen || SolInDim > 1) {
211 CMD "invalid or degenarate bounds detected.");
214 CMD "invalid or degenarate bounds detected.");
228 #define CMD "Optimist::Solver::bounds(...): "
230 if constexpr (ForceEigen || SolInDim > 1) {
232 CMD "invalid or degenarate bounds detected.");
235 CMD "invalid or degenarate bounds detected.");
268 "Optimist::Solver::max_function_evaluations(...): invalid input detected.");
269 this->m_max_function_evaluations = t_max_function_evaluations;
298 "Optimist::Solver::max_first_derivative_evaluations(...): invalid input detected.");
321 "Optimist::Solver::max_second_derivative_evaluations(...): invalid input detected.");
343 "Optimist::Solver::max_iterations(...): invalid input detected.");
344 this->m_max_iterations = t_max_iterations;
351 Real
alpha()
const {
return this->m_alpha;}
360 !std::isnan(t_alpha) && std::isfinite(t_alpha) && 0.0 <= t_alpha && t_alpha <= 1.0,
361 "Optimist::Solver::alpha(...): invalid input detected.");
362 this->m_alpha = t_alpha;
384 "Optimist::Solver::max_relaxations(...): invalid input detected.");
385 this->m_max_relaxations = t_max_relaxations;
400 OPTIMIST_ASSERT(!std::isnan(t_tolerance) && std::isfinite(t_tolerance) && t_tolerance > 0.0,
401 "Optimist::Solver::tolerance(...): invalid input detected.");
402 this->m_tolerance = t_tolerance;
453 std::string
task()
const {
return this->m_task;}
459 void task(std::string t_task) {this->m_task = t_task;}
477 std::ostream &
ostream()
const {
return *this->m_ostream;}
483 void ostream(std::ostream & t_ostream) {this->m_ostream = &t_ostream;}
493 template <
typename FunctionLambda>
496 #define CMD "Optimist::Solver::solve(...): "
498 static_assert(DerivedSolver::requires_function,
499 CMD "the solver requires a function.");
500 return static_cast<DerivedSolver *
>(
this)->
solve(
501 std::forward<FunctionLambda>(function),
502 nullptr,
nullptr, x_ini, x_sol);
517 template <
typename FunctionLambda,
typename FirstDerivativeLambda>
518 bool solve(FunctionLambda && function, FirstDerivativeLambda && first_derivative,
InputType const & x_ini,
521 #define CMD "Optimist::Solver::solve(...): "
523 static_assert(DerivedSolver::requires_function,
524 CMD "the solver requires a function.");
525 static_assert(DerivedSolver::requires_first_derivative,
526 CMD "the solver requires the first derivative.");
527 return static_cast<DerivedSolver *
>(
this)->
solve(
528 std::forward<FunctionLambda>(function),
529 std::forward<FirstDerivativeLambda>(first_derivative),
530 nullptr, x_ini, x_sol);
547 template <
typename FunctionLambda,
typename FirstDerivativeLambda,
typename SecondDerivativeLambda>
548 bool solve(FunctionLambda && function, FirstDerivativeLambda && first_derivative, SecondDerivativeLambda
551 #define CMD "Optimist::Solver::solve(...): "
553 static_assert(DerivedSolver::requires_function,
554 CMD "the solver requires the function.");
555 static_assert(DerivedSolver::requires_first_derivative,
556 CMD "the solver requires the first derivative.");
557 static_assert(DerivedSolver::requires_second_derivative,
558 CMD "the solver requires the second derivative.");
559 return static_cast<DerivedSolver *
>(
this)->
solve(
560 std::forward<FunctionLambda>(function),
561 std::forward<FirstDerivativeLambda>(first_derivative),
562 std::forward<SecondDerivativeLambda>(second_derivative),
578 template <Integer FunInDim, Integer FunOutDim,
typename DerivedFunction>
582 #define CMD "Optimist::Solver::rootfind(...): "
584 static_assert(SolInDim == FunInDim,
585 CMD "solver input dimension must be equal to the function input dimension.");
586 static_assert(SolOutDim == FunOutDim || SolOutDim == 1,
587 CMD "solver output dimension must be equal to the function output dimension or 1.");
588 static_assert(!(SolInDim == 1 && DerivedSolver::is_optimizer),
589 CMD "one-dimensional optimizers do not support root-finding problems.");
590 return this->
solve(function, x_ini, x_sol, SolOutDim != FunOutDim || (SolInDim > 1 && SolOutDim == 1));
605 template <Integer FunInDim, Integer FunOutDim,
typename DerivedFunction>
609 #define CMD "Optimist::Solver::optimize(...): "
611 static_assert(SolInDim == FunInDim,
612 CMD "solver input dimension must be equal to the function input dimension.");
613 static_assert(SolOutDim == 1,
614 CMD "solver output dimension must be equal to the function output dimension or 1.");
615 static_assert(!(SolInDim == 1 && DerivedSolver::is_rootfinder),
616 CMD "one-dimensional root-finders do not support optimization problems.");
617 return this->
solve(function, x_ini, x_sol,
true);
626 std::string
name()
const {
return static_cast<const DerivedSolver *
>(
this)->name_impl();};
640 template <Integer FunInDim, Integer FunOutDim,
typename DerivedFunction>
644 #define CMD "Optimist::Solver::solve(...): "
648 static_assert(SolInDim == FunInDim,
649 CMD "solver input dimension must be equal to the function input dimension.");
650 static_assert(SolOutDim == FunOutDim || SolOutDim == 1,
651 CMD "solver output dimension must be equal to the function output dimension or 1.");
654 auto function_lambda = [&function, is_optimization] (
InputType const & x,
OutputType & out) ->
bool
657 typename FunctionType::OutputType f; success = function.
evaluate(x, f);
659 CMD "function evaluation failed during function computation.");
661 if (is_optimization) {
662 if constexpr (FunOutDim == 1) {out = 0.5*f*f;}
663 else if constexpr (SolOutDim != FunOutDim) {out = 0.5*f.squaredNorm();}
664 else {
OPTIMIST_ERROR(
CMD "optimization problem with inconsistent output in function.");}
666 if constexpr (SolOutDim == FunOutDim) {out = f;}
667 else {
OPTIMIST_ERROR(
CMD "root-finding problem with inconsistent output in function.");}
672 auto first_derivative_lambda = [&function, is_optimization,
this] (
InputType const & x,
676 typename FunctionType::FirstDerivativeType J; success = function.
first_derivative(x, J);
678 CMD "first derivative evaluation failed during first derivative computation.");
680 if (is_optimization) {
682 typename FunctionType::OutputType f; success = function.
evaluate(x, f);
684 CMD "function evaluation failed during first derivative computation.");
686 if constexpr (FunInDim == 1 && FunOutDim == 1) {out = J*f;}
687 else if constexpr (SolOutDim != FunOutDim) {out = J.transpose()*f;}
688 else {
OPTIMIST_ERROR(
CMD "optimization problem inconsistent output in first derivative.");}
690 if constexpr (SolOutDim == FunOutDim) {out = J;}
691 else {
OPTIMIST_ERROR(
CMD "root-finding problem with inconsistent output in first derivative.");}
696 auto second_derivative_lambda = [&function, is_optimization,
this] (
InputType const & x,
700 typename FunctionType::SecondDerivativeType H; success = function.
second_derivative(x, H);
702 CMD "function evaluation failed during second derivative computation.");
704 if (is_optimization) {
705 typename FunctionType::OutputType f; success = function.
evaluate(x, f);
708 CMD "function evaluation failed during second derivative computation.");
709 typename FunctionType::FirstDerivativeType J; success = function.
first_derivative(x, J);
710 this->m_first_derivative_evaluations++;
712 CMD "first derivative evaluation failed during second derivative computation.");
713 if constexpr (FunInDim == 1 && FunOutDim == 1) {out = J*J + f*H;}
714 else if constexpr (SolOutDim != FunOutDim) {
715 out = J.transpose()*J;
716 for (
Integer i{0}; i < static_cast<Integer>(H.size()); ++i) {out += f(i)*H[i];}
718 else {
OPTIMIST_ERROR(
CMD "optimization problem with inconsistent output in second derivative.");}
720 if constexpr (SolOutDim == FunOutDim) {out = H;}
721 else {
OPTIMIST_ERROR(
CMD "root-finding problem with inconsistent output in second derivative.");}
727 if constexpr (DerivedSolver::requires_function && !DerivedSolver::requires_first_derivative &&
728 !DerivedSolver::requires_second_derivative) {
729 return static_cast<DerivedSolver *
>(
this)->
solve(function_lambda, x_ini, x_sol);
730 }
else if constexpr (DerivedSolver::requires_function && DerivedSolver::requires_first_derivative &&
731 !DerivedSolver::requires_second_derivative) {
732 return static_cast<DerivedSolver *
>(
this)->
solve(function_lambda, first_derivative_lambda,
734 }
else if constexpr (DerivedSolver::requires_function && DerivedSolver::requires_first_derivative &&
735 DerivedSolver::requires_second_derivative) {
736 return static_cast<DerivedSolver *
>(
this)->
solve(function_lambda, first_derivative_lambda,
737 second_derivative_lambda, x_ini, x_sol);
751 this->m_first_derivative_evaluations = 0;
752 this->m_second_derivative_evaluations = 0;
753 this->m_iterations = 0;
754 this->m_relaxations = 0;
755 this->m_converged =
false;
756 this->m_trace.clear();
767 template <
typename FunctionLambda>
771 "Optimist::" << this->
name() <<
"::evaluate_function(...): maximum allowed function evaluations reached.");
773 return function(x, out);
784 template <
typename FirstDerivativeLambda>
788 "Optimist::" << this->
name() <<
"::evaluate_first_derivative(...): maximum allowed first derivative evaluations reached.");
789 ++this->m_first_derivative_evaluations;
790 return function(x, out);
800 template <
typename SecondDerivativeLambda>
804 "Optimist::" << this->
name() <<
"::evaluate_second_derivative(...): maximum allowed second derivative evaluations reached.");
805 ++this->m_second_derivative_evaluations;
806 return function(x, out);
827 template <
typename FunctionLambda>
831 #define CMD "Optimist::Solver::damp(...): "
833 Real step_norm_old, step_norm_new, residuals_old, residuals_new, tau{1.0};
834 for (this->m_relaxations = 0; this->m_relaxations < this->m_max_relaxations; ++this->m_relaxations)
837 step_new = tau * step_old;
838 x_new = x_old + step_new;
839 bool success{this->
evaluate_function(std::forward<FunctionLambda>(function), x_new, function_new)};
841 CMD "function evaluation failed during damping.");
844 if constexpr (ForceEigen || (SolInDim > 1 && SolOutDim > 1)) {
845 residuals_old = function_old.norm();
846 residuals_new = function_new.norm();
847 step_norm_old = step_old.norm();
848 step_norm_new = step_new.norm();
850 residuals_old = std::abs(function_old);
851 residuals_new = std::abs(function_new);
852 step_norm_old = std::abs(step_old);
853 step_norm_new = std::abs(step_new);
855 if (residuals_new < residuals_old || step_norm_new < (Real(1.0)-tau/Real(2.0))*step_norm_old) {
858 tau *= this->m_alpha;
887 << c_tl << h_78 << c_tr << std::endl
888 << v_ll <<
"Solver:" << std::setw(69) << this->
name() << v_rr << std::endl
889 << v_ll <<
"Task: " << std::setw(69) << this->
task() << v_rr << std::endl
890 << j_ll << h_7 << j_tt << h_7 << j_tt << h_7 << j_tt << h_7 << j_tt << h_7 << j_tt << h_14 << j_tt << h_23 << j_rr << std::endl
891 << v_ll <<
"#Iter" << v_lc <<
" #f" << v_lc <<
" #Df" << v_lc <<
" #DDf" << v_lc <<
" #Rlx" << v_lc
892 <<
" ║f(x)║₂ " << v_lc <<
"Additional notes" << std::setw(9) << v_rr << std::endl
893 << j_ll << h_7 << j_cc << h_7 << j_cc << h_7 << j_cc << h_7 << j_cc << h_7 << j_cc << h_14 << j_cc << h_23 << j_rr << std::endl;
915 << j_ll << h_7 << j_bb << h_7 << j_bb << h_7 << j_bb << h_7 << j_bb << h_7 << j_bb << h_14 << j_bb << h_23 << j_rr << std::endl
916 << v_ll << std::setw(40) << (this->m_converged ?
"CONVERGED" :
"NOT CONVERGED") << std::setw(40) << v_rr << std::endl
917 << c_bl << h_78 << c_br << std::endl;
924 void info(Real residuals, std::string
const & notes =
"-")
930 *this->m_ostream << v_ll
931 << std::setw(5) << this->m_iterations << v_lc
933 << std::setw(5) << this->m_first_derivative_evaluations << v_lc
934 << std::setw(5) << this->m_second_derivative_evaluations << v_lc
935 << std::setw(5) << this->m_relaxations << v_lc
936 << std::setw(12) << std::scientific << std::setprecision(6) << residuals << v_lc
937 << notes << std::setw(25-notes.length()) << v_rr << std::endl;
#define OPTIMIST_ERROR(MSG)
Definition Optimist.hh:34
#define OPTIMIST_BASIC_CONSTANTS(Real)
Definition Optimist.hh:71
#define OPTIMIST_ASSERT(COND, MSG)
Definition Optimist.hh:44
Class container for the generic function.
Definition Function.hh:43
bool second_derivative(const InputType &x, SecondDerivativeType &out) const
Definition Function.hh:108
bool first_derivative(const InputType &x, FirstDerivativeType &out) const
Definition Function.hh:97
bool evaluate(const InputType &x, OutputType &out) const
Definition Function.hh:86
Integer max_iterations() const
Definition SolverBase.hh:335
typename std::vector< InputType > TraceType
Definition SolverBase.hh:59
bool m_damped
Definition SolverBase.hh:93
bool evaluate_second_derivative(SecondDerivativeLambda &&function, InputType const &x, SecondDerivativeType &out)
Definition SolverBase.hh:801
void lower_bound(InputType const &t_lower_bound)
Definition SolverBase.hh:181
void max_second_derivative_evaluations(Integer second_derivative_evaluations)
Definition SolverBase.hh:318
void ostream(std::ostream &t_ostream)
Definition SolverBase.hh:483
bool converged() const
Definition SolverBase.hh:465
InputType const & upper_bound() const
Definition SolverBase.hh:200
void alpha(Real t_alpha)
Definition SolverBase.hh:357
Integer max_first_derivative_evaluations() const
Definition SolverBase.hh:289
void max_iterations(Integer t_max_iterations)
Definition SolverBase.hh:341
void info(Real residuals, std::string const ¬es="-")
Definition SolverBase.hh:924
constexpr Integer output_dimension() const
Definition SolverBase.hh:253
const TraceType & trace() const
Definition SolverBase.hh:471
Integer m_iterations
Definition SolverBase.hh:84
Integer max_relaxations() const
Definition SolverBase.hh:375
typename std::conditional_t< ForceEigen||(SolOutDim > 1), Eigen::Vector< Real, SolOutDim >, Real > OutputType
Definition SolverBase.hh:55
Real alpha() const
Definition SolverBase.hh:351
bool damp(FunctionLambda &&function, InputType const &x_old, InputType const &function_old, InputType const &step_old, InputType &x_new, InputType &function_new, InputType &step_new)
Definition SolverBase.hh:828
Integer m_max_iterations
Definition SolverBase.hh:85
std::string task() const
Definition SolverBase.hh:453
void enable_damped_mode()
Definition SolverBase.hh:442
SolverBase(FunctionLambda &&function, FirstDerivativeLambda &&first_derivative, SecondDerivativeLambda &&second_derivative, InputType const &x_ini, InputType &x_sol)
Definition SolverBase.hh:161
Integer relaxations() const
Definition SolverBase.hh:369
SolverBase(FunctionLambda &&function, FirstDerivativeLambda &&first_derivative, InputType const &x_ini, InputType &x_sol)
Definition SolverBase.hh:140
void verbose_mode(bool t_verbose)
Definition SolverBase.hh:409
void store_trace(InputType const &x)
Definition SolverBase.hh:813
std::string name() const
Definition SolverBase.hh:626
void header()
Definition SolverBase.hh:870
Integer max_second_derivative_evaluations() const
Definition SolverBase.hh:312
Integer function_evaluations() const
Definition SolverBase.hh:259
Real tolerance() const
Definition SolverBase.hh:392
Integer iterations() const
Definition SolverBase.hh:329
bool solve(FunctionBase< Real, FunInDim, FunOutDim, DerivedFunction, ForceEigen &&FunOutDim==1 > const &function, InputType const &x_ini, InputType &x_sol, bool is_optimization)
Definition SolverBase.hh:641
void reset()
Definition SolverBase.hh:748
void max_relaxations(Integer t_max_relaxations)
Definition SolverBase.hh:381
std::ostream & ostream() const
Definition SolverBase.hh:477
void bounds(InputType const &t_lower_bound, InputType const &t_upper_bound)
Definition SolverBase.hh:226
bool solve(FunctionLambda &&function, FirstDerivativeLambda &&first_derivative, InputType const &x_ini, InputType &x_sol)
Definition SolverBase.hh:518
void damped_mode(bool t_damped)
Definition SolverBase.hh:431
SolverBase(FunctionLambda &&function, InputType const &x_ini, InputType &x_sol)
Definition SolverBase.hh:124
Integer m_max_first_derivative_evaluations
Definition SolverBase.hh:80
bool m_verbose
Definition SolverBase.hh:92
Integer m_max_second_derivative_evaluations
Definition SolverBase.hh:81
bool m_converged
Definition SolverBase.hh:98
bool solve(FunctionLambda &&function, FirstDerivativeLambda &&first_derivative, SecondDerivativeLambda &&second_derivative, InputType const &x_ini, InputType &x_sol)
Definition SolverBase.hh:548
Real m_alpha
Definition SolverBase.hh:86
InputType m_upper_bound
Definition SolverBase.hh:71
void enable_verbose_mode()
Definition SolverBase.hh:420
void disable_damped_mode()
Definition SolverBase.hh:447
constexpr Integer input_dimension() const
Definition SolverBase.hh:247
Integer max_function_evaluations() const
Definition SolverBase.hh:276
TraceType m_trace
Definition SolverBase.hh:99
bool verbose_mode() const
Definition SolverBase.hh:415
Integer second_derivative_evaluations() const
Definition SolverBase.hh:306
void upper_bound(InputType const &t_upper_bound)
Definition SolverBase.hh:206
InputType const & lower_bound() const
Definition SolverBase.hh:175
void bottom()
Definition SolverBase.hh:900
SolverBase()
Definition SolverBase.hh:105
std::ostream * m_ostream
Definition SolverBase.hh:94
bool evaluate_function(FunctionLambda &&function, InputType const &x, OutputType &out)
Definition SolverBase.hh:768
Integer m_function_evaluations
Definition SolverBase.hh:74
std::conditional_t< ForceEigen||(SolInDim > 1)||(SolOutDim > 1), std::conditional_t< SolInDim==1||SolOutDim==1, Eigen::Matrix< Real, SolInDim, SolInDim >, std::vector< Eigen::Matrix< Real, SolInDim, SolInDim > > >, Real > SecondDerivativeType
Definition SolverBase.hh:64
Integer m_first_derivative_evaluations
Definition SolverBase.hh:75
Integer m_relaxations
Definition SolverBase.hh:87
void max_first_derivative_evaluations(Integer first_derivative_evaluations)
Definition SolverBase.hh:295
Real m_tolerance
Definition SolverBase.hh:91
std::string m_task
Definition SolverBase.hh:97
bool rootfind(FunctionBase< Real, FunInDim, FunOutDim, DerivedFunction, ForceEigen &&FunOutDim==1 > const &function, InputType const &x_ini, InputType &x_sol)
Definition SolverBase.hh:579
bool damped_mode() const
Definition SolverBase.hh:437
typename std::conditional_t< ForceEigen||(SolInDim > 1), Eigen::Vector< Real, SolInDim >, Real > InputType
Definition SolverBase.hh:53
Integer m_second_derivative_evaluations
Definition SolverBase.hh:76
std::conditional_t< ForceEigen||(SolInDim > 1)||(SolOutDim > 1), Eigen::Matrix< Real, SolOutDim, SolInDim >, Real > FirstDerivativeType
Definition SolverBase.hh:62
bool optimize(FunctionBase< Real, FunInDim, FunOutDim, DerivedFunction, ForceEigen &&FunOutDim==1 > const &function, InputType const &x_ini, InputType &x_sol)
Definition SolverBase.hh:606
Integer m_max_relaxations
Definition SolverBase.hh:88
Integer first_derivative_evaluations() const
Definition SolverBase.hh:283
void disable_verbose_mode()
Definition SolverBase.hh:425
void tolerance(Real t_tolerance)
Definition SolverBase.hh:399
Integer m_max_function_evaluations
Definition SolverBase.hh:79
bool evaluate_first_derivative(FirstDerivativeLambda &&function, InputType const &x, FirstDerivativeType &out)
Definition SolverBase.hh:785
void max_function_evaluations(Integer t_max_function_evaluations)
Definition SolverBase.hh:265
void task(std::string t_task)
Definition SolverBase.hh:459
bool solve(FunctionLambda &&function, InputType const &x_ini, InputType &x_sol)
Definition SolverBase.hh:494
InputType m_lower_bound
Definition SolverBase.hh:70
Namespace for the Optimist library.
Definition Optimist.hh:88
static std::string table_vertical_line()
Retrieve the Unicode character for the vertical line of a table.
Definition Optimist.hh:174
static std::string table_bottom_right_corner()
Retrieve the Unicode character for the bottom-right corner of a table.
Definition Optimist.hh:120
static std::string table_top_left_corner()
Retrieve the Unicode character for the top-left corner of a table.
Definition Optimist.hh:102
static std::string table_bottom_left_corner()
Retrieve the Unicode character for the bottom-left corner of a table.
Definition Optimist.hh:114
OPTIMIST_DEFAULT_INTEGER_TYPE Integer
The Integer type as used for the API.
Definition Optimist.hh:96
static std::string table_center_cross()
Retrieve the Unicode character for the center cross of a table.
Definition Optimist.hh:150
static std::string table_bottom_junction()
Retrieve the Unicode character for the bottom junction of a table.
Definition Optimist.hh:144
static std::string table_top_junction()
Retrieve the Unicode character for the top junction of a table.
Definition Optimist.hh:138
static std::string table_top_right_corner()
Retrieve the Unicode character for the top-right corner of a table.
Definition Optimist.hh:108
static std::string table_horizontal_line()
Retrieve the Unicode character for the horizontal line of a table.
Definition Optimist.hh:156
static std::string table_right_junction()
Retrieve the Unicode character for the right junction of a table.
Definition Optimist.hh:132
static std::string table_left_junction()
Retrieve the Unicode character for the left junction of a table.
Definition Optimist.hh:126