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 Pencil class.

Basic example

>>> 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)
../_images/pencil_case5.png

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<lmda<lmda_i (where it applies) and signatures (p_i, n_i) for lmda=lmda_i, the signature sequence will be:

(nu_1, (...(p_1, n_1)...), nu_2, (...(p_2, n_2)...), nu_3, (...(p_3, n_3)...), n_4)

Given representations for two quadrics, we can calculate an iteration of the signature sequence for the pecil they span in the library.

>>> 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 SignatureSequence class. This allows to compare two signature sequences and determine whether they are equivalent.

>>> 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 regular_matrix() that returns an alternative matrix for Q2.

>>> 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).

>>> qsic = pencil.intersection()
>>> type(qsic)
<class 'ddg.geometry._quadrics.QuadricIntersection'>
>>> 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 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.

>>> pencil.classification()
['type5b', 'Type 5: The Quadric Surface Intersection Curve has two null-homotopic components joining at a crunode.']

pic1 pic2

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:

>>> 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.

>>> 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.

>>> 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.)