Skip to content

Commit 1818282

Browse files
committed
Merge branch 'feature/cuts' into develop
2 parents b6037ad + 6dd4e8b commit 1818282

4 files changed

Lines changed: 535 additions & 220 deletions

File tree

include/PolyhedralFunction.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1577,6 +1577,21 @@ class PolyhedralFunction : public C05Function {
15771577

15781578
void delete_rows( ModParam issueMod = eModBlck );
15791579

1580+
/*--------------------------------------------------------------------------*/
1581+
/// remove the parallel ( dominated ) rows of the PolyhedralFunction
1582+
/** Removes the rows that are parallel to ( and dominated by ) another one:
1583+
* two rows i and k are parallel if, for every component j,
1584+
* | a_{ij} - a_{kj} | <= abs_error and
1585+
* | a_{ij} - a_{kj} | <= rel_error * max( | a_{ij} | , | a_{kj} | ); among
1586+
* two parallel rows the dominated one ( the one with the worse constant ) is
1587+
* deleted. This is a purely geometric operation on ( A , b ), requiring no
1588+
* Solver. See also PolyhedralFunctionBlock::remove_redundant_rows() for the
1589+
* removal of the inactive rows, which needs to solve an LP. */
1590+
1591+
void remove_parallel_rows( FunctionValue abs_error = 0 ,
1592+
FunctionValue rel_error = 0 ,
1593+
ModParam issueMod = eModBlck );
1594+
15801595
/*--------------------------------------------------------------------------*/
15811596
/*-------------------------------- FRIENDS ---------------------------------*/
15821597
/*--------------------------------------------------------------------------*/

include/PolyhedralFunctionBlock.h

Lines changed: 126 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
namespace SMSpp_di_unipi_it
4949
{
5050

51+
class BlockSolverConfig; // forward declaration
52+
5153
/*--------------------------------------------------------------------------*/
5254
/*-------------------- CLASS PolyhedralFunctionBlock -----------------------*/
5355
/*--------------------------------------------------------------------------*/
@@ -333,10 +335,24 @@ class PolyhedralFunctionBlock : public AbstractBlock
333335

334336
PolyhedralFunctionBlock( Block * father = nullptr )
335337
: AbstractBlock( father ) , f_rep( 0 ) ,
336-
f_polyf( {} , {} , {} , -Inf< Function::FunctionValue >() , true , this
337-
) ,
338+
f_own_polyf( {} , {} , {} , -Inf< Function::FunctionValue >() , true , this
339+
) ,
338340
f_v() , f_const() { }
339341

342+
/*--------------------------------------------------------------------------*/
343+
/// constructor wrapping an *external* PolyhedralFunction
344+
/** Constructs a PolyhedralFunctionBlock that does not own its
345+
* PolyhedralFunction but operates on the given external \p polyf ( which
346+
* keeps its own Observer and is neither serialized nor destroyed by this
347+
* Block ). This is used to build a transient epigraph LP over an existing
348+
* PolyhedralFunction, e.g. in remove_redundant_rows(). */
349+
350+
PolyhedralFunctionBlock( Block * father , PolyhedralFunction * polyf )
351+
: AbstractBlock( father ) , f_rep( 0 ) ,
352+
f_own_polyf( {} , {} , {} , -Inf< Function::FunctionValue >() , true ,
353+
nullptr ) ,
354+
f_v() , f_const() { if( polyf ) f_polyf_p = polyf; }
355+
340356
/*--------------------------------------------------------------------------*/
341357
/// load the PolyhedralFunctionBlock out of an istream
342358
/** Method to deserialize the PolyhedralFunctionBlock out of an istream.
@@ -368,7 +384,7 @@ class PolyhedralFunctionBlock : public AbstractBlock
368384
// have the PolyhedralFunction do all the dirty work for us
369385
// don't bother issuing individual Modification, since a NBModification will
370386
// anyway be issued soon (if anybody is listening)
371-
f_polyf.deserialize( group , eNoMod );
387+
PF().deserialize( group , eNoMod );
372388

373389
// the PolyhedralFunctionBlock is "naked": no abstract representaton
374390
f_rep = 0;
@@ -390,7 +406,7 @@ class PolyhedralFunctionBlock : public AbstractBlock
390406

391407
std::vector< std::string > expected_dims( void ) const override {
392408
auto ret = AbstractBlock::expected_dims();
393-
auto pfev = f_polyf.expected_dims();
409+
auto pfev = PF().expected_dims();
394410
ret.insert( ret.end() , pfev.begin() , pfev.end() );
395411
return( ret );
396412
}
@@ -400,7 +416,7 @@ class PolyhedralFunctionBlock : public AbstractBlock
400416

401417
std::vector< std::string > expected_vars( void ) const override {
402418
auto ret = AbstractBlock::expected_vars();
403-
auto pfev = f_polyf.expected_vars();
419+
auto pfev = PF().expected_vars();
404420
ret.insert( ret.end() , pfev.begin() , pfev.end() );
405421
return( ret );
406422
}
@@ -502,14 +518,14 @@ class PolyhedralFunctionBlock : public AbstractBlock
502518
*
503519
* The variable v is NOT generated. In its place we generate
504520
*
505-
* - one *non-negative* ColVariable \f$\theta_i\f$ per row of f_polyf
506-
* (both diagonal and vertical, in the same order as in f_polyf), as
521+
* - one *non-negative* ColVariable \f$\theta_i\f$ per row of PF()
522+
* (both diagonal and vertical, in the same order as in PF()), as
507523
* the first group of *dynamic* Variable (f_theta);
508524
*
509525
* - one *non-negative* static ColVariable \f$\gamma\f$ as the first
510526
* group of static Variable (f_gamma), playing the role of the dual
511-
* multiplier of the global bound of f_polyf. If
512-
* f_polyf.is_bound_set() is false, then \f$\gamma\f$ is is_fixed-ed to
527+
* multiplier of the global bound of PF(). If
528+
* PF().is_bound_set() is false, then \f$\gamma\f$ is is_fixed-ed to
513529
* 0 to make sure it has no effect (the contribution to the
514530
* normalization constraint is null and the contribution to the
515531
* objective is null too, regardless of how the "ineffective" bound
@@ -617,7 +633,7 @@ class PolyhedralFunctionBlock : public AbstractBlock
617633
* \f]
618634
* where the internal \f$\gamma\f$ (this PolyhedralFunctionBlock's own
619635
* static ColVariable f_gamma, fixed to 0 if no bound is set) plays the
620-
* role of the dual multiplier of the global lower/upper bound of f_polyf.
636+
* role of the dual multiplier of the global lower/upper bound of PF().
621637
*
622638
* When the PolyhedralFunctionBlock is used as one component of a larger
623639
* "inf-convolution" structure (typically: the father AbstractBlock packs
@@ -664,7 +680,7 @@ class PolyhedralFunctionBlock : public AbstractBlock
664680
* \f[
665681
* \sum_{i \in B} \theta_i a_i = z
666682
* \f]
667-
* (where the \f$a_i\f$'s are the rows of f_polyf and z is the "main"
683+
* (where the \f$a_i\f$'s are the rows of PF() and z is the "main"
668684
* coordinate w.r.t. which the conjugate is computed) is *not*
669685
* implemented internally. This design choice reflects the fact that
670686
* multiple PolyhedralFunctionBlock may coexist within the same parent
@@ -675,13 +691,13 @@ class PolyhedralFunctionBlock : public AbstractBlock
675691
*
676692
* The list passed as input is assumed to already exist (i.e. it contains
677693
* one FRowConstraint per coordinate of z) and to be in 1:1
678-
* correspondence with the active Variable of f_polyf, that is:
679-
* \p constraints.size() must equal f_polyf.get_num_active_var(), and the
694+
* correspondence with the active Variable of PF(), that is:
695+
* \p constraints.size() must equal PF().get_num_active_var(), and the
680696
* j-th constraint (0-based) is the one corresponding to the j-th active
681-
* Variable of f_polyf.
697+
* Variable of PF().
682698
*
683699
* The method augments each constraint by adding to its LinearFunction
684-
* the term \f$\theta_i \, a_{i,j}\f$ for every row i of f_polyf for
700+
* the term \f$\theta_i \, a_{i,j}\f$ for every row i of PF() for
685701
* which \f$a_{i,j} \ne 0\f$.
686702
*
687703
* The variables \f$\theta_i\f$ involved in these terms are the dynamic
@@ -708,7 +724,66 @@ class PolyhedralFunctionBlock : public AbstractBlock
708724
/** @name Methods for reading the data of the PolyhedralFunctionBlock
709725
* @{ */
710726

711-
PolyhedralFunction & get_PolyhedralFunction( void ) { return( f_polyf ); }
727+
PolyhedralFunction & get_PolyhedralFunction( void ) { return( PF() ); }
728+
729+
/*--------------------------------------------------------------------------*/
730+
/// inhibit ( or restore ) the mirroring of the abstract representation
731+
/** Inhibits ( \p dumb == true ) or restores ( \p dumb == false, the default )
732+
* the mirroring of the abstract representation back onto the
733+
* PolyhedralFunction in add_Modification(). While "playing dumb", changes to
734+
* the Constraint / Objective of the abstract representation still reach any
735+
* attached Solver, but they are not reflected onto PF(). See f_play_dumb. */
736+
737+
void set_play_dumb( bool dumb = true ) { f_play_dumb = dumb; }
738+
739+
/*--------------------------------------------------------------------------*/
740+
/// remove the redundant rows of the PolyhedralFunction
741+
/** Removes from the PolyhedralFunction all the rows that are redundant, i.e.,
742+
* that do not contribute to its value at any point of its domain. Letting
743+
* s = -1 if the PolyhedralFunction is convex and s = +1 if it is concave,
744+
* there are two kinds of redundant rows:
745+
*
746+
* - *parallel* ( dominated ) rows: if a_i == a_k and s b_k >= s b_i for some
747+
* i != k, then row k is dominated by row i. These are removed geometrically
748+
* by PolyhedralFunction::remove_parallel_rows(), which needs no Solver.
749+
*
750+
* - *inactive* rows: a row k that, although parallel to no other, never
751+
* attains the pointwise maximum ( convex ) / minimum ( concave ). Deciding
752+
* this needs an LP. Writing the convex PolyhedralFunction as the epigraph
753+
* \f[
754+
* \min \{ v : v \ge a_i x + b_i , \; i \in I \}
755+
* \f]
756+
* row k is useless exactly when
757+
* \f[
758+
* \min \{ v - ( a_k x + b_k ) : v \ge a_i x + b_i , \;
759+
* i \in I \setminus \{ k \} \} \ge 0 ,
760+
* \f]
761+
* a negative ( possibly -INF ) optimum meaning that at some point row k
762+
* lies strictly above all the others, so it cannot be removed; the concave
763+
* case is symmetric.
764+
*
765+
* The LP is solved by the Solver configured by \p solver_config. Since the
766+
* "active" x of the PolyhedralFunction are not Variable of this Block ( they
767+
* belong to the parent model ), the LP is assembled in a transient
768+
* AbstractBlock holding fresh free x, inside which an inner
769+
* PolyhedralFunctionBlock shares this' PolyhedralFunction ( via the pointer
770+
* constructor ), so that its own linearized representation ( v and the
771+
* v >= a_i x + b_i rows ) is reused as the epigraph LP. Cycling over the
772+
* rows, the objective is aimed at row h and its own constraint is temporarily
773+
* relaxed; a redundant row is recorded and, at the end, delete_rows()-ed from
774+
* the PolyhedralFunction, so that the removal Modification reaches the parent
775+
* model. The inner Block "plays dumb" ( see set_play_dumb() ) so that the
776+
* relaxations are not mirrored back onto the shared PolyhedralFunction; the
777+
* inner Block and the AbstractBlock are built and torn down on each call.
778+
*
779+
* The four tolerances ( absolute and relative, for the parallel and for the
780+
* inactive test ) are read, like the other parameters of a
781+
* PolyhedralFunctionBlock, from this Block's BlockConfig: its "extra"
782+
* Configuration, a SimpleConfiguration< std::vector< double > > with up to
783+
* four entries [ parallel_abs, parallel_rel, optimization_abs,
784+
* optimization_rel ]; missing entries default to 0. */
785+
786+
void remove_redundant_rows( BlockSolverConfig * solver_config );
712787

713788
/*--------------------------------------------------------------------------*/
714789

@@ -718,7 +793,7 @@ class PolyhedralFunctionBlock : public AbstractBlock
718793
return( AbstractBlock::get_valid_upper_bound( true ) );
719794
else
720795
return( std::min( AbstractBlock::get_valid_upper_bound( false ) ,
721-
f_polyf.get_global_upper_bound() ) );
796+
PF().get_global_upper_bound() ) );
722797
}
723798

