.. _pencils: Pencils ======= Pencils are lines in the projective space of quadrics. They can be spanned by two quadrics ``Q1`` and ``Q2`` and contain all quadrics whose representing matrices are linear combinations of the representatives of ``Q1`` and ``Q2``. In our library, they are represented by instances of the :py:class:`~ddg.geometry.Pencil` class. .. contents:: Table of contents :local: :backlinks: none Basic example ~~~~~~~~~~~~~ .. doctest:: >>> import ddg >>> import numpy as np >>> Q1 = ddg.geometry.Quadric(np.diag([3, -1, 1, 0])) >>> Q2 = ddg.geometry.Quadric( ... np.array([[1, 0, 0, 0], [0, -1, 0, 2], [0, 0, 1, 0], [0, 2, 0, 0]]), ... ) >>> pencil = ddg.geometry.Pencil(Q1, Q2) .. image:: pencil_case5.png :width: 65 % :align: center Signature Sequence ~~~~~~~~~~~~~~~~~~ The signature sequence calculates the signatures of all quadrics in the pencil ``lmda*Q2+Q1`` (which is the affine representation of the pencil ``lmda*Q2+mu*Q1``) and presents them in a sequence of signatures ordered by size of ``lmda``. It only returns the index (positive part of signature) for non-degenerate quadrics and as many brackets around (the non-degenerate part of the) signatures of degenerate quadrics as is the multiplicity of the root ``lmda`` in ``f(lmda):=det(lmda*Q2+Q1)``. If we have degenerate quadrics for values ``lmda_i`` where ``i=1,...,3`` and signatures ``(nu_i, mu_i)`` for ``lmda_i-1>> Q1 = ddg.geometry.Quadric( ... np.array([[-1, 0, 0, 0], [0, 1, 0, -2], [0, 0, -1, 0], [0, -2, 0, 0]]), ... ) >>> Q2 = ddg.geometry.Quadric(np.diag([-3, 1, 1, 0])) >>> pencil = ddg.geometry.Pencil(Q1, Q2) >>> signature_seq = pencil.signature_sequence() >>> signature_seq Signature Sequence with tuples of indices: (2, 2, 3, 2), signatures: (Signature(plus=2, minus=1, zero=1), Signature(plus=2, minus=1, zero=1), Signature(plus=2, minus=1, zero=1)), multiplicities: (2, 1, 1) >>> print(signature_seq) (2, ((2, 1)), 2, (2, 1), 3, (2, 1), 2) Since this calculation depends on the chosen quadrics in the pencil and their representation, there are equivalence classes on the set of signature sequences and the result is only uniquely determined up to equivalence for the pencil. Therefore, signature sequences can be brought into a normal form with help of the :py:class:`~ddg.geometry.signatures.SignatureSequence` class. This allows to compare two signature sequences and determine whether they are equivalent. .. doctest:: >>> print(signature_seq.normalform()) (2, ((1, 2)), 2, (1, 2), 1, (1, 2), 2) The signature sequence can only be calculated for a non-degenerate pencil (that contains at least one non-degenerate quadric) and if ``Q2`` is a non-degenerate pencil in the representation ``lmda*Q2+Q1``. In the calculation this is achieved via the method :py:meth:`~ddg.geometry.Pencil.regular_matrix` that returns an alternative matrix for ``Q2``. .. doctest:: >>> newQ2 = pencil.regular_matrix() >>> newQ2 Quadric( array([[ 3. , 0. , 0. , 0. ], [ 0. , -0.33333333, 0. , -2. ], [ 0. , 0. , -2.33333333, 0. ], [ 0. , -2. , 0. , 0. ]]) ) Quadric intersection ~~~~~~~~~~~~~~~~~~~~ The points of intersection of two quadrics in a pencil are contained in every quadric of the pencil. In ``RP³`` the intersection constitutes a quadric surface intersection curve (QSIC). .. doctest:: >>> qsic = pencil.intersection() >>> type(qsic) >>> qsic.dimension_complex 1 >>> print(qsic) intersection of the two quadrics Q1 and Q2 in 3D projective space Q1: quadric in 3D projective space signature: (1, 3) matrix: [[-1. 0. 0. 0.] [ 0. 1. 0. -2.] [ 0. 0. -1. 0.] [ 0. -2. 0. 0.]] Q2: quadric in 3D projective space signature: (2, 1, 1) matrix: [[-3. 0. 0. 0.] [ 0. 1. 0. 0.] [ 0. 0. 1. 0.] [ 0. 0. 0. 0.]] Classification ~~~~~~~~~~~~~~ In 3-dimensional real projective space ``RP³`` the signature sequences of pencils provide a classification of pencils and determine topological properties of their intersection curves. In the library, the classification is part of the :py:class:`~ddg.geometry.Pencil` class (albeit it uses the signature sequence) and returns a tuple with a short handle for distinguishing between cases and a description of the intersection curve. .. doctest:: >>> pencil.classification() ['type5b', 'Type 5: The Quadric Surface Intersection Curve has two null-homotopic components joining at a crunode.'] |pic1| |pic2| .. |pic1| image:: pencil_case5_intersection.png :width: 45% .. |pic2| image:: qsic.png :width: 45% Index Sequence and Segre Symbol ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From the classification, a normal form of the index sequence and Segre symbol of the pencil can be read off. The index seqence resembles the signature sequence, but instead of signatures for the degenerate quadrics, it represents the blocks in the quadric pair canonical form (QPCF) for the pair ``Q2`` and ``Q1`` (a representation related to the Jordan normal form of ``Q2⁻¹Q1``) corresponding to the root ``lmda``. Here, the symbols represent Jordan blocks of different sizes: - ``|`` size 1 - ``≀...≀₊`` size n (number of wavy lines) with positive sign in the QPCF - ``≀...≀₋`` size n (number of wavy lines) with negative sign in the QPCF (The QPCF is calculated as follows: If ``Q2`` is non-singular and ``Q2⁻¹Q1`` has Jordan normal form ``J=diag(J_1,..., J_n)``, then ``Q2, Q1`` are simultaneously congruent to:: diag(±E_1, ..., ±E_n), diag(±E_1J_1, ..., ±E_nJ_n), where ``E_i`` is a matrix of the form:: | 1 | | . | | . | | . | | 1 |) In our example, the index sequence can be determined as follows: .. doctest:: >>> index_seq = pencil.index_sequence() >>> print(index_seq) 〈2 ≀≀₋ 2 | 1 | 2〉 The index sequence of a pencil is always returned in normal form. The Segre symbol denotes the sizes of Jordan blocks in the Jordan normal form of ``Q2⁻¹Q1`` (and hence in the QPCF of ``Q1`` and ``Q2``) to roots ``lmda`` (that are eigevalues of the Jordan normal form). Jordan blocks to the same eigenvalue are collected in parenthesis and ordered descendingly by size. The subscript number after the Segre symbol denotes how many different real root values there are in the Jordan normal form. .. doctest:: >>> segre_symb = pencil.segre_symbol() >>> print(segre_symb) [2 1 1]₃ Eigenvalue Curve Function ~~~~~~~~~~~~~~~~~~~~~~~~~ This module also provides means to calculate the eigenvalue curve function of a pencil defined as ``f(u, lmda) = det(lmda*Q2+Q1-u*I)``, where ``I`` is the identity matrix. .. doctest:: >>> import sympy as sp >>> u = sp.Symbol("u") >>> lmda = sp.Symbol("lmda") >>> f = pencil.eigenvalue_curve_function() >>> f(u, lmda) (-u*(-2.33333333333333*lmda - u - 1.0)*(-0.333333333333333*lmda - u + 1.0) - 4.0*(-lmda - 1)**2*(-2.33333333333333*lmda - u - 1.0))*(3.0*lmda - u - 1.0) (The eigenvalue curve function is used for special cases in the classification algorithm where the signature sequence is ambiguous and admits different Segre symbols.)