1 /*
2 This program is free software; you can redistribute it and/or
3 modify it under the terms of the GNU General Public License
4 as published by the Free Software Foundation; either version 2
5 of the License, or (at your option) any later version.
6
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 GNU General Public License for more details.
11
12 You should have received a copy of the GNU General Public License
13 along with this program; if not, write to the Free Software
14 Foundation, Inc., 51 Franklin Street, Fifth Floor,
15 Boston, MA 02110-1301, USA.
16
17 ---
18 Copyright (C) 2011 - 2015, Simon Hampe <simon.hampe@googlemail.com>
19
20 ---
21 Copyright (c) 2016-2021
22 Ewgenij Gawrilow, Michael Joswig, and the polymake team
23 Technische Universität Berlin, Germany
24 https://polymake.org
25
26 Contains functions to compute intersection products in tropical surfaces.
27 */
28
29 #include "polymake/client.h"
30 #include "polymake/Rational.h"
31 #include "polymake/Matrix.h"
32 #include "polymake/Vector.h"
33 #include "polymake/linalg.h"
34 #include "polymake/PowerSet.h"
35 #include "polymake/IncidenceMatrix.h"
36 #include "polymake/common/lattice_tools.h"
37 #include "polymake/tropical/thomog.h"
38 #include "polymake/tropical/linear_algebra_tools.h"
39 #include "polymake/tropical/lattice.h"
40 #include "polymake/tropical/specialcycles.h"
41 #include "polymake/tropical/convex_hull_tools.h"
42 #include "polymake/tropical/refine.h"
43 #include "polymake/tropical/morphism_thomog.h"
44
45 namespace polymake { namespace tropical {
46
47 /*
48 * @brief Takes a list of rays which add up to 0, whose span dimension is one less than their number and whose maximal minors are 1. Then it also
49 * takes a list of vectors lying in that span. It will then compute for each vector the unique representation as a positive linear combination of the rays.
50 * @param Matrix<Rational> rank_one_flats, as PRIMITIVE INTEGER
51 * @param Matrix<Rational> curve_rays, as PRIMITIVE INTEGER
52 * @return Matrix<Integer> Each row corresponds to the representation of the same row in curve_rays. Each column corresponds to a row of surface_rays and contains
53 * the corresponding positive coefficient.
54 * */
positive_decomposition(const Matrix<Rational> & rank_one_flats,const Matrix<Rational> & curve_rays)55 Matrix<Integer> positive_decomposition(const Matrix<Rational> &rank_one_flats, const Matrix<Rational> &curve_rays)
56 {
57 Matrix<Integer> result(curve_rays.rows(), rank_one_flats.rows());
58 // Iterate curve rays
59 for (Int cr = 0; cr < curve_rays.rows(); ++cr) {
60 // Compute a linear representation of the vector in the rays
61 Vector<Rational> linRep = linearRepresentation(curve_rays.row(cr), rank_one_flats);
62 //vGo through its entries. For each negative entry, add the absolute value to all other entries and set this one to 0.
63 for (Int entry = 0; entry < linRep.dim(); ++entry) {
64 if (linRep[entry] < 0)
65 linRep = linRep - (linRep[entry] * ones_vector<Rational>(linRep.dim()));
66 }
67 result.row(cr) = linRep;
68 }
69 return result;
70 } //END positive_decomposition
71
72 /*
73 * @brief Takes a list of rays of a curve given by their positive linear representations wrt to a rank-1-flat-vectors matrix and a list of weights of those rays. It then computes the degree of that curve.
74 * @param Matrix<Rational> curve_decompositions
75 * @param Vector<Integer> curve_weights
76 * @return Integer
77 */
degree_via_decomposition(const Matrix<Integer> & curve_decompositions,const Vector<Integer> & curve_weights)78 Integer degree_via_decomposition(const Matrix<Integer> &curve_decompositions, const Vector<Integer> &curve_weights)
79 {
80 Integer deg = 0;
81 for (Int i = 0; i < curve_decompositions.rows(); ++i) {
82 deg += curve_decompositions(i,0) * curve_weights[i];
83 }
84 return deg;
85 }//END degree_in_uniform
86
87 /*
88 * @brief Computes the intersection multiplicity of two fan curves in a surface that is GLnZ-isomorphic
89 * to a uniform surface
90 * @param Matrix<Integer> rank_one_flats An integer matrix, whose rows are rays in a surface corresponding to the rank one flats of a matroid realization of that surface.
91 * @param Matrix<Rational> curve_a_rays The rays of the first curve (not homog, no leading coord)
92 * @param Vector<Integer> curve_a_weights The weights of the rays of the first curve, in the same order as the rays
93 * @param Matrix<Rational> curve_b_rays The rays of the second curve (not homog, no leading coord)
94 * @param Vector<Integer> curve_b_weights The weights of the rays of the second curve, in the same order as the rays.
95 */
intersection_multiplicity_via_flats(Matrix<Rational> & rank_one_flats,Matrix<Rational> & curve_a_rays,const Vector<Integer> & curve_a_weights,Matrix<Rational> & curve_b_rays,const Vector<Integer> & curve_b_weights)96 Integer intersection_multiplicity_via_flats(Matrix<Rational>& rank_one_flats,
97 Matrix<Rational>& curve_a_rays,
98 const Vector<Integer>& curve_a_weights,
99 Matrix<Rational>& curve_b_rays,
100 const Vector<Integer>& curve_b_weights)
101 {
102 // Make everything integer
103 rank_one_flats = Matrix<Rational>(common::primitive(rank_one_flats));
104 curve_a_rays = Matrix<Rational>(common::primitive(curve_a_rays));
105 curve_b_rays = Matrix<Rational>(common::primitive(curve_b_rays));
106
107 Matrix<Integer> curve_a_decompositions = positive_decomposition( rank_one_flats, curve_a_rays);
108 Matrix<Integer> curve_b_decompositions = positive_decomposition( rank_one_flats, curve_b_rays);
109
110 Integer result = degree_via_decomposition(curve_a_decompositions, curve_a_weights) *
111 degree_via_decomposition( curve_b_decompositions, curve_b_weights);
112
113 // We iterate pairs of rays of the two curves
114 for (Int aray = 0; aray < curve_a_rays.rows(); ++aray) {
115 Vector<Integer> amap = curve_a_decompositions.row(aray);
116 for (Int bray = 0; bray < curve_b_rays.rows(); ++bray) {
117 Vector<Integer> bmap = curve_b_decompositions.row(bray);
118 Integer correction = 0;
119 // Iterate pairs of rank one flats
120 for (auto pair = entire(all_subsets_of_k( sequence(0,rank_one_flats.rows()), 2)); !pair.at_end(); ++pair) {
121 Vector<Int> pair_as_vector(*pair);
122 correction = std::max( correction,
123 std::min(
124 amap[pair_as_vector[0]] * bmap[pair_as_vector[1]],
125 amap[pair_as_vector[1]] * bmap[pair_as_vector[0]]
126 )*curve_a_weights[aray]*curve_b_weights[bray]
127 );
128 } // END iterate pairs of rank one flats
129 result -= correction;
130 } // END iterate curve B rays
131 } // END iterate curve A rays
132
133 return result;
134 } // END intersection_multiplicity_in_uniform
135
136 /*
137 * @brief Takes a smooth surface fan and finds vectors corresponding to the rank one flats in some matroidal
138 * realization
139 * @param BigObject surface A tropical surface fan.
140 * @return Matrix<Rational> The rank one rays in non-tropically homogeneous coordinates.
141 */
142 template <typename Addition>
find_rank_one_vectors(BigObject surface)143 Matrix<Rational> find_rank_one_vectors(BigObject surface)
144 {
145 BigObject matroid, map;
146 bool is_smooth;
147 call_function("is_smooth", surface) >> is_smooth >> matroid >> map;
148 //Sanity check
149 if (!is_smooth) throw std::runtime_error("Finding rank one vectors: Surface is not smooth.");
150
151 BigObject face_lattice = matroid.give("LATTICE_OF_FLATS");
152
153 //Extract rank one flats
154 Int N = matroid.give("N_ELEMENTS");
155 NodeMap<Directed, Set<Int>> all_faces = face_lattice.give("FACES");
156 Set<Int> rank_one_flats = face_lattice.call_method("nodes_of_rank", 1);
157
158 Matrix<Rational> map_matrix = map.give("MATRIX");
159 map_matrix = inv(map_matrix.minor(range_from(1), range_from(1)));
160 map_matrix = thomog_morphism(map_matrix, zero_vector<Rational>(map_matrix.cols())).first;
161
162 Matrix<Rational> converted_vectors(0, map_matrix.cols());
163
164 for (auto j = entire(rank_one_flats); !j.at_end(); ++j) {
165 Vector<Rational> fvector(N);
166 const Set<Int>& flat = all_faces[*j];
167 fvector.slice(flat) = Addition::orientation() * ones_vector<Rational>(flat.size());
168 converted_vectors /= (map_matrix * fvector);
169 }
170
171 return tdehomog(converted_vectors, 0, 0);
172 } // END find_rank_one_vectors
173
174 /*
175 * @brief Finds all the maximal cones of a surface containing a given point
176 * @param Vector<Rational> point, in tropical homogeneous coordinates with leading coordinate.
177 * @param Matrix<Rational> facet_normals The [[FACET_NORMALS]]
178 * @param Matrix<Rational> affine_normals The [[AFFINE_HULL_NORMALS]]
179 * @param SparseMatrix<Int> maximal_facets The [[MAXIMAL_POLYTOPES_FACETS]]
180 * @param IncidenceMatrix maximal_affine The [[MAXIMAL_POLYTOPES_AFFINE_HULL_NORMALS]]
181 * @param Matrix<Rational> vertices The [[VERTICES]]
182 * @param IncidenceMatrix<> cones The [[MAXIMAL_POLYTOPES]]
183 * @param Matrix<Rational> lineality The [[LINEALITY_SPACE]]
184 * @return BigObject The star of the surface as a Cycle
185 */
186 template <typename Addition>
compute_surface_star(const Vector<Rational> & point,const Matrix<Rational> & facet_normals,const Matrix<Rational> & affine_normals,const SparseMatrix<Int> & maximal_facets,const IncidenceMatrix<> & maximal_affine,const Matrix<Rational> & vertices,const Matrix<Rational> & lineality,const IncidenceMatrix<> & cones)187 BigObject compute_surface_star(const Vector<Rational>& point,
188 const Matrix<Rational>& facet_normals,
189 const Matrix<Rational>& affine_normals,
190 const SparseMatrix<Int>& maximal_facets,
191 const IncidenceMatrix<>& maximal_affine,
192 const Matrix<Rational>& vertices,
193 const Matrix<Rational>& lineality,
194 const IncidenceMatrix<>& cones)
195 {
196 // First of all find the set of all maximal cones containing the point.
197 Set<Int> containing_cones;
198 for (Int mc = 0; mc < cones.rows(); ++mc) {
199 if (!is_zero(affine_normals.minor( maximal_affine.row(mc), All) * point)) {
200 continue;
201 }
202 bool found_non_facet = false;
203 for (Int col = 0; col < maximal_facets.cols() && !found_non_facet; ++col) {
204 if (maximal_facets(mc,col) != 0) {
205 if (facet_normals.row(col) * maximal_facets(mc,col) * point < 0) {
206 found_non_facet = true;
207 continue;
208 }
209 }
210 }
211 if (found_non_facet) continue;
212 // Arriving here, it must contain the point
213 containing_cones += mc;
214 } // END iterate maximal cones
215
216 // Now construct the star itself
217 Matrix<Rational> star_rays = vertices;
218 Matrix<Rational> star_lineality = lineality;
219 IncidenceMatrix<> star_cones = cones;
220
221 std::pair<Set<Int>, Set<Int>> f_and_nf = far_and_nonfar_vertices(vertices);
222 Set<Int> cone_intersection = accumulate( rows( cones.minor(containing_cones,All)), operations::mul());
223 Set<Int> unused_rays = sequence(0, vertices.rows()) -
224 accumulate( rows(cones.minor(containing_cones,All)),operations::add());
225
226 // Start by constructing the lineality space
227 Vector<Int> intersect_nonfar( cone_intersection * f_and_nf.second);
228 Set<Int> intersect_far = cone_intersection * f_and_nf.first;
229 Set<Int> rays_to_remove = intersect_far + unused_rays;
230
231 for (Int inf = 1; inf < intersect_nonfar.dim(); ++inf) {
232 star_lineality /= (vertices.row(intersect_nonfar[inf]) - star_rays.row(intersect_nonfar[0]));
233 rays_to_remove += intersect_nonfar[inf];
234 }
235 star_lineality /= vertices.minor( intersect_far,All);
236 star_lineality = star_lineality.minor( basis_rows(star_lineality),All);
237
238 // Replace nonfar vertices in adjacent cones by differences
239 Set<Int> nonfar_remaining = f_and_nf.second - rays_to_remove - intersect_nonfar[0];
240 for (auto nfr = entire(nonfar_remaining); !nfr.at_end(); ++nfr) {
241 star_rays.row(*nfr) = star_rays.row(*nfr) - star_rays.row(intersect_nonfar[0]);
242 }
243
244 // Make the reference vertex the origin
245 star_rays.row( intersect_nonfar[0]) = unit_vector<Rational>(star_rays.cols(), 0);
246
247 return BigObject("Cycle", mlist<Addition>(),
248 "VERTICES", star_rays.minor(~rays_to_remove, All),
249 "MAXIMAL_POLYTOPES", star_cones.minor(containing_cones, ~rays_to_remove),
250 "LINEALITY_SPACE", star_lineality,
251 "WEIGHTS", ones_vector<Integer>(containing_cones.size()));
252
253 } // END findSurfaceStarCones
254
255
256 /*
257 * @brief Computes the rays of the star of a curve at a point
258 * @param Matrix<Rational> curve_vertices The curve's vertices (non-homog. with leading coord)
259 * @param IncidenceMatrix<> curve_cones The cuve's cones
260 * @param IncidenceMatrix<> curve_containers The indices of all the maximal cones containing the point.
261 * @param Matrix<Rationa> lineality If the curve consists only of a lineality space, this is its generator.
262 * @return Matrix<Rational> The star rays, non-homog and without(!) leading coordinate.
263 */
264 std::pair<Matrix<Rational>, Vector<Integer>>
compute_curve_star_rays(const Matrix<Rational> & curve_vertices,const IncidenceMatrix<> & curve_cones,const Vector<Integer> & curve_weights,const Set<Int> & curve_containers,const Matrix<Rational> & lineality)265 compute_curve_star_rays(
266 const Matrix<Rational>& curve_vertices,
267 const IncidenceMatrix<>& curve_cones,
268 const Vector<Integer>& curve_weights,
269 const Set<Int>& curve_containers,
270 const Matrix<Rational>& lineality )
271 {
272 // Split up the rays in far and nonfar
273 std::pair<Set<Int>, Set<Int>> f_and_nf = far_and_nonfar_vertices(curve_vertices);
274
275 Matrix<Rational> result(0,curve_vertices.cols()-1);
276 Vector<Integer> star_weights;
277
278 // If the curve is only a lineality space:
279 if (lineality.rows() > 0) {
280 result /= lineality.minor(All, range_from(1));
281 result /= -lineality.minor(All, range_from(1));
282 star_weights |= curve_weights[0];
283 star_weights |= curve_weights[0];
284 return std::make_pair(result, star_weights);
285 }
286
287 // If there is only one maximal cone, we compute its direction vector
288 if (curve_containers.size() == 1) {
289 Set<Int> single_cone = curve_cones.row( *(curve_containers.begin()));
290 Set<Int> far_in_container = f_and_nf.first * single_cone;
291 Vector<Rational> direction;
292 if (far_in_container.size() == 0) {
293 // It's a bounded edge
294 Vector<Int> both_rays(single_cone);
295 direction = curve_vertices.row( both_rays[0]) - curve_vertices.row(both_rays[1]);
296 } else {
297 direction = curve_vertices.row( *(far_in_container.begin()));
298 }
299
300 direction = direction.slice(range_from(1));
301 result /= direction;
302 result /= -direction;
303 star_weights = curve_weights[ *(curve_containers.begin())] * ones_vector<Integer>(2);
304 return std::make_pair(result, star_weights);
305 } else {
306 // If there are several maximal cones, then every one of them must have point as a vertex
307 const Int points_index = accumulate(rows(curve_cones.minor(curve_containers,All)), operations::mul()).front();
308 for (auto adjRays = entire(curve_containers); !adjRays.at_end(); ++adjRays) {
309 Vector<Rational> direction;
310 const Int ray_index = (curve_cones.row(*adjRays) - points_index).front();
311 if (curve_vertices(ray_index,0) == 0) {
312 direction = curve_vertices.row(ray_index);
313 } else {
314 direction = curve_vertices.row(ray_index) - curve_vertices.row(points_index);
315 }
316 direction = direction.slice(range_from(1));
317 result /= direction;
318 star_weights |= curve_weights[*adjRays];
319 }
320 }
321 return std::make_pair(result, star_weights);
322 } // END compute_curve_star_rays
323
324 /*
325 * @brief For a curve and a point, computes the indices of all maximal cones containing the point
326 * @param Vector<Rational> point the point, non-homog, with leading coord
327 * @param Matrix<Rational> curve_rays The curve vertices, non-homog, with leading coord
328 * @param IncidenceMatrix curve_cones The curve cones
329 * @param Int some_container An index of one maximal cone containing the point
330 * @return Set<Int> All indices of cones containing the point
331 */
compute_containing_cones(const Vector<Rational> & point,const Matrix<Rational> & curve_rays,const IncidenceMatrix<> & curve_cones,Int some_container)332 Set<Int> compute_containing_cones(const Vector<Rational>& point,
333 const Matrix<Rational>& curve_rays,
334 const IncidenceMatrix<>& curve_cones,
335 Int some_container)
336 {
337 // Go through the rays of the container - if point is equal to one of them, return all
338 // maximal cones using the vertex
339 Set<Int> container_set = curve_cones.row(some_container);
340 for (auto cray = entire(container_set); !cray.at_end(); ++cray) {
341 if (point == curve_rays.row(*cray)) {
342 return T(curve_cones).row(*cray);
343 }
344 }
345
346 // If it isn't, it's just the one cone
347 return scalar2set(some_container);
348 }
349
350
351 template <typename Addition>
intersect_in_smooth_surface(BigObject surface,BigObject cycle_a,BigObject cycle_b)352 BigObject intersect_in_smooth_surface(BigObject surface, BigObject cycle_a, BigObject cycle_b)
353 {
354 // Extract data
355 Int dim_a = cycle_a.give("PROJECTIVE_DIM");
356 Int dim_b = cycle_b.give("PROJECTIVE_DIM");
357 Int ambient_dim = surface.give("PROJECTIVE_AMBIENT_DIM");
358
359 // Basic sanity checks
360 if (dim_a + dim_b <= 1)
361 return empty_cycle<Addition>(ambient_dim);
362 if (dim_a > 2 || dim_b > 2)
363 throw std::runtime_error("intersect_in_smooth_surface: Cycles dimension too large.");
364
365 // If one is full-dimensional, return the other one with multiplied weights
366 const Vector<Integer> weights_a = cycle_a.give("WEIGHTS");
367 const Vector<Integer> weights_b = cycle_b.give("WEIGHTS");
368 if (dim_a == 2) {
369 return cycle_b.call_method("multiply_weights", weights_a[0]);
370 }
371 if (dim_b == 2) {
372 return cycle_a.call_method("multiply_weights", weights_b[1]);
373 }
374
375 // From here on we know we have two curves.
376
377 // Refine both curves along the surface
378 RefinementResult refined_cycle_a = refinement(cycle_a, surface, false, false, false, true, false);
379 RefinementResult refined_cycle_b = refinement(cycle_b, surface, false, false, false, true, false);
380 Matrix<Rational> rays_a = refined_cycle_a.complex.give("VERTICES");
381 rays_a = tdehomog(rays_a);
382 Matrix<Rational> rays_b = refined_cycle_b.complex.give("VERTICES");
383 rays_b = tdehomog(rays_b);
384 Matrix<Rational> lin_a = refined_cycle_a.complex.give("LINEALITY_SPACE");
385 lin_a = tdehomog(lin_a);
386 Matrix<Rational> lin_b = refined_cycle_b.complex.give("LINEALITY_SPACE");
387 lin_b = tdehomog(lin_b);
388 IncidenceMatrix<> cones_a = refined_cycle_a.complex.give("MAXIMAL_POLYTOPES");
389 IncidenceMatrix<> cones_b = refined_cycle_b.complex.give("MAXIMAL_POLYTOPES");
390 const Vector<Integer> refweights_a = refined_cycle_a.complex.give("WEIGHTS");
391 const Vector<Integer> refweights_b = refined_cycle_b.complex.give("WEIGHTS");
392
393 // Now intersect the refined versions.
394 fan_intersection_result ab_intersect = fan_intersection(rays_a, lin_a, cones_a, rays_b, lin_b, cones_b);
395
396 // The potential intersection points are the nonfar vertices of this
397 Matrix<Rational> ab_vertices = ab_intersect.rays;
398 Matrix<Rational> ab_vertices_homog = thomog(ab_vertices);
399 IncidenceMatrix<> ab_vertices_by_cones = T(ab_intersect.cones);
400 Vector<Integer> ab_weights;
401 Set<Int> nonfar_and_nonzero;
402
403 Matrix<Rational> surface_rays = surface.give("VERTICES");
404 IncidenceMatrix<> surface_cones = surface.give("MAXIMAL_POLYTOPES");
405 Matrix<Rational> surface_lineality = surface.give("LINEALITY_SPACE");
406 Matrix<Rational> facet_normals = surface.give("FACET_NORMALS");
407 Matrix<Rational> affine_hull = surface.give("AFFINE_HULL");
408 SparseMatrix<Int> maximal_facets = surface.give("MAXIMAL_POLYTOPES_FACETS");
409 IncidenceMatrix<> maximal_affine = surface.give("MAXIMAL_POLYTOPES_AFFINE_HULL_NORMALS");
410
411 for (Int point = 0; point < ab_vertices.rows(); ++point) {
412 // If it's a ray, ignore it.
413 if (ab_vertices(point,0) == 0)
414 continue;
415
416 // If it's a vertex, we need to compute the star of X at that point.
417 BigObject surface_star = compute_surface_star<Addition>(
418 ab_vertices_homog.row(point),
419 facet_normals,
420 affine_hull,
421 maximal_facets,
422 maximal_affine,
423 surface_rays,
424 surface_lineality,
425 surface_cones);
426 Matrix<Rational> rank_one_vectors = find_rank_one_vectors<Addition>(surface_star);
427
428 // Now compute the star at each curve
429 Int cone_of_point = ab_vertices_by_cones.row(point).front();
430 Int cone_of_a = ab_intersect.xcontainers.row(cone_of_point).front();
431 Int cone_of_b = ab_intersect.ycontainers.row(cone_of_point).front();
432 std::pair<Matrix<Rational>, Vector<Integer>> a_star =
433 compute_curve_star_rays(rays_a, cones_a, refweights_a,
434 compute_containing_cones(ab_vertices.row(point), rays_a, cones_a, cone_of_a), lin_a);
435 std::pair<Matrix<Rational>, Vector<Integer>> b_star =
436 compute_curve_star_rays(rays_b, cones_b, refweights_b,
437 compute_containing_cones(ab_vertices.row(point), rays_b, cones_b, cone_of_b), lin_b);
438
439
440 // Finally, we can compute the multiplicity
441
442 Integer mult = intersection_multiplicity_via_flats(
443 rank_one_vectors, a_star.first, a_star.second, b_star.first, b_star.second);
444
445 if (mult != 0) {
446 nonfar_and_nonzero += point;
447 ab_weights |= mult;
448 }
449 } // END iterate intersection rays
450
451 // If no points remain, return the empty_cycle
452 if (nonfar_and_nonzero.size() == 0)
453 return empty_cycle<Addition>(ambient_dim);
454 ab_vertices = ab_vertices.minor(nonfar_and_nonzero, range_from(1));
455
456 return point_collection<Addition>(thomog(ab_vertices,0,0), ab_weights);
457 } // END intersect_in_smooth_surface
458
459
460 // --------------------- PERL WRAPPERS -----------------------------
461
462
463 UserFunctionTemplate4perl("# @category Intersection theory"
464 "# Computes the intersection product of two cycles in a smooth surface"
465 "# @param Cycle<Addition> surface A smooth surface"
466 "# @param Cycle<Addition> A any cycle in the surface"
467 "# @param Cycle<Addition> B any cycle in the surface"
468 "# @return Cycle<Addition> The intersection product of A and B in the surface",
469 "intersect_in_smooth_surface<Addition>(Cycle<Addition>,Cycle<Addition>, Cycle<Addition>)");
470
471 FunctionTemplate4perl("compute_surface_star<Addition>(Vector, Matrix,Matrix,SparseMatrix<Int>, IncidenceMatrix, Matrix, Matrix,IncidenceMatrix)");
472
473 } }
474