4848namespace 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