| 2465 | def oriented_boundary(self, cone): |
| 2466 | r""" |
| 2467 | Return the facets bounding ``cone`` with their induced |
| 2468 | orientation. |
| 2469 | |
| 2470 | INPUT: |
| 2471 | |
| 2472 | - ``cone`` -- a cone of the fan or the whole fan. |
| 2473 | |
| 2474 | OUTPUT: |
| 2475 | |
| 2476 | The boundary cones of ``cone`` as a formal linear combination |
| 2477 | of cones with coefficients `\pm 1`. Each summand is a facet of |
| 2478 | ``cone`` and the coefficient indicates whether their (chosen) |
| 2479 | orientation argrees or disagrees with the "outward normal |
| 2480 | first" boundary orientation. Note that the orientation of any |
| 2481 | individial cone is arbitrary. This method once and for all |
| 2482 | picks orientations for all cones and then computes the |
| 2483 | boundaries relative to that chosen orientation. |
| 2484 | |
| 2485 | If ``cone`` is the fan itself, the generating cones with their |
| 2486 | orientation relative to the ambient space are returned. |
| 2487 | |
| 2488 | See :meth:`complex` for the associated chain complex. If you |
| 2489 | do not require the orientation, use :meth:`cone.facets() |
| 2490 | <sage.geometry.cone.ConvexRationalPolyhedralCone.facets>` |
| 2491 | instead. |
| 2492 | |
| 2493 | EXAMPLES:: |
| 2494 | |
| 2495 | sage: fan = toric_varieties.P(3).fan() |
| 2496 | sage: cone = fan(2)[0] |
| 2497 | sage: bdry = fan.oriented_boundary(cone); bdry |
| 2498 | -1-d cone of Rational polyhedral fan in 3-d lattice N |
| 2499 | + 1-d cone of Rational polyhedral fan in 3-d lattice N |
| 2500 | sage: bdry[0] |
| 2501 | (-1, 1-d cone of Rational polyhedral fan in 3-d lattice N) |
| 2502 | sage: bdry[1] |
| 2503 | (1, 1-d cone of Rational polyhedral fan in 3-d lattice N) |
| 2504 | sage: fan.oriented_boundary(bdry[0][1]) |
| 2505 | -0-d cone of Rational polyhedral fan in 3-d lattice N |
| 2506 | sage: fan.oriented_boundary(bdry[1][1]) |
| 2507 | -0-d cone of Rational polyhedral fan in 3-d lattice N |
| 2508 | |
| 2509 | If you pass the fan itself, this method returns the |
| 2510 | orientation of the generating cones which is determined by the |
| 2511 | order of the rays in :meth:`cone.ray_basis() |
| 2512 | <sage.geometry.cone.IntegralRayCollection.ray_basis>` :: |
| 2513 | |
| 2514 | sage: fan.oriented_boundary(fan) |
| 2515 | 3-d cone of Rational polyhedral fan in 3-d lattice N |
| 2516 | - 3-d cone of Rational polyhedral fan in 3-d lattice N |
| 2517 | + 3-d cone of Rational polyhedral fan in 3-d lattice N |
| 2518 | - 3-d cone of Rational polyhedral fan in 3-d lattice N |
| 2519 | sage: [ matrix(cone.ray_basis()).det() for cone in fan.generating_cones() ] |
| 2520 | [-1, 1, -1, 1] |
| 2521 | |
| 2522 | A non-full dimensional fan:: |
| 2523 | |
| 2524 | sage: cone = Cone([(4,5)]) |
| 2525 | sage: fan = Fan([cone]) |
| 2526 | sage: fan.oriented_boundary(cone) |
| 2527 | 0-d cone of Rational polyhedral fan in 2-d lattice N |
| 2528 | sage: fan.oriented_boundary(fan) |
| 2529 | 1-d cone of Rational polyhedral fan in 2-d lattice N |
| 2530 | |
| 2531 | TESTS:: |
| 2532 | |
| 2533 | sage: fan = toric_varieties.P2().fan() |
| 2534 | sage: trivial_cone = fan(0)[0] |
| 2535 | sage: fan.oriented_boundary(trivial_cone) |
| 2536 | 0 |
| 2537 | """ |
| 2538 | if not cone is self: |
| 2539 | cone = self.embed(cone) |
| 2540 | if '_oriented_boundary' in self.__dict__: |
| 2541 | return self._oriented_boundary[cone] |
| 2542 | |
| 2543 | # Fix (arbitrary) orientations of the generating cones. Induced |
| 2544 | # by ambient space orientation for full-dimensional cones |
| 2545 | from sage.structure.formal_sum import FormalSum |
| 2546 | def sign(x): |
| 2547 | assert x != 0 |
| 2548 | if x>0: return +1 |
| 2549 | else: return -1 |
| 2550 | N_QQ = self.lattice().base_extend(QQ) |
| 2551 | dim = self.lattice_dim() |
| 2552 | outward_vectors = dict() |
| 2553 | generating_cones = [] |
| 2554 | for c in self.generating_cones(): |
| 2555 | if c.dim()==dim: |
| 2556 | outward_v = [] |
| 2557 | else: |
| 2558 | Q = N_QQ.quotient(c.rays()) |
| 2559 | outward_v = [ Q.lift(q) for q in Q.gens() ] |
| 2560 | |
| 2561 | outward_vectors[c] = outward_v |
| 2562 | orientation = sign(matrix(outward_v + list(c.ray_basis())).det()) |
| 2563 | generating_cones.append(tuple([orientation, c])) |
| 2564 | boundaries = {self:FormalSum(generating_cones)} |
| 2565 | |
| 2566 | # The orientation of each facet is arbitrary, but the |
| 2567 | # partititon of the boundary in positively and negatively |
| 2568 | # oriented facets is not. |
| 2569 | for d in range(dim, -1, -1): |
| 2570 | for c in self(d): |
| 2571 | c_boundary = [] |
| 2572 | c_matrix = matrix(outward_vectors[c] + list(c.ray_basis())) |
| 2573 | c_matrix_inv = c_matrix.inverse() |
| 2574 | for facet in c.facets(): |
| 2575 | outward_ray_indices = set(c.ambient_ray_indices()) \ |
| 2576 | .difference(set(facet.ambient_ray_indices())) |
| 2577 | outward_vector = - sum(self.ray(i) for i in outward_ray_indices) |
| 2578 | outward_vectors[facet] = [outward_vector] + outward_vectors[c] |
| 2579 | facet_matrix = matrix(outward_vectors[facet] + list(facet.ray_basis())) |
| 2580 | orientation = sign((c_matrix_inv * facet_matrix).det()) |
| 2581 | c_boundary.append(tuple([orientation, facet])) |
| 2582 | boundaries[c] = FormalSum(c_boundary) |
| 2583 | |
| 2584 | self._oriented_boundary = boundaries |
| 2585 | return boundaries[cone] |
| 2586 | |
| 2587 | def complex(self, base_ring=ZZ, extended=False): |
| 2588 | r""" |
| 2589 | Return the chain complex of the fan. |
| 2590 | |
| 2591 | To a `d`-dimensional fan `\Sigma`, one can canonically |
| 2592 | associate a chain complex `K^\bullet` |
| 2593 | |
| 2594 | .. math:: |
| 2595 | |
| 2596 | 0 \longrightarrow |
| 2597 | \ZZ^{\Sigma(d)} \longrightarrow |
| 2598 | \ZZ^{\Sigma(d-1)} \longrightarrow |
| 2599 | \cdots \longrightarrow |
| 2600 | \ZZ^{\Sigma(0)} \longrightarrow |
| 2601 | 0 |
| 2602 | |
| 2603 | where the leftmost non-zero entry is in degree `0` and the |
| 2604 | rightmost entry in degree `d`. See [Klyachko], eq. (3.2). This |
| 2605 | complex computes the homology of `|\Sigma|\subset N_\RR` with |
| 2606 | arbitrary support, |
| 2607 | |
| 2608 | .. math:: |
| 2609 | |
| 2610 | H_i(K) = H_{d-i}(|\Sigma|, \ZZ)_{\text{non-cpct}} |
| 2611 | |
| 2612 | For a complete fan, this is just the non-compactly supported |
| 2613 | homology of `\RR^d`. In this case, `H_0(K)=\ZZ` and `0` in all |
| 2614 | non-zero degrees. |
| 2615 | |
| 2616 | For a complete fan, there is an extended chain complex |
| 2617 | |
| 2618 | .. math:: |
| 2619 | |
| 2620 | 0 \longrightarrow |
| 2621 | \ZZ \longrightarrow |
| 2622 | \ZZ^{\Sigma(d)} \longrightarrow |
| 2623 | \ZZ^{\Sigma(d-1)} \longrightarrow |
| 2624 | \cdots \longrightarrow |
| 2625 | \ZZ^{\Sigma(0)} \longrightarrow |
| 2626 | 0 |
| 2627 | |
| 2628 | where we take the first `\ZZ` term to be in degree -1. This |
| 2629 | complex is an exact sequence, that is, all homology groups |
| 2630 | vanish. |
| 2631 | |
| 2632 | The orientation of each cone is chose as in |
| 2633 | :meth:`oriented_boundary`. |
| 2634 | |
| 2635 | INPUT: |
| 2636 | |
| 2637 | - ``extended`` -- Boolean (default:False). Whether to |
| 2638 | construct the extended complex, that is, including the |
| 2639 | `\ZZ`-term at degree -1 or not. |
| 2640 | |
| 2641 | - ``base_ring`` -- A ring (default: ``ZZ``). The ring to use |
| 2642 | instead of `\ZZ`. |
| 2643 | |
| 2644 | OUTPUT: |
| 2645 | |
| 2646 | The complex associated to the fan as a :class:`ChainComplex |
| 2647 | <sage.homology.chain_complex.ChainComplex>`. Raises a |
| 2648 | ``ValueError`` if the extended complex is requested for a |
| 2649 | non-complete fan. |
| 2650 | |
| 2651 | EXAMPLES:: |
| 2652 | |
| 2653 | sage: fan = toric_varieties.P(3).fan() |
| 2654 | sage: K_normal = fan.complex(); K_normal |
| 2655 | Chain complex with at most 4 nonzero terms over Integer Ring |
| 2656 | sage: K_normal.homology() |
| 2657 | {0: Z, 1: 0, 2: 0, 3: 0} |
| 2658 | sage: K_extended = fan.complex(extended=True); K_extended |
| 2659 | Chain complex with at most 5 nonzero terms over Integer Ring |
| 2660 | sage: K_extended.homology() |
| 2661 | {0: 0, 1: 0, 2: 0, 3: 0, -1: 0} |
| 2662 | |
| 2663 | Homology computations are much faster over `\QQ` if you don't |
| 2664 | care about the torsion coefficients:: |
| 2665 | |
| 2666 | sage: toric_varieties.P2_123().fan().complex(extended=True, base_ring=QQ) |
| 2667 | Chain complex with at most 4 nonzero terms over Rational Field |
| 2668 | sage: _.homology() |
| 2669 | {0: Vector space of dimension 0 over Rational Field, |
| 2670 | 1: Vector space of dimension 0 over Rational Field, |
| 2671 | 2: Vector space of dimension 0 over Rational Field, |
| 2672 | -1: Vector space of dimension 0 over Rational Field} |
| 2673 | |
| 2674 | The extended complex is only defined for complete fans:: |
| 2675 | |
| 2676 | sage: fan = Fan([ Cone([(1,0)]) ]) |
| 2677 | sage: fan.is_complete() |
| 2678 | False |
| 2679 | sage: fan.complex(extended=True) |
| 2680 | Traceback (most recent call last): |
| 2681 | ... |
| 2682 | ValueError: The extended complex is only defined for complete fans! |
| 2683 | |
| 2684 | The definition of the complex does not refer to the ambient |
| 2685 | space of the fan, so it does not distinguish a fan from the |
| 2686 | same fan embedded in a subspace:: |
| 2687 | |
| 2688 | sage: K1 = Fan([Cone([(-1,)]), Cone([(1,)])]).complex() |
| 2689 | sage: K2 = Fan([Cone([(-1,0,0)]), Cone([(1,0,0)])]).complex() |
| 2690 | sage: K1 == K2 |
| 2691 | True |
| 2692 | |
| 2693 | Things get more complicated for non-complete fans:: |
| 2694 | |
| 2695 | sage: fan = Fan([Cone([(1,1,1)]), |
| 2696 | ... Cone([(1,0,0),(0,1,0)]), |
| 2697 | ... Cone([(-1,0,0),(0,-1,0),(0,0,-1)])]) |
| 2698 | sage: fan.complex().homology() |
| 2699 | {0: 0, 1: 0, 2: Z x Z, 3: 0} |
| 2700 | sage: fan = Fan([Cone([(1,0,0),(0,1,0)]), |
| 2701 | ... Cone([(-1,0,0),(0,-1,0),(0,0,-1)])]) |
| 2702 | sage: fan.complex().homology() |
| 2703 | {0: 0, 1: 0, 2: Z, 3: 0} |
| 2704 | sage: fan = Fan([Cone([(-1,0,0),(0,-1,0),(0,0,-1)])]) |
| 2705 | sage: fan.complex().homology() |
| 2706 | {0: 0, 1: 0, 2: 0, 3: 0} |
| 2707 | |
| 2708 | REFERENCES: |
| 2709 | |
| 2710 | .. [Klyachko] |
| 2711 | A. A. Klyachko, |
| 2712 | Equivariant Bundles on Toral Varieties. |
| 2713 | Mathematics of the USSR - Izvestiya 35 (1990), 337-375. |
| 2714 | """ |
| 2715 | dim = self.dim() |
| 2716 | delta = dict() |
| 2717 | for degree in range(1, dim+1): |
| 2718 | m = matrix(base_ring, len(self(degree-1)), len(self(degree)), base_ring.zero()) |
| 2719 | for i, cone in enumerate(self(degree)): |
| 2720 | boundary = self.oriented_boundary(cone) |
| 2721 | for orientation, d_cone in boundary: |
| 2722 | m[self(degree-1).index(d_cone), i] = orientation |
| 2723 | delta[dim-degree] = m |
| 2724 | |
| 2725 | from sage.homology.chain_complex import ChainComplex |
| 2726 | if not extended: |
| 2727 | return ChainComplex(delta, base_ring=base_ring) |
| 2728 | |
| 2729 | # add the extra entry for the extended complex |
| 2730 | if not self.is_complete(): |
| 2731 | raise ValueError('The extended complex is only defined for complete fans!') |
| 2732 | extension = matrix(base_ring, len(self(dim)), 1, base_ring.zero()) |
| 2733 | generating_cones = self.oriented_boundary(self) |
| 2734 | for orientation, d_cone in generating_cones: |
| 2735 | extension[self(dim).index(d_cone), 0] = orientation |
| 2736 | delta[-1] = extension |
| 2737 | return ChainComplex(delta, base_ring=base_ring) |
| 2738 | |