724799
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
@@ -729,7 +804,7 @@ class PolyhedralFunctionBlock : public AbstractBlock
729804
return( AbstractBlock::get_valid_lower_bound( true ) );
730805
else
731806
return( std::max( AbstractBlock::get_valid_lower_bound( false ) ,
732-
f_polyf.get_global_lower_bound() ) );
807+
PF().get_global_lower_bound() ) );
733808
}
734809

735810
/** @} ---------------------------------------------------------------------*/
@@ -926,13 +1001,13 @@ class PolyhedralFunctionBlock : public AbstractBlock
9261001
{
9271002
//!! std::cout << *mod << std::endl;
9281003

929-
// if the "natural" representation is used (bit 0 of f_rep is 0), or
930-
// the Modification comes from a sub-Block, or it does not concern the
931-
// Block any longer, just pass it up. With the "linearized primal"
932-
// ("01") and "linearized dual" ("11") encodings this method intercepts
933-
// Modifications and mirrors them between f_polyf and the (primal or
934-
// dual) abstract representation.
935-
if( is_natural() || ( mod->get_Block() != this ) ||
1004+
// if the "natural" representation is used (bit 0 of f_rep is 0), or this
1005+
// Block is "playing dumb" (see set_play_dumb()), or the Modification comes
1006+
// from a sub-Block, or it does not concern the Block any longer, just pass
1007+
// it up. With the "linearized primal" ("01") and "linearized dual" ("11")
1008+
// encodings this method intercepts Modifications and mirrors them between
1009+
// PF() and the (primal or dual) abstract representation.
1010+
if( is_natural() || f_play_dumb || ( mod->get_Block() != this ) ||
9361011
( ! mod->concerns_Block() ) ) {
9371012
AbstractBlock::add_Modification( mod , chnl ); // just pass it up
9381013
return;
@@ -941,7 +1016,7 @@ class PolyhedralFunctionBlock : public AbstractBlock
9411016
mod->concerns_Block( false ); // recall it's been checked already
9421017

9431018
auto tmod = std::dynamic_pointer_cast< const FunctionMod >( mod );
944-
if( tmod && ( tmod->function() == & f_polyf ) ) {
1019+
if( tmod && ( tmod->function() == & PF() ) ) {
9451020
// if the Modification comes from the PolyhedralFunction; it will
9461021
// generate a (bunch of) Modification(s) in the abstract
9471022
// representation, and this Modification itself will also remain to
@@ -1003,7 +1078,7 @@ class PolyhedralFunctionBlock : public AbstractBlock
10031078
AbstractBlock::serialize( group );
10041079

10051080
// have the PolyhedralFunction do all the dirty work for us
1006-
f_polyf.serialize( group );
1081+
PF().serialize( group );
10071082
}
10081083

10091084
/** @} ---------------------------------------------------------------------*/
@@ -1095,7 +1170,7 @@ class PolyhedralFunctionBlock : public AbstractBlock
10951170

10961171
/*--------------------------------------------------------------------------*/
10971172
/// PF -> dual abstract: counterpart of guts_of_add_Modification_PF
1098-
/** Mirrors a Modification coming from f_polyf into the *dual* abstract
1173+
/** Mirrors a Modification coming from PF() into the *dual* abstract
10991174
* representation (f_theta dynamic variables, f_normcns normalization
11001175
* constraint, the FRealObjective LinearFunction and, when registered, the
11011176
* f_coupling external coupling constraints).
@@ -1110,11 +1185,11 @@ class PolyhedralFunctionBlock : public AbstractBlock
11101185
/*--------------------------------------------------------------------------*/
11111186
/// dual abstract -> PF: counterpart of guts_of_add_Modification_LR
11121187
/** Mirrors a Modification coming from the *dual* abstract representation
1113-
* back into f_polyf. Most direct modifications of the dual abstract
1188+
* back into PF(). Most direct modifications of the dual abstract
11141189
* structures (theta variables, normalization, objective LinearFunction,
1115-
* coupling constraints) would leave f_polyf in an inconsistent state and
1190+
* coupling constraints) would leave PF() in an inconsistent state and
11161191
* are rejected. The few "internal" Modifications produced by this class
1117-
* itself while processing a PolyhedralFunctionMod from f_polyf are
1192+
* itself while processing a PolyhedralFunctionMod from PF() are
11181193
* recognised and silently absorbed. */
11191194

11201195
void guts_of_add_Modification_LR_dual( c_p_Mod mod , ChnlName chnl );
@@ -1133,7 +1208,7 @@ class PolyhedralFunctionBlock : public AbstractBlock
11331208
/*--------------------------- PRIVATE METHODS ------------------------------*/
11341209
/*--------------------------------------------------------------------------*/
11351210

1136-
// clears all the abstract representaton, but not f_polyf
1211+
// clears all the abstract representaton, but not PF()
11371212
void guts_of_destructor( void );
11381213

11391214
// constructs the i-th constraint of the linearized representation
@@ -1173,7 +1248,22 @@ class PolyhedralFunctionBlock : public AbstractBlock
11731248
bool is_dual( void ) const // dual linearized representation
11741249
{ return( ( f_rep & 3 ) == 3 ); }
11751250

1176-
PolyhedralFunction f_polyf; ///< the PolyhedralFunction
1251+
PolyhedralFunction f_own_polyf; ///< the owned PolyhedralFunction ( default )
1252+
1253+
/// pointer to the *active* PolyhedralFunction: the owned one by default, or
1254+
/// an external one when this Block wraps it ( see the second constructor )
1255+
PolyhedralFunction * f_polyf_p{ & f_own_polyf };
1256+
1257+
/// the active PolyhedralFunction ( owned or external ), used everywhere
1258+
PolyhedralFunction & PF( void ) { return( * f_polyf_p ); }
1259+
const PolyhedralFunction & PF( void ) const { return( * f_polyf_p ); }
1260+
1261+
/// when true, add_Modification() does not mirror the abstract representation
1262+
/// ( Constraint / Objective ) back onto PF(): the Modification are still
1263+
/// passed on to any attached Solver, but PF() is left untouched. Set via
1264+
/// set_play_dumb(); used by remove_redundant_rows() on a transient inner
1265+
/// Block sharing an external PolyhedralFunction
1266+
bool f_play_dumb = false;
11771267

11781268
ColVariable f_v; ///< the v variable in the linearized representation
11791269

@@ -1184,13 +1274,13 @@ class PolyhedralFunctionBlock : public AbstractBlock
11841274

11851275
std::list< ColVariable > f_theta;
11861276
///< the dynamic variables theta in the dual
1187-
/// representation, one per row of f_polyf (both
1277+
/// representation, one per row of PF() (both
11881278
/// diagonal and vertical, in the same order)
11891279

11901280
ColVariable f_gamma; ///< the static ColVariable gamma in the dual
11911281
/// representation, playing the role of the dual
11921282
/// multiplier of the global lower/upper bound of
1193-
/// f_polyf (is_fixed-ed to 0 when no bound is set)
1283+
/// PF() (is_fixed-ed to 0 when no bound is set)
11941284

11951285
FRowConstraint f_normcns;
11961286
///< the static "normalization" constraint of the
@@ -1206,7 +1296,7 @@ class PolyhedralFunctionBlock : public AbstractBlock
12061296
/// set_conjugate_constraint(). Used by the
12071297
/// add_Modification machinery to keep the
12081298
/// coupling LinearFunctions in sync with the rows
1209-
/// of f_polyf in the dual representation.
1299+
/// of PF() in the dual representation.
12101300
/// nullptr until set_conjugate_constraint() is
12111301
/// called (and remains nullptr if it never is).
12121302

0 commit comments

Comments
 (0)