1 /* Polyhedron class implementation: conversion().
2 Copyright (C) 2001-2010 Roberto Bagnara <bagnara@cs.unipr.it>
3 Copyright (C) 2010-2016 BUGSENG srl (http://bugseng.com)
4
5 This file is part of the Parma Polyhedra Library (PPL).
6
7 The PPL is free software; you can redistribute it and/or modify it
8 under the terms of the GNU General Public License as published by the
9 Free Software Foundation; either version 3 of the License, or (at your
10 option) any later version.
11
12 The PPL is distributed in the hope that it will be useful, but WITHOUT
13 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software Foundation,
19 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1307, USA.
20
21 For the most up-to-date information see the Parma Polyhedra Library
22 site: http://bugseng.com/products/ppl/ . */
23
24 #ifndef PPL_Polyhedron_conversion_templates_hh
25 #define PPL_Polyhedron_conversion_templates_hh 1
26
27 #include "Bit_Row_defs.hh"
28 #include "Bit_Matrix_defs.hh"
29 #include "Polyhedron_defs.hh"
30 #include "Scalar_Products_defs.hh"
31 #include "Scalar_Products_inlines.hh"
32 #include "Temp_defs.hh"
33 #include "math_utilities_defs.hh"
34
35 #include <cstddef>
36 #include <climits>
37
38 // This flag turns on the quick non-adjacency test;
39 // the performance impact of this test was evaluated by H. Le Verge (1994);
40 // see also Corollary 4.2 in B. Genov's PhD thesis (2014).
41 #ifndef PPL_QUICK_NON_ADJ_TEST
42 #define PPL_QUICK_NON_ADJ_TEST 1
43 #endif
44
45 // This flag turns on the quick adjacency test;
46 // for a justification, see Corollary 4.3 in B. Genov's PhD thesis (2014),
47 // where it is also said that the test was implemented in cddlib.
48 #ifndef PPL_QUICK_ADJ_TEST
49 #define PPL_QUICK_ADJ_TEST 1
50 #endif
51
52 namespace Parma_Polyhedra_Library {
53
54 /*!
55 \return
56 The number of lines of the polyhedron or the number of equality
57 constraints in the result of conversion.
58
59 \param source
60 The system to use to convert \p dest: it may be modified;
61
62 \param start
63 The index of \p source row from which conversion begin;
64
65 \param dest
66 The result of the conversion;
67
68 \param sat
69 The saturation matrix telling us, for each row in \p source, which
70 are the rows of \p dest that satisfy but do not saturate it;
71
72 \param num_lines_or_equalities
73 The number of rows in the system \p dest that are either lines of
74 the polyhedron (when \p dest is a system of generators) or equality
75 constraints (when \p dest is a system of constraints).
76
77 \if Include_Implementation_Details
78
79 For simplicity, all the following comments assume we are converting a
80 constraint system \p source to a generator system \p dest;
81 the comments for the symmetric case can be obtained by duality.
82
83 If some of the constraints in \p source are redundant, they will be removed.
84 This is why the \p source is not declared to be a constant parameter.
85
86 If \p start is 0, then \p source is a sorted system; also, \p dest is
87 a generator system corresponding to an empty constraint system.
88 If otherwise \p start is greater than 0, then the two sub-systems of
89 \p source made by the non-pending rows and the pending rows, respectively,
90 are both sorted; also, \p dest is the generator system corresponding to
91 the non-pending constraints of \p source.
92
93 Independently from the value of \p start, \p dest has lines from index 0
94 to index \p num_lines_or_equalities - 1 and rays/points from index
95 \p num_lines_or_equalities to the last of its rows.
96
97 Note that here the rows of \p sat are indexed by rows of \p dest
98 and its columns are indexed by rows of \p source.
99
100 We know that polyhedra can be represented by both a system of
101 constraints or a system of generators (points, rays and lines)
102 (see Section \ref representation).
103 When we have both descriptions for a polyhedron \f$P\f$
104 we have what is called a <EM>double description</EM>
105 (or <EM>DD pair</EM>) for \f$P\f$.
106
107 Here, the <EM>representation system</EM> refers to the system \f$C\f$
108 whose rows represent the constraints that characterize \f$P\f$
109 and the <EM>generating system</EM>, the system \f$G\f$ whose rows
110 represent the generators of \f$P\f$.
111 We say that a pair \f$(C, G)\f$ of (real) systems is
112 a <EM>double description pair</EM> if
113 \f[
114 C\vect{x} \geq \vect{0}
115 \quad\iff\quad
116 \exists \vect{\lambda} \geq \vect{0} \mathrel{.}
117 \vect{x} = G\vect{\lambda}.
118 \f]
119
120 The term "double description" is quite natural in the sense that
121 such a pair contains two different description of the same object.
122 In fact, if we refer to the cone representation of a polyhedron \f$P\f$
123 and we call \f$C\f$ and \f$G\f$ the systems of constraints and
124 rays respectively, we have
125 \f[
126 P = \{\, \vect{x} \in \Rset^n \mid C\vect{x} \geq \vect{0}\, \}
127 = \{\, \vect{x} \in \Rset^n \mid \vect{x} = G\vect{\lambda}
128 \text{ for some } \vect{\lambda} \geq \vect{0}\, \}.
129 \f]
130
131 Because of the theorem of Minkowski (see Section \ref prelims),
132 we can say that, given a \f$m \times n\f$ representation system
133 \f$C\f$ such that
134 \f$\mathop{\mathrm{rank}}(C) = n = \mathit{dimension of the whole space}\f$
135 for a non-empty polyhedron \f$P\f$,
136 it is always possible to find a generating system \f$G\f$ for \f$P\f$
137 such that \f$(C, G)\f$ is a DD pair.
138 Conversely, Weyl's theorem ensures that, for each generating system
139 \f$G\f$, it is possible to find a representation system \f$C\f$
140 such that \f$(C, G)\f$ is a DD pair.
141
142 For efficiency reasons, our representation of polyhedra makes use
143 of a double description.
144 We are thus left with two problems:
145 -# given \f$C\f$ find \f$G\f$ such that \f$(C, G)\f$ is a DD pair;
146 -# given \f$G\f$ find \f$C\f$ such that \f$(C, G)\f$ is a DD pair.
147
148 Using Farkas' Lemma we can prove that these two problems are
149 computationally equivalent (i.e., linear-time reducible to each other).
150 Farkas' Lemma establishes a fundamental property of vectors in
151 \f$\Rset^n\f$ that, in a sense, captures the essence of duality.
152 Consider a matrix \f$A \in \Rset^{m \times n}\f$ and let
153 \f$\{ \vect{a}_1, \ldots, \vect{a}_m \}\f$ be its set of row vectors.
154 Consider also another vector \f$\vect{c} \in \Rset^n\f$ such that,
155 whenever a vector \f$\vect{y} \in \Rset^n\f$ has a non-negative projection
156 on the \f$\vect{a}_i\f$'s, it also has a non-negative projection
157 on \f$\vect{c}\f$.
158 The lemma states that \f$\vect{c}\f$ has this property if and only if
159 it is in the cone generated by the \f$\vect{a}_i\f$'s.
160 Formally, the lemma states the equivalence of the two following
161 assertions:
162 -# \f$
163 \forall \vect{y}
164 \mathrel{:} (A\vect{y} \geq 0 \implies
165 \langle \vect{y},\vect{c} \rangle \geq 0)
166 \f$;
167 -# \f$
168 \exists \vect{\lambda} \geq \vect{0}
169 \mathrel{.} \vect{c}^\mathrm{T} = \vect{\lambda}^\mathrm{T}A
170 \f$.
171
172 With this result we can prove that \f$(C, G)\f$ is a DD pair
173 if and only if \f$(G^\mathrm{T}, C^\mathrm{T})\f$ is a DD pair.
174
175 Suppose \f$(C, G)\f$ is a DD pair.
176 Thus, for each \f$x\f$ of the appropriate dimension,
177 \f$C\vect{x} \geq \vect{0}\f$ if and only if
178 \f$\exists \lambda \geq 0 \mathrel{.} \vect{x} = G\vect{\lambda}\f$,
179 which is of course equivalent to
180 \f$
181 \exists \vect{\lambda} \geq \vect{0}
182 \mathrel{.} \vect{x}^\mathrm{T} = \vect{\lambda}^\mathrm{T}G^\mathrm{T}
183 \f$.
184
185 First, we assume that \f$\vect{z}\f$ is such that
186 \f$G^\mathrm{T}\vect{z} \geq \vect{0}\f$
187 and we will show that
188 \f$\exists \vect{\mu} \geq \vect{0} \mathrel{.}
189 \vect{z} = C^\mathrm{T}\vect{\mu}\f$.
190 Let \f$\vect{x}\f$ be such that \f$C\vect{x} \geq \vect{0}\f$.
191 Since \f$(C, G)\f$ is a DD pair, this is equivalent to
192 \f$
193 \exists \vect{\lambda} \geq \vect{0}
194 \mathrel{.} \vect{x}^\mathrm{T} = \vect{\lambda}^\mathrm{T}G^\mathrm{T}
195 \f$,
196 which, by Farkas' Lemma is equivalent to
197 \f$
198 \forall \vect{y} \mathrel{:} (G^\mathrm{T}\vect{y} \geq \vect{0} \implies
199 \langle \vect{y}, \vect{x} \rangle \geq 0)
200 \f$.
201 Taking \f$\vect{y} = \vect{z}\f$ and recalling our assumption that
202 \f$G^\mathrm{T}\vect{z} \geq \vect{0}\f$
203 we can conclude that \f$\langle \vect{z}, \vect{x} \rangle \geq 0\f$,
204 that is equivalent to \f$\langle \vect{x}, \vect{z} \rangle \geq 0\f$.
205 We have thus established that
206 \f$
207 \forall \vect{x} \mathrel{:} (C\vect{x} \geq \vect{0} \implies
208 \langle \vect{x}, \vect{z} \rangle \geq 0)
209 \f$.
210 By Farkas' Lemma, this is equivalent to
211 \f$\exists \vect{\mu} \geq \vect{0} \mathrel{.}
212 \vect{z}^\mathrm{T} = \vect{\mu}^\mathrm{T} C\f$,
213 which is equivalent to what we wanted to prove, that is,
214 \f$\exists \vect{\mu} \geq \vect{0} \mathrel{.}
215 \vect{z} = C^\mathrm{T}\vect{\mu}\f$.
216
217 In order to prove the reverse implication, the following observation
218 turns out to be useful:
219 when \f$(C, G)\f$ is a DD pair, \f$CG \geq 0\f$.
220 In fact,
221 let \f$\vect{e}_j\f$ be the vector whose components are all \f$0\f$
222 apart from the \f$j\f$-th one, which is \f$1\f$.
223 Clearly \f$\vect{e}_j \geq \vect{0}\f$ and, taking
224 \f$\vect{\lambda} = \vect{e}_j\f$ and
225 \f$\vect{x} = G\vect{\lambda} = G \vect{e}_j\f$, we have
226 \f$C\vect{x} = C(G \vect{e}_j) = (CG)\vect{e}_j \geq \vect{0}\f$,
227 since \f$(C, G)\f$ is a DD pair.
228 Thus, as \f$(CG)\vect{e}_j\f$ is the \f$j\f$-th column of \f$CG\f$
229 and since the choice of \f$j\f$ was arbitrary, \f$CG \geq \vect{0}\f$.
230
231 We now assume that \f$\vect{z}\f$ is such that
232 \f$\exists \vect{\mu} \geq \vect{0} \mathrel{.}
233 \vect{z} = C^\mathrm{T}\vect{\mu}\f$
234 and we will prove that \f$G^\mathrm{T}\vect{z} \geq \vect{0}\f$.
235 By Farkas' Lemma, the assumption
236 \f$\exists \vect{\mu} \geq \vect{0} \mathrel{.}
237 \vect{z}^\mathrm{T} = \vect{\mu}^\mathrm{T}C\f$,
238 is equivalent to
239 \f$\forall \vect{y} \mathrel{:} (C\vect{y} \geq \vect{0}
240 \implies \langle \vect{y}, \vect{z} \rangle \geq 0)\f$.
241 If we take \f$\vect{y} = G\vect{e}_j\f$ then \f$C\vect{y}
242 = CG\vect{e}_j \geq 0\f$,
243 since \f$CG \geq \vect{0}\f$.
244 So
245 \f$
246 \langle \vect{y}, \vect{z} \rangle
247 = (\vect{e}_j^\mathrm{T}G^\mathrm{T}) \vect{z}
248 = \vect{e}_j^\mathrm{T}(G^\mathrm{T} \vect{z})
249 \geq 0
250 \f$,
251 that is, the \f$j\f$-th component of \f$G^\mathrm{T}\vect{z}\f$
252 is non-negative. The arbitrary choice of \f$j\f$ allows us to conclude
253 that \f$G^\mathrm{T}\vect{z} \geq \vect{0}\f$, as required.
254
255 In view of this result, the following exposition assumes, for clarity,
256 that the conversion being performed is from constraints to generators.
257 Thus, even if the roles of \p source and \p dest can be interchanged,
258 in the sequel we assume the \p source system will contain the constraints
259 that represent the polyhedron and the \p dest system will contain
260 the generator that generates it.
261
262 There are some observations that are useful to understand this function:
263
264 Observation 1: Let \f$A\f$ be a system of constraints that generate
265 the polyhedron \f$P\f$ and \f$\vect{c}\f$ a new constraint that must
266 be added. Suppose that there is a line \f$\vect{z}\f$ that does not
267 saturate the constraint \f$\vect{c}\f$. If we combine the old lines
268 and rays that do not saturate \f$\vect{c}\f$ (except \f$\vect{z}\f$)
269 with \f$\vect{z}\f$ such that the new ones saturate \f$\vect{c}\f$,
270 the new lines and rays also saturate the constraints saturated by
271 the old lines and rays.
272
273 In fact, if \f$\vect{y}_1\f$ is the old generator that does not saturate
274 \f$\vect{c}\f$, \f$\vect{y}_2\f$ is the new one such that
275 \f[
276 \vect{y}_2 = \lambda \vect{y}_1 + \mu \vect{z}
277 \f]
278 and \f$\vect{c}_1\f$ is a previous constraint that \f$\vect{y}_1\f$
279 and \f$\vect{z}\f$ saturates, we can see
280 \f[
281 \langle \vect{c}_1, \vect{y}_2 \rangle
282 = \langle \vect{c}_1, (\lambda \vect{y}_1 + \mu \vect{z}) \rangle
283 = \lambda \langle \vect{c}_1, \vect{y}_1 \rangle
284 + \mu \langle \vect{c}_1, \vect{z} \rangle
285 = 0 + \mu \langle \vect{c}_1, \vect{z} \rangle
286 = \mu \langle \vect{c}_1, \vect{z} \rangle
287 \f]
288 and
289 \f[
290 \mu \langle \vect{c}_1, \vect{z} \rangle = 0.
291 \f]
292
293 Proposition 1: Let \f$\vect{r}_1\f$ and \f$\vect{r}_2\f$ be distinct
294 rays of \f$P\f$.
295 Then the following statements are equivalent:
296 a) \f$\vect{r}_1\f$ and \f$\vect{r}_2\f$ are adjacent extreme rays
297 (see Section \ref prelims);
298 b) \f$\vect{r}_1\f$ and \f$\vect{r}_2\f$ are extreme rays and the
299 rank of the system composed by the constraints saturated by both
300 \f$\vect{r}_1\f$ and \f$\vect{r}_2\f$ is equal to
301 \f$d - 2\f$, where \f$d\f$ is the rank of the system of constraints.
302
303 In fact, let \f$F\f$ be the system of generators that saturate the
304 constraints saturated by both \f$\vect{r}_1\f$ and \f$\vect{r}_2\f$.
305 If b) holds, the set \f$F\f$ is 2-dimensional and \f$\vect{r}_1\f$ and
306 \f$\vect{r}_2\f$ generate this set. So, every generator
307 \f$\vect{x}\f$ of \f$F\f$ can be built as a combination of
308 \f$\vect{r}_1\f$ and \f$\vect{r}_2\f$, i.e.
309 \f[
310 \vect{x} = \lambda \vect{r}_1 + \mu \vect{r}_2.
311 \f]
312 This combination is non-negative because there exists at least a
313 constraint \f$c\f$ saturated by \f$\vect{r}_1\f$ and not
314 \f$\vect{r}_2\f$ (or vice versa) (because they are distinct) for which
315 \f[
316 \langle \vect{c}, \vect{x} \rangle \geq 0
317 \f]
318 and
319 \f[
320 \langle \vect{c}, \vect{x} \rangle
321 = \lambda \langle \vect{c}, \vect{r}_1 \rangle
322 (or = \mu \langle \vect{c}, \vect{r}_2 \rangle).
323 \f]
324 So, there is no other extreme ray in \f$F\f$ and a) holds.
325 Otherwise, if b) does not hold, the rank of the system generated by
326 the constraints saturated by both \f$\vect{r}_1\f$ and \f$\vect{r}_2\f$
327 is equal to \f$d - k\f$, with \p k \>= 3, the set \f$F\f$ is
328 \p k -dimensional and at least \p k extreme rays are necessary
329 to generate \f$F\f$.
330 So, \f$\vect{r}_1\f$ and \f$\vect{r}_2\f$ are not adjacent and
331 a) does not hold.
332
333 Proposition 2: When we build the new system of generators starting from
334 a system \f$A\f$ of constraints of \f$P\f$, if \f$\vect{c}\f$ is the
335 constraint to add to \f$A\f$ and all lines of \f$P\f$ saturate
336 \f$\vect{c}\f$, the new set of rays is the union of those rays that
337 saturate, of those that satisfy and of a set \f$\overline Q\f$ of
338 rays such that each of them
339 -# lies on the hyper-plane represented by the k-th constraint,
340 -# is a positive combination of two adjacent rays \f$\vect{r}_1\f$ and
341 \f$\vect{r}_2\f$ such that the first one satisfies the constraint and
342 the other does not satisfy it.
343 If the adjacency property is not taken in account, the new set of
344 rays is not irredundant, in general.
345
346 In fact, if \f$\vect{r}_1\f$ and \f$\vect{r}_2\f$ are not adjacent,
347 the rank of the system composed by the constraints saturated by both
348 \f$\vect{r}_1\f$ and \f$\vect{r}_2\f$ is different from \f$d - 2\f$
349 (see the previous proposition) or neither \f$\vect{r}_1\f$ nor
350 \f$\vect{r}_2\f$ are extreme rays. Since the new ray \f$\vect{r}\f$
351 is a combination of \f$\vect{r}_1\f$ and \f$\vect{r}_2\f$,
352 it saturates the same constraints saturated by both \f$\vect{r}_1\f$ and
353 \f$\vect{r}_2\f$.
354 If the rank is less than \f$d - 2\f$, the rank of
355 the system composed by \f$\vect{c}\f$ (that is saturated by \f$\vect{r}\f$)
356 and by the constraints of \f$A\f$ saturated by \f$\vect{r}\f$ is less
357 than \f$d - 1\f$. It means that \f$r\f$ is redundant (see
358 Section \ref prelims).
359 If neither \f$\vect{r}_1\f$ nor \f$\vect{r}_2\f$ are extreme rays,
360 they belong to a 2-dimensional face containing exactly two extreme rays
361 of \f$P\f$.
362 These two adjacent rays build a ray equal to \f$\vect{r}\f$ and so
363 \f$\vect{r}\f$ is redundant.
364
365 \endif
366 */
367 template <typename Source_Linear_System, typename Dest_Linear_System>
368 dimension_type
conversion(Source_Linear_System & source,const dimension_type start,Dest_Linear_System & dest,Bit_Matrix & sat,dimension_type num_lines_or_equalities)369 Polyhedron::conversion(Source_Linear_System& source,
370 const dimension_type start,
371 Dest_Linear_System& dest,
372 Bit_Matrix& sat,
373 dimension_type num_lines_or_equalities) {
374 typedef typename Dest_Linear_System::row_type dest_row_type;
375 typedef typename Source_Linear_System::row_type source_row_type;
376
377 // Constraints and generators must have the same dimension,
378 // otherwise the scalar products below will bomb.
379 PPL_ASSERT(source.space_dimension() == dest.space_dimension());
380 const dimension_type source_space_dim = source.space_dimension();
381 const dimension_type source_num_rows = source.num_rows();
382 const dimension_type source_num_columns = source_space_dim
383 + (source.is_necessarily_closed() ? 1U : 2U);
384
385
386 dimension_type dest_num_rows = dest.num_rows();
387 // The rows removed from `dest' will be placed in this vector, so they
388 // can be recycled if needed.
389 std::vector<dest_row_type> recyclable_dest_rows;
390
391 using std::swap;
392
393 // By construction, the number of columns of `sat' is the same as
394 // the number of rows of `source'; also, the number of rows of `sat'
395 // is the same as the number of rows of `dest'.
396 PPL_ASSERT(source_num_rows == sat.num_columns());
397 PPL_ASSERT(dest_num_rows == sat.num_rows());
398
399 // If `start > 0', then we are converting the pending constraints.
400 PPL_ASSERT(start == 0 || start == source.first_pending_row());
401
402 PPL_DIRTY_TEMP_COEFFICIENT(normalized_sp_i);
403 PPL_DIRTY_TEMP_COEFFICIENT(normalized_sp_o);
404
405 bool dest_sorted = dest.is_sorted();
406 const dimension_type dest_first_pending_row = dest.first_pending_row();
407
408 // This will contain the row indexes of the redundant rows of `source'.
409 std::vector<dimension_type> redundant_source_rows;
410
411 #if PPL_QUICK_ADJ_TEST
412 // This will contain the number of ones in each row of `sat'.
413 PPL_DIRTY_TEMP(std::vector<dimension_type>, sat_num_ones);
414 sat_num_ones.resize(dest_num_rows, 0);
415 for (dimension_type i = dest_num_rows; i-- > 0; ) {
416 sat_num_ones[i] = sat[i].count_ones();
417 }
418 #endif // PPL_QUICK_ADJ_TEST
419
420 // Converting the sub-system of `source' having rows with indexes
421 // from `start' to the last one (i.e., `source_num_rows' - 1).
422 for (dimension_type k = start; k < source_num_rows; ++k) {
423 const source_row_type& source_k = source[k];
424
425 #ifndef NDEBUG
426 #if PPL_QUICK_ADJ_TEST
427 for (dimension_type i = dest_num_rows; i-- > 0; ) {
428 PPL_ASSERT(sat_num_ones[i] == sat[i].count_ones());
429 }
430 #endif // PPL_QUICK_ADJ_TEST
431 #endif // NDEBUG
432
433 // `scalar_prod[i]' will contain the scalar product of the
434 // constraint `source_k' and the generator `dest_rows[i]'. This
435 // product is 0 if and only if the generator saturates the
436 // constraint.
437 PPL_DIRTY_TEMP(std::vector<Coefficient>, scalar_prod);
438 if (dest_num_rows > scalar_prod.size()) {
439 scalar_prod.insert(scalar_prod.end(),
440 dest_num_rows - scalar_prod.size(),
441 Coefficient_zero());
442 }
443 // `index_non_zero' will indicate the first generator in `dest_rows'
444 // that does not saturate the constraint `source_k'.
445 dimension_type index_non_zero = 0;
446 for ( ; index_non_zero < dest_num_rows; ++index_non_zero) {
447 WEIGHT_BEGIN();
448 Scalar_Products::assign(scalar_prod[index_non_zero],
449 source_k,
450 dest.sys.rows[index_non_zero]);
451 WEIGHT_ADD_MUL(17, source_space_dim);
452 if (scalar_prod[index_non_zero] != 0) {
453 // The generator does not saturate the constraint.
454 break;
455 }
456 // Check if the client has requested abandoning all expensive
457 // computations. If so, the exception specified by the client
458 // is thrown now.
459 maybe_abandon();
460 }
461 for (dimension_type i = index_non_zero + 1; i < dest_num_rows; ++i) {
462 WEIGHT_BEGIN();
463 Scalar_Products::assign(scalar_prod[i], source_k, dest.sys.rows[i]);
464 WEIGHT_ADD_MUL(25, source_space_dim);
465 // Check if the client has requested abandoning all expensive
466 // computations. If so, the exception specified by the client
467 // is thrown now.
468 maybe_abandon();
469 }
470
471 // We first treat the case when `index_non_zero' is less than
472 // `num_lines_or_equalities', i.e., when the generator that
473 // does not saturate the constraint `source_k' is a line.
474 // The other case (described later) is when all the lines
475 // in `dest_rows' (i.e., all the rows having indexes less than
476 // `num_lines_or_equalities') do saturate the constraint.
477
478 if (index_non_zero < num_lines_or_equalities) {
479 // Since the generator `dest_rows[index_non_zero]' does not saturate
480 // the constraint `source_k', it can no longer be a line
481 // (see saturation rule in Section \ref prelims).
482 // Therefore, we first transform it to a ray.
483 dest.sys.rows[index_non_zero].set_is_ray_or_point_or_inequality();
484 // Of the two possible choices, we select the ray satisfying
485 // the constraint (namely, the ray whose scalar product
486 // with the constraint gives a positive result).
487 if (scalar_prod[index_non_zero] < 0) {
488 // The ray `dest_rows[index_non_zero]' lies on the wrong half-space:
489 // we change it to have the opposite direction.
490 neg_assign(scalar_prod[index_non_zero]);
491 neg_assign(dest.sys.rows[index_non_zero].expr);
492 // The modified row may still not be OK(), so don't assert OK here.
493 // They are all checked at the end of this function.
494 }
495 // Having changed a line to a ray, we set `dest_rows' to be a
496 // non-sorted system, we decrement the number of lines of `dest_rows'
497 // and, if necessary, we move the new ray below all the remaining lines.
498 dest_sorted = false;
499 --num_lines_or_equalities;
500 if (index_non_zero != num_lines_or_equalities) {
501 swap(dest.sys.rows[index_non_zero],
502 dest.sys.rows[num_lines_or_equalities]);
503 swap(scalar_prod[index_non_zero],
504 scalar_prod[num_lines_or_equalities]);
505 }
506 const dest_row_type& dest_nle = dest.sys.rows[num_lines_or_equalities];
507
508 // Computing the new lineality space.
509 // Since each line must lie on the hyper-plane corresponding to
510 // the constraint `source_k', the scalar product between
511 // the line and the constraint must be 0.
512 // This property already holds for the lines having indexes
513 // between 0 and `index_non_zero' - 1.
514 // We have to consider the remaining lines, having indexes
515 // between `index_non_zero' and `num_lines_or_equalities' - 1.
516 // Each line that does not saturate the constraint has to be
517 // linearly combined with generator `dest_nle' so that the
518 // resulting new line saturates the constraint.
519 // Note that, by Observation 1 above, the resulting new line
520 // will still saturate all the constraints that were saturated by
521 // the old line.
522
523 Coefficient& scalar_prod_nle = scalar_prod[num_lines_or_equalities];
524 PPL_ASSERT(scalar_prod_nle != 0);
525 for (dimension_type
526 i = index_non_zero; i < num_lines_or_equalities; ++i) {
527 if (scalar_prod[i] != 0) {
528 // The following fragment optimizes the computation of
529 //
530 // <CODE>
531 // Coefficient scale = scalar_prod[i];
532 // scale.gcd_assign(scalar_prod_nle);
533 // Coefficient normalized_sp_i = scalar_prod[i] / scale;
534 // Coefficient normalized_sp_n = scalar_prod_nle / scale;
535 // for (dimension_type c = dest_num_columns; c-- > 0; ) {
536 // dest[i][c] *= normalized_sp_n;
537 // dest[i][c] -= normalized_sp_i * dest_nle[c];
538 // }
539 // </CODE>
540 normalize2(scalar_prod[i],
541 scalar_prod_nle,
542 normalized_sp_i,
543 normalized_sp_o);
544 dest_row_type& dest_i = dest.sys.rows[i];
545 neg_assign(normalized_sp_i);
546 dest_i.expr.linear_combine(dest_nle.expr,
547 normalized_sp_o, normalized_sp_i);
548 dest_i.strong_normalize();
549 // The modified row may still not be OK(), so don't assert OK here.
550 // They are all checked at the end of this function.
551 scalar_prod[i] = 0;
552 // dest_sorted has already been set to false.
553 }
554 }
555
556 // Computing the new pointed cone.
557 // Similarly to what we have done during the computation of
558 // the lineality space, we consider all the remaining rays
559 // (having indexes strictly greater than `num_lines_or_equalities')
560 // that do not saturate the constraint `source_k'. These rays
561 // are positively combined with the ray `dest_nle' so that the
562 // resulting new rays saturate the constraint.
563 for (dimension_type
564 i = num_lines_or_equalities + 1; i < dest_num_rows; ++i) {
565 if (scalar_prod[i] != 0) {
566 // The following fragment optimizes the computation of
567 //
568 // <CODE>
569 // Coefficient scale = scalar_prod[i];
570 // scale.gcd_assign(scalar_prod_nle);
571 // Coefficient normalized_sp_i = scalar_prod[i] / scale;
572 // Coefficient normalized_sp_n = scalar_prod_nle / scale;
573 // for (dimension_type c = dest_num_columns; c-- > 0; ) {
574 // dest[i][c] *= normalized_sp_n;
575 // dest[i][c] -= normalized_sp_i * dest_nle[c];
576 // }
577 // </CODE>
578 normalize2(scalar_prod[i],
579 scalar_prod_nle,
580 normalized_sp_i,
581 normalized_sp_o);
582 dest_row_type& dest_i = dest.sys.rows[i];
583 WEIGHT_BEGIN();
584 neg_assign(normalized_sp_i);
585 dest_i.expr.linear_combine(dest_nle.expr,
586 normalized_sp_o, normalized_sp_i);
587 dest_i.strong_normalize();
588 // The modified row may still not be OK(), so don't assert OK here.
589 // They are all checked at the end of this function.
590 scalar_prod[i] = 0;
591 // `dest_sorted' has already been set to false.
592 WEIGHT_ADD_MUL(41, source_space_dim);
593 }
594 // Check if the client has requested abandoning all expensive
595 // computations. If so, the exception specified by the client
596 // is thrown now.
597 maybe_abandon();
598 }
599 // Since the `scalar_prod_nle' is positive (by construction), it
600 // does not saturate the constraint `source_k'. Therefore, if
601 // the constraint is an inequality, we set to 1 the
602 // corresponding element of `sat' ...
603 Bit_Row& sat_nle = sat[num_lines_or_equalities];
604 if (source_k.is_ray_or_point_or_inequality()) {
605 sat_nle.set(k - redundant_source_rows.size());
606 #if PPL_QUICK_ADJ_TEST
607 ++sat_num_ones[num_lines_or_equalities];
608 #endif // PPL_QUICK_ADJ_TEST
609 }
610 else {
611 // ... otherwise, the constraint is an equality which is
612 // violated by the generator `dest_nle': the generator has to be
613 // removed from `dest_rows'.
614 --dest_num_rows;
615 swap(dest.sys.rows[num_lines_or_equalities],
616 dest.sys.rows[dest_num_rows]);
617 recyclable_dest_rows.resize(recyclable_dest_rows.size() + 1);
618 swap(dest.sys.rows.back(), recyclable_dest_rows.back());
619 dest.sys.rows.pop_back();
620 PPL_ASSERT(dest_num_rows == dest.sys.rows.size());
621
622 swap(scalar_prod_nle, scalar_prod[dest_num_rows]);
623 swap(sat_nle, sat[dest_num_rows]);
624 #if PPL_QUICK_ADJ_TEST
625 swap(sat_num_ones[num_lines_or_equalities],
626 sat_num_ones[dest_num_rows]);
627 #endif // PPL_QUICK_ADJ_TEST
628 // dest_sorted has already been set to false.
629 }
630 // Finished handling the line or equality case:
631 // continue with next `k'.
632 continue;
633 }
634
635 // Here all the lines in `dest_rows' saturate the constraint `source_k'.
636 PPL_ASSERT(index_non_zero >= num_lines_or_equalities);
637 // First, we reorder the generators in `dest_rows' as follows:
638 // -# all the lines should have indexes between 0 and
639 // `num_lines_or_equalities' - 1 (this already holds);
640 // -# all the rays that saturate the constraint should have
641 // indexes between `num_lines_or_equalities' and
642 // `lines_or_equal_bound' - 1; these rays form the set Q=.
643 // -# all the rays that have a positive scalar product with the
644 // constraint should have indexes between `lines_or_equal_bound'
645 // and `sup_bound' - 1; these rays form the set Q+.
646 // -# all the rays that have a negative scalar product with the
647 // constraint should have indexes between `sup_bound' and
648 // `dest_num_rows' - 1; these rays form the set Q-.
649 dimension_type lines_or_equal_bound = num_lines_or_equalities;
650 dimension_type inf_bound = dest_num_rows;
651 // While we find saturating generators, we simply increment
652 // `lines_or_equal_bound'.
653 while (inf_bound > lines_or_equal_bound
654 && scalar_prod[lines_or_equal_bound] == 0) {
655 ++lines_or_equal_bound;
656 }
657 dimension_type sup_bound = lines_or_equal_bound;
658 while (inf_bound > sup_bound) {
659 const int sp_sign = sgn(scalar_prod[sup_bound]);
660 if (sp_sign == 0) {
661 // This generator has to be moved in Q=.
662 swap(dest.sys.rows[sup_bound], dest.sys.rows[lines_or_equal_bound]);
663 swap(scalar_prod[sup_bound], scalar_prod[lines_or_equal_bound]);
664 swap(sat[sup_bound], sat[lines_or_equal_bound]);
665 #if PPL_QUICK_ADJ_TEST
666 swap(sat_num_ones[sup_bound], sat_num_ones[lines_or_equal_bound]);
667 #endif // PPL_QUICK_ADJ_TEST
668 ++lines_or_equal_bound;
669 ++sup_bound;
670 dest_sorted = false;
671 }
672 else if (sp_sign < 0) {
673 // This generator has to be moved in Q-.
674 --inf_bound;
675 swap(dest.sys.rows[sup_bound], dest.sys.rows[inf_bound]);
676 swap(sat[sup_bound], sat[inf_bound]);
677 swap(scalar_prod[sup_bound], scalar_prod[inf_bound]);
678 #if PPL_QUICK_ADJ_TEST
679 swap(sat_num_ones[sup_bound], sat_num_ones[inf_bound]);
680 #endif // PPL_QUICK_ADJ_TEST
681 dest_sorted = false;
682 }
683 else {
684 // sp_sign > 0: this generator has to be moved in Q+.
685 ++sup_bound;
686 }
687 }
688
689 if (sup_bound == dest_num_rows) {
690 // Here the set Q- is empty.
691 // If the constraint is an inequality, then all the generators
692 // in Q= and Q+ satisfy the constraint. The constraint is redundant
693 // and it can be safely removed from the constraint system.
694 // This is why the `source' parameter is not declared `const'.
695 if (source_k.is_ray_or_point_or_inequality()) {
696 redundant_source_rows.push_back(k);
697 }
698 else {
699 // The constraint is an equality, so that all the generators
700 // in Q+ violate it. Since the set Q- is empty, we can simply
701 // remove from `dest_rows' all the generators of Q+.
702 PPL_ASSERT(dest_num_rows >= lines_or_equal_bound);
703 while (dest_num_rows != lines_or_equal_bound) {
704 recyclable_dest_rows.resize(recyclable_dest_rows.size() + 1);
705 swap(dest.sys.rows.back(), recyclable_dest_rows.back());
706 dest.sys.rows.pop_back();
707 --dest_num_rows;
708 }
709 PPL_ASSERT(dest_num_rows == dest.sys.rows.size());
710 }
711 // Finished handling the case when Q- is empty:
712 // continue with next `k'.
713 continue;
714 }
715
716 // The set Q- is not empty, i.e., at least one generator
717 // violates the constraint `source_k'.
718 if (sup_bound == num_lines_or_equalities) {
719 // The set Q+ is empty, so that all generators that satisfy
720 // the constraint also saturate it.
721 // We can simply remove from `dest_rows' all the generators in Q-.
722 PPL_ASSERT(dest_num_rows >= sup_bound);
723 while (dest_num_rows != sup_bound) {
724 recyclable_dest_rows.resize(recyclable_dest_rows.size() + 1);
725 swap(dest.sys.rows.back(), recyclable_dest_rows.back());
726 dest.sys.rows.pop_back();
727 --dest_num_rows;
728 }
729 PPL_ASSERT(dest_num_rows == dest.sys.rows.size());
730 // Finished handling the case when Q+ is empty:
731 // continue with next `k'.
732 continue;
733 }
734
735 // The sets Q+ and Q- are both non-empty.
736 // The generators of the new pointed cone are all those satisfying
737 // the constraint `source_k' plus a set of new rays enjoying
738 // the following properties:
739 // -# they lie on the hyper-plane represented by the constraint
740 // -# they are obtained as a positive combination of two
741 // adjacent rays, the first taken from Q+ and the second
742 // taken from Q-.
743
744 const dimension_type bound = dest_num_rows;
745
746 #if PPL_QUICK_NON_ADJ_TEST
747 // For the quick non-adjacency test, we refer to the definition
748 // of a minimal proper face (see comments in Polyhedron_defs.hh):
749 // an extremal ray saturates at least `n' - `t' - 1 constraints,
750 // where `n' is the dimension of the space and `t' is the dimension
751 // of the lineality space. Since `n == source_num_columns - 1' and
752 // `t == num_lines_or_equalities', we obtain that an extremal ray
753 // saturates at least `source_num_columns - num_lines_or_equalities - 2'
754 // constraints.
755 const dimension_type min_saturators
756 = source_num_columns - num_lines_or_equalities - 2;
757 // NOTE: we are treating the `k'-th constraint.
758 const dimension_type max_saturators = k - redundant_source_rows.size();
759 #endif // PPL_QUICK_NON_ADJ_TEST
760
761 // In the following loop,
762 // `i' runs through the generators in the set Q+ and
763 // `j' runs through the generators in the set Q-.
764 for (dimension_type i = lines_or_equal_bound; i < sup_bound; ++i) {
765 for (dimension_type j = sup_bound; j < bound; ++j) {
766 // Checking if generators `dest_rows[i]' and `dest_rows[j]' are
767 // adjacent.
768 // If there exist another generator that saturates
769 // all the constraints saturated by both `dest_rows[i]' and
770 // `dest_rows[j]', then they are NOT adjacent.
771 PPL_ASSERT(sat[i].last() == C_Integer<unsigned long>::max
772 || sat[i].last() < k);
773 PPL_ASSERT(sat[j].last() == C_Integer<unsigned long>::max
774 || sat[j].last() < k);
775
776 // Being the union of `sat[i]' and `sat[j]',
777 // `new_satrow' corresponds to a ray that saturates all the
778 // constraints saturated by both `dest_rows[i]' and
779 // `dest_rows[j]'.
780 Bit_Row new_satrow(sat[i], sat[j]);
781
782 // Even before actually creating the new ray as a
783 // positive combination of `dest_rows[i]' and `dest_rows[j]',
784 // we exploit saturation information to perform:
785 // - a quick non-adjacency test;
786 // - a quick adjacency test.
787
788 #if (PPL_QUICK_NON_ADJ_TEST || PPL_QUICK_ADJ_TEST)
789 // Compute the number of common saturators.
790 dimension_type new_satrow_ones = new_satrow.count_ones();
791 #endif // (PPL_QUICK_NON_ADJ_TEST || PPL_QUICK_ADJ_TEST)
792
793 #if PPL_QUICK_NON_ADJ_TEST
794 const dimension_type num_common_satur
795 = max_saturators - new_satrow_ones;
796 if (num_common_satur < min_saturators) {
797 // Quick non-adjacency test succeded: consider next `j'.
798 continue;
799 }
800 #endif // PPL_QUICK_NON_ADJ_TEST
801
802 #if PPL_QUICK_ADJ_TEST
803 // If either `sat[i]' or `sat[j]' has exactly one more zeroes
804 // than `new_satrow', then `dest_rows[i]' and `dest_rows[j]'
805 // are adjacent. Equivalently, adjacency holds if `new_satrow_ones'
806 // is equal to 1 plus the maximum of `sat_num_ones[i]' and
807 // `sat_num_ones[j]'.
808 const dimension_type max_ones_i_j
809 = std::max(sat_num_ones[i], sat_num_ones[j]);
810 if (max_ones_i_j + 1 == new_satrow_ones) {
811 // Quick adjacency test succeded: skip the full test.
812 goto are_adjacent;
813 }
814 #endif // PPL_QUICK_ADJ_TEST
815
816 // Perform the full (combinatorial) adjacency test.
817 {
818 bool redundant = false;
819 WEIGHT_BEGIN();
820 for (dimension_type l = num_lines_or_equalities; l < bound; ++l) {
821 if (l != i && l != j
822 && subset_or_equal(sat[l], new_satrow)) {
823 // Found another generator saturating all the constraints
824 // saturated by both `dest_rows[i]' and `dest_rows[j]'.
825 redundant = true;
826 break;
827 }
828 }
829 PPL_ASSERT(bound >= num_lines_or_equalities);
830 WEIGHT_ADD_MUL(15, bound - num_lines_or_equalities);
831 if (redundant) {
832 // Full non-adjacency test succeded: consider next `j'.
833 continue;
834 }
835 }
836
837 #if PPL_QUICK_ADJ_TEST
838 are_adjacent:
839 #endif // PPL_QUICK_ADJ_TEST
840 // Adding the new ray to `dest_rows' and the corresponding
841 // saturation row to `sat'.
842 dest_row_type new_row;
843 if (recyclable_dest_rows.empty()) {
844 sat.add_recycled_row(new_satrow);
845 #if PPL_QUICK_ADJ_TEST
846 sat_num_ones.push_back(new_satrow_ones);
847 #endif // PPL_QUICK_ADJ_TEST
848 }
849 else {
850 swap(new_row, recyclable_dest_rows.back());
851 recyclable_dest_rows.pop_back();
852 new_row.set_space_dimension_no_ok(source_space_dim);
853 swap(sat[dest_num_rows], new_satrow);
854 #if PPL_QUICK_ADJ_TEST
855 swap(sat_num_ones[dest_num_rows], new_satrow_ones);
856 #endif // PPL_QUICK_ADJ_TEST
857 }
858
859 // The following fragment optimizes the computation of
860 //
861 // <CODE>
862 // Coefficient scale = scalar_prod[i];
863 // scale.gcd_assign(scalar_prod[j]);
864 // Coefficient normalized_sp_i = scalar_prod[i] / scale;
865 // Coefficient normalized_sp_j = scalar_prod[j] / scale;
866 // for (dimension_type c = dest_num_columns; c-- > 0; ) {
867 // new_row[c] = normalized_sp_i * dest[j][c];
868 // new_row[c] -= normalized_sp_j * dest[i][c];
869 // }
870 // </CODE>
871 normalize2(scalar_prod[i],
872 scalar_prod[j],
873 normalized_sp_i,
874 normalized_sp_o);
875 WEIGHT_BEGIN();
876
877 neg_assign(normalized_sp_o);
878 new_row = dest.sys.rows[j];
879 // TODO: Check if the following assertions hold.
880 PPL_ASSERT(normalized_sp_i != 0);
881 PPL_ASSERT(normalized_sp_o != 0);
882 new_row.expr.linear_combine(dest.sys.rows[i].expr,
883 normalized_sp_i, normalized_sp_o);
884
885 WEIGHT_ADD_MUL(86, source_space_dim);
886 new_row.strong_normalize();
887 // Don't assert new_row.OK() here, because it may fail if
888 // the parameter `dest' contained a row that wasn't ok.
889 // Since we added a new generator to `dest_rows',
890 // we also add a new element to `scalar_prod';
891 // by construction, the new ray lies on the hyper-plane
892 // represented by the constraint `source_k'.
893 // Thus, the added scalar product is 0.
894 PPL_ASSERT(scalar_prod.size() >= dest_num_rows);
895 if (scalar_prod.size() <= dest_num_rows) {
896 scalar_prod.push_back(Coefficient_zero());
897 }
898 else {
899 scalar_prod[dest_num_rows] = Coefficient_zero();
900 }
901 dest.sys.rows.resize(dest.sys.rows.size() + 1);
902 swap(dest.sys.rows.back(), new_row);
903 // Increment the number of generators.
904 ++dest_num_rows;
905 } // End of loop on `j'.
906 // Check if the client has requested abandoning all expensive
907 // computations. If so, the exception specified by the client
908 // is thrown now.
909 maybe_abandon();
910 } // End of loop on `i'.
911 // Now we substitute the rays in Q- (i.e., the rays violating
912 // the constraint) with the newly added rays.
913 dimension_type j;
914 if (source_k.is_ray_or_point_or_inequality()) {
915 // The constraint is an inequality:
916 // the violating generators are those in Q-.
917 j = sup_bound;
918 // For all the generators in Q+, set to 1 the corresponding
919 // entry for the constraint `source_k' in the saturation matrix.
920
921 // After the removal of redundant rows in `source', the k-th
922 // row will have index `new_k'.
923 const dimension_type new_k = k - redundant_source_rows.size();
924 for (dimension_type l = lines_or_equal_bound;
925 l < sup_bound; ++l) {
926 sat[l].set(new_k);
927 #if PPL_QUICK_ADJ_TEST
928 ++sat_num_ones[l];
929 #endif // PPL_PPL_QUICK_ADJ_TEST
930 }
931 }
932 else {
933 // The constraint is an equality:
934 // the violating generators are those in the union of Q+ and Q-.
935 j = lines_or_equal_bound;
936 }
937 // Swapping the newly added rays
938 // (index `i' running through `dest_num_rows - 1' down-to `bound')
939 // with the generators violating the constraint
940 // (index `j' running through `j' up-to `bound - 1').
941 dimension_type i = dest_num_rows;
942 while (j < bound && i > bound) {
943 --i;
944 swap(dest.sys.rows[i], dest.sys.rows[j]);
945 swap(scalar_prod[i], scalar_prod[j]);
946 swap(sat[i], sat[j]);
947 #if PPL_QUICK_ADJ_TEST
948 swap(sat_num_ones[i], sat_num_ones[j]);
949 #endif // PPL_QUICK_ADJ_TEST
950 ++j;
951 dest_sorted = false;
952 }
953 // Setting the number of generators in `dest':
954 // - if the number of generators violating the constraint
955 // is less than or equal to the number of the newly added
956 // generators, we assign `i' to `dest_num_rows' because
957 // all generators above this index are significant;
958 // - otherwise, we assign `j' to `dest_num_rows' because
959 // all generators below index `j-1' violates the constraint.
960 const dimension_type new_num_rows = (j == bound) ? i : j;
961 PPL_ASSERT(dest_num_rows >= new_num_rows);
962 while (dest_num_rows != new_num_rows) {
963 recyclable_dest_rows.resize(recyclable_dest_rows.size() + 1);
964 swap(dest.sys.rows.back(), recyclable_dest_rows.back());
965 dest.sys.rows.pop_back();
966 --dest_num_rows;
967 }
968 PPL_ASSERT(dest_num_rows == dest.sys.rows.size());
969 } // End of loop on `k'.
970
971 // We may have identified some redundant constraints in `source',
972 // which have been swapped at the end of the system.
973 if (redundant_source_rows.size() > 0) {
974 source.remove_rows(redundant_source_rows);
975 sat.remove_trailing_columns(redundant_source_rows.size());
976 }
977
978 // If `start == 0', then `source' was sorted and remained so.
979 // If otherwise `start > 0', then the two sub-system made by the
980 // non-pending rows and the pending rows, respectively, were both sorted.
981 // Thus, the overall system is sorted if and only if either
982 // `start == source_num_rows' (i.e., the second sub-system is empty)
983 // or the row ordering holds for the two rows at the boundary between
984 // the two sub-systems.
985 if (start > 0 && start < source.num_rows()) {
986 source.set_sorted(compare(source[start - 1], source[start]) <= 0);
987 }
988 // There are no longer pending constraints in `source'.
989 source.unset_pending_rows();
990
991 // We may have identified some redundant rays in `dest_rows',
992 // which have been swapped into recyclable_dest_rows.
993 if (!recyclable_dest_rows.empty()) {
994 const dimension_type num_removed_rows = recyclable_dest_rows.size();
995 sat.remove_trailing_rows(num_removed_rows);
996 }
997 if (dest_sorted) {
998 // If the non-pending generators in `dest' are still declared to be
999 // sorted, then we have to also check for the sortedness of the
1000 // pending generators.
1001 for (dimension_type i = dest_first_pending_row;
1002 i < dest_num_rows; ++i) {
1003 if (compare(dest.sys.rows[i - 1], dest.sys.rows[i]) > 0) {
1004 dest_sorted = false;
1005 break;
1006 }
1007 }
1008 }
1009 #ifndef NDEBUG
1010 // The previous code can modify the rows' fields, exploiting the friendness.
1011 // Check that all rows are OK now.
1012 for (dimension_type i = dest.num_rows(); i-- > 0; ) {
1013 PPL_ASSERT(dest.sys.rows[i].OK());
1014 }
1015 #endif
1016
1017 dest.sys.index_first_pending = dest.num_rows();
1018 dest.set_sorted(dest_sorted);
1019 PPL_ASSERT(dest.sys.OK());
1020
1021 return num_lines_or_equalities;
1022 }
1023
1024 } // namespace Parma_Polyhedra_Library
1025
1026 #endif // !defined(PPL_Polyhedron_conversion_templates_hh)
1027