Skip to content

Pethő–de Weger ideal and recurrence branch enumeration #86

Description

@roed-math

Goal

The exact algebraic layer of the generalized Ramanujan–Nagell pipeline: turn x^2 + D = k * b^n on the supported regimes into a finite, exactly computed list of branches, each of which is a binary recurrence with exact integer initial data whose terms must hit one exact integer target. No analysis, no bounds, no floating point: everything here is ideal arithmetic in the maximal order of an imaginary quadratic field plus integer recurrences. #87 bounds the branch index; #88 wires the result into the solver.

Algorithmic template: Pethő–de Weger, Products of prime powers in binary recurrence sequences I. The hyperbolic case, with an application to the generalized Ramanujan–Nagell equation, Mathematics of Computation 47 (1986), 713–727 (PethoDeWeger1986, bibliography addition below). The reduction written out here is stated in full with inline proofs — every lemma is proved in this issue, so the citation is attribution, not a load-bearing pointer (ground: proved inline; and the resulting branch enumeration was machine-checked against exhaustive search on Sage 10.7 — see "Acceptance criteria").

Target base

Dependencies

Supported inputs

Concrete integers D >= 1, k >= 1, b >= 2 with gcd(k, b) = 1 and b not a perfect power (#88 performs that reduction upstream), in exactly two regimes:

  • (A) gcd(2*D, b) = 1 (so b is odd and coprime to D);
  • (B-split) b = 2 and D ≡ 7 (mod 8) (k is then odd), with n <= 2 checked directly and n >= 3 handled by the analysis below.

Which D are supported (explicitly). Every concrete D >= 1 satisfying the regime condition on bD need not be squarefree, and D ≡ 3 (mod 4) is fully supported. The reason is the choice of ring: all ideal arithmetic happens in the maximal order O_K of K = QQ(sqrt(-D)) = QQ(sqrt(-d0)), never in the (in general non-maximal) order ZZ[sqrt(-D)]. ZZ[sqrt(-D)] has conductor f in O_K when d0 ≢ 3 (mod 4) and conductor 2*f when d0 ≡ 3 (mod 4) (notation of §1); working in O_K means unique factorization of ideals and "to contain is to divide" are available with no conductor hypothesis at all. The non-maximality resurfaces in exactly one place — the exact coordinate target B_q = F of §6, stated in the ZZ-basis of O_K — and nowhere in the hypotheses. The D ≡ 3 (mod 4) half-integer basis is what makes F = 2*f/delta rather than f/delta, and it is also what makes the (B-split) half-integer factorization legal in the first place.

The b = 2, D ≢ 7 (mod 8) cases (odd and even D) are #85's elementary regimes and are not treated here. Everything else (b > 2 sharing an odd prime with D, gcd(k, b) > 1, D < 1, non-concrete parameters) stays on #88's fallback.

Algorithm and conventions

1. Setup and notation (all exact)

  • D = f^2 * d0 with d0 squarefree (d0 = ZZ(D).squarefree_part(), f = ZZ(D // d0).isqrt()).

  • K = QQ(sqrt(-d0)), maximal order O_K = ZZ[omega] with

    omega := (1 + sqrt(-d0))/2   if d0 ≡ 3 (mod 4),      omega := sqrt(-d0)   otherwise.
    

    (1, omega) is a ZZ-basis of O_K; every element below is recorded by its exact coordinates in it.

  • Regime constants:

    regime (A):        delta := 1,   shift s := 0
    regime (B-split):  delta := 2,   shift s := 2
    m := n - s        (so m >= 0 exactly when n >= s)
    
  • alpha := (x + sqrt(-D))/delta, alphabar its K/QQ-conjugate, and

    beta := alpha - alphabar = (2/delta) * sqrt(-D),    beta^2 = -(2/delta)^2 * D  ∈ ZZ.
    
  • F := 2*f/delta if d0 ≡ 3 (mod 4), else F := f/delta (in the second case delta = 1, so F = f); F is a positive integer in every supported case.

alpha lies in O_K, and the norm equation.

  • Regime (A): alpha = x + f*sqrt(-d0) ∈ O_K trivially, and alpha*alphabar = x^2 + D = k*b^n = k*b^m.

  • Regime (B-split): for n >= 3, x^2 = k*2^n - D ≡ -D ≡ 1 (mod 8), so x is odd; f is odd too (D odd), and d0 ≡ D ≡ 7 (mod 8) (because f^2 ≡ 1 (mod 8)), so d0 ≡ 3 (mod 4) and omega = (1 + sqrt(-d0))/2. Then

    alpha = (x + f*sqrt(-d0))/2 = (x - f)/2 + f*omega ∈ O_K      ((x - f)/2 ∈ ZZ since x, f are both odd),
    alpha*alphabar = (x^2 + D)/4 = k*2^(n-2) = k*b^m.
    

    n ∈ {0, 1, 2} is checked directly (exact square test), so n >= 3 may be assumed.
    Also disc(K) = -d0 ≡ 1 (mod 8), so 2 splits in K — the fact that makes this regime work at all.

So in both regimes, in O_K:

(alpha) * (alphabar) = (k) * (b)^m,        m = n - s >= 0.

2. The ideal-gcd lemma (stated and proved inline)

Lemma IG. Let g := (alpha) + (alphabar) (the ideal gcd of the two principal ideals) in the Dedekind domain O_K. Then g divides both (alpha + alphabar) and (alpha - alphabar) = (beta). In particular

regime (A):        g | (2*sqrt(-D));
regime (B-split):  g | (sqrt(-D)).

Proof. g contains alpha and alphabar, hence contains their sum and their difference; in a Dedekind domain an ideal containing an element divides the principal ideal it generates ("to contain is to divide"). beta = (2/delta)*sqrt(-D) is 2*sqrt(-D) when delta = 1 and sqrt(-D) when delta = 2. ∎

Corollary IG1 (the exact consequence: g is coprime to b). In both supported regimes g + (b) = (1).

Proof. Regime (A): suppose a prime P of O_K divides both g and (b), and let p be the rational prime under P, so p | b; gcd(2*D, b) = 1 gives p odd and p ∤ D. By Lemma IG, P | (2)*(sqrt(-D)), so P | (2) or P | (sqrt(-D)) (P prime). P | (2) forces p = 2, excluded. P | (sqrt(-D)) gives P | (sqrt(-D))^2 = (D), hence p | N(P) | N((D)) = D^2, so p | D, excluded. ∎
Regime (B-split): g | (sqrt(-D)) gives N(g) | N((sqrt(-D))) = D, which is odd, so no prime of g lies over 2 = b. ∎

(Both regimes also give g | (2*x/delta) from the sum, which is not needed below.)

Corollary IG2 (no ramification over b). Every prime p | b is unramified in K. Proof. disc(K) ∈ {-d0, -4*d0} divides 4*d0. Regime (A): gcd(2*d0, b) = 1 since d0 | D, so gcd(disc(K), b) = 1. Regime (B-split): b = 2 and disc(K) = -d0 ≡ 1 (mod 8), so 2 is unramified (indeed split). Unramified ⟺ p ∤ disc(K). ∎

3. Inert primes kill every positive exponent

Lemma INERT. If some p | b is inert in K, then every solution has m = 0 (i.e. n = s).

Proof. P := (p) is prime in O_K and stable under conjugation, so v_P(alpha) = v_P(alphabar). By IG1, min(v_P(alpha), v_P(alphabar)) = v_P(g) = 0, hence both vanish. But v_P(alpha) + v_P(alphabar) = v_P((k)*(b)^m) = v_p(k) + m*v_p(b) = m*v_p(b) (using gcd(k, b) = 1), and v_p(b) >= 1, so m = 0. ∎

Implementation: detect this with len(K.primes_above(p)) == 1 for some p | b (with IG2 this means inert), emit the finitely many direct checks for n = s (and, in regime (B-split), n ∈ {0, 1, 2}), and record Evidence(kind="structural", ...) with the argument above. This is a complete proof of the whole instance; no branch enumeration is needed.

4. The b-part splits: branch data

Assume from now on that every p | b splits: (p) = P_p * Pbar_p with P_p != Pbar_p. A splitting pattern is a choice Q_p ∈ {P_p, Pbar_p} for each p | b; write b = prod_p p^(a_p) and

C := prod_{p | b} Q_p^(a_p),        N(C) = b,        C * Cbar = (b).

Lemma SPLIT. For every solution there is a unique splitting pattern and a unique ideal A of O_K with

(alpha) = A * C^m,        A * Abar = (k),        A coprime to (b).

Proof. Write (alpha) = B_alpha * A with B_alpha the part supported on primes over b and A the part coprime to (b). Conjugating, (alphabar) = Bbar_alpha * Abar. Multiplying and comparing the (b)-supported and (b)-coprime parts of (k)*(b)^m separately (legitimate because gcd(k, b) = 1): B_alpha * Bbar_alpha = (b)^m and A * Abar = (k). For each p | b, v_{P_p}(alpha) + v_{P_p}(alphabar) = m*a_p and min(v_{P_p}(alpha), v_{P_p}(alphabar)) = 0 by IG1, so one of the two is m*a_p and the other 0; setting Q_p := the prime of {P_p, Pbar_p} at which alpha carries the full exponent gives B_alpha = C^m (the choices at P_p and Pbar_p are consistent because v_{Pbar_p}(alpha) = v_{P_p}(alphabar)). ∎

Lemma CLASS. Fix a pattern and an A with A*Abar = (k). Let t := ord([C]) in the class group Cl(K) (so t | h := |Cl(K)|). If there is no r ∈ {0, ..., t-1} with [A]*[C]^r = 1, the branch is provably empty. Otherwise that r is unique, every solution of this branch has m ≡ r (mod t), and writing m = t*q + r with q >= 0:

alpha = u * tau * mu^q,       tau := a generator of A*C^r,   mu := a generator of C^t,   u ∈ O_K^*.

Proof. (alpha) = A*C^m is principal, so [A]*[C]^m = 1 in Cl(K); [C]^m depends only on m mod t, which gives existence/uniqueness of r and the congruence. A*C^r and C^t are principal by construction, and A*C^m = (A*C^r)*(C^t)^q, so (alpha) = (tau*mu^q); two generators of the same principal ideal differ by a unit. m >= 0 and m ≡ r (mod t) with 0 <= r < t force q >= 0. ∎

Units. K is imaginary quadratic, so O_K^* is exactly the group of roots of unity in K: {±1} for d0 ∉ {1, 3}, {±1, ±i} for d0 = 1, the six sixth roots of unity for d0 = 3. Write w := |O_K^*| ∈ {2, 4, 6} (K.unit_group().zeta_order(); generator K.unit_group().torsion_generator()).

A branch is therefore the tuple (pattern, A, r, t, tau, mu, u). tau and mu are only determined up to units, but u ranges over all of O_K^*, so any deterministic choice of generator is sound; pin Sage's gens_reduced()[0] for reproducibility.

Lemma FINITE (branch count). The number of branches is at most

2^omega(b) * sigma_0((k)) * w,

where omega(b) is the number of distinct primes dividing b and sigma_0((k)) is the number of ideal divisors of (k) in O_K (both exactly computable; sigma_0((k)) <= prod_{p^e || k} (2*e + 1)). Proof. At most 2^omega(b) patterns; A | A*Abar = (k) so A is one of the sigma_0((k)) ideal divisors of (k); at most one r per (pattern, A) by Lemma CLASS; w units. ∎ The class number enters only through t | h, which fixes the arithmetic progression m ≡ r (mod t) — it does not multiply the branch count.

5. Exact per-branch data consumed downstream

For each branch, with T := Tr(mu) = mu + mubar ∈ ZZ and N := N(mu) = mu*mubar ∈ ZZ:

  • N = N(C)^t = b^t (positive; K is imaginary quadratic);
  • N(tau) = N(A) * N(C)^r = k * b^r, hence |mu| = b^(t/2) and |tau| = sqrt(k*b^r);
  • v_{Q_p}(mu) = t*a_p > 0 and v_{Qbar_p}(mu) = 0; v_{Q_p}(tau) = r*a_p and v_{Qbar_p}(tau) = 0;
  • p ∤ T for every p | b. Proof. v_{Qbar_p}(mu) = 0 and v_{Qbar_p}(mubar) = v_{Q_p}(mu) = t*a_p >= 1, so v_{Qbar_p}(T) = v_{Qbar_p}(mu + mubar) = min(0, t*a_p) = 0, while p | T would give v_{Qbar_p}(T) >= v_{Qbar_p}(p) = 1. ∎ (Explicit analytic bounds and certified reductions for Ramanujan–Nagell #87 needs exactly this to represent mu p-adically.)

6. The recurrence and the target normalization

Write w_q := u * tau * mu^q = A_q + B_q * omega with A_q, B_q ∈ ZZ (coordinates in the ZZ-basis (1, omega) of O_K). Since mu^2 = T*mu - N:

A_{q+2} = T*A_{q+1} - N*A_q,       B_{q+2} = T*B_{q+1} - N*B_q,
T = Tr(mu) ∈ ZZ,   N = N(mu) = b^t ∈ ZZ,

with exact initial data (A_0, B_0) := coordinates of u*tau and (A_1, B_1) := coordinates of u*tau*mu. This is the Lucas-sequence pair of the branch: both components run on the companion recurrence of mu's minimal polynomial x^2 - T*x + N, and they differ only in their initial data. (Proof: w_{q+2} = w_q * mu^2 = T*w_{q+1} - N*w_q; taking coordinates is ZZ-linear. ∎)

Target-power normalization. With m = t*q + r and n = m + s:

(x, n) is a solution arising from this branch at index q   <=>   B_q = F,
and then                    x = delta * A_q + (f if d0 ≡ 3 (mod 4) else 0).

Proof. alpha = x/delta + (f/delta)*sqrt(-d0). If d0 ≡ 3 (mod 4), A_q + B_q*omega = (A_q + B_q/2) + (B_q/2)*sqrt(-d0), so B_q/2 = f/delta — i.e. B_q = 2*f/delta = F — and x/delta = A_q + B_q/2 = A_q + f/delta, i.e. x = delta*A_q + f. If d0 ≢ 3 (mod 4) then delta = 1 and A_q + B_q*sqrt(-d0) = x + f*sqrt(-d0) gives B_q = f = F and x = A_q. Conversely, B_q = F makes w_q of exactly that shape, and w_q * conj(w_q) = N(u*tau)*N(mu)^q = k*b^(r + t*q) = k*b^m reproduces the equation. ∎

Relation to the Pethő–de Weger shape. PethoDeWeger1986 studies terms of a binary recurrence that equal a prescribed product of prime powers. The normalization above is exactly that shape with the b-power absorbed into the index: the target is the constant F, and the prime power reappears as N = b^t inside the recurrence and as n = t*q + r + s in the exponent. This is also the shape of the classical Nagell argument (B_q = ±1 for the (7, 1, 2) sequence).

7. Pinned Sage realization (verified on Sage 10.7)

  • d0 = ZZ(D).squarefree_part(), f = ZZ(D // d0).isqrt() (f^2*d0 == D asserted).
  • K = QuadraticField(-d0, 'a'); K.class_group(), K.unit_group().zeta_order(), K.unit_group().torsion_generator().
  • K.primes_above(p): length 1 ⟺ inert (ramification excluded by IG2), length 2 ⟺ split.
  • Ideal divisors of (k): expand K.ideal(k).factor(); keep A iff A * A.apply_morphism(K.hom([-K.gen()])) == K.ideal(k). Note: in Sage 10.7 a NumberFieldFractionalIdeal has no .conjugate() method — conjugation must go through apply_morphism with the nontrivial automorphism (verified; the naive .conjugate() call raises AttributeError).
  • t = K.class_group()(C).order(); mu = (C**t).gens_reduced()[0]; tau = (A * C**r).gens_reduced()[0]; the identities K.ideal(mu) == C**t and K.ideal(tau) == A*C**r are asserted.
  • Coordinates: list(K(z)) returns (z0, z1) with z = z0 + z1*sqrt(-d0); then (A_q, B_q) = (z0 - z1, 2*z1) if d0 % 4 == 3, else (z0, z1).
  • T = mu.trace(), N = mu.norm() (asserted equal to b^t).

Output and API contract

A private, purely algebraic helper in solvers.py (no public API change):

@dataclass(frozen=True)
class _RNBranch:
    pattern: tuple    # ((p, a_p, Q_p), ...) — the chosen prime over each p | b
    ideal_A: object   # the O_K-ideal A with A*Abar = (k)
    r: ZZ             # m ≡ r (mod t)
    t: ZZ             # order of [C] in Cl(K)
    tau: object       # generator of A*C^r      (exact K element)
    mu: object        # generator of C^t        (exact K element)
    unit: object      # u ∈ O_K^*               (exact K element)
    trace: ZZ         # T = Tr(mu)
    norm: ZZ          # N = N(mu) = b^t
    init: tuple       # ((A_0, B_0), (A_1, B_1)) — exact ZZ coordinates

_rn_setup(D, k, b)    -> shared record (d0, f, K, omega, delta, shift, F, beta,
                         class_number, w, regime)
_rn_branches(setup)   -> tuple of _RNBranch, or the INERT verdict
  • Every numeric field is an exact Sage ZZ; every algebraic field is an exact K element. No floats, no balls, no p-adics in this child.
  • Deterministic: the pattern order is the factorization order of b with K.primes_above(p) order; the A order is the divisor-expansion order of K.ideal(k).factor(); the unit order is zeta^j, j = 0 .. w-1. The tuple is reproducible bit for bit across runs (locked by the fixtures below).
  • Empty branches (no r) are dropped from the returned tuple, and their count is reported so Ramanujan–Nagell solver integration and regression fixtures #88 can put it in the structural evidence text (the sentence "all N branches are provably empty", with the exact count substituted, is itself a complete proof of kind="empty").
  • The INERT verdict carries the offending prime p and the argument text for Evidence(kind="structural", ...).

Failure and fallback semantics

  • Out-of-regime inputs never reach this child (Ramanujan–Nagell solver integration and regression fixtures #88's guards); the helper asserts its preconditions and raises ValueError if they are violated — an internal-consistency failure, not a user-facing outcome.
  • Sage failures inside the exact ideal arithmetic (class group, gens_reduced) surface as outcome="cas-failure" per the existing vocabulary, raised by Ramanujan–Nagell solver integration and regression fixtures #88's wrapper.
  • This child never returns partial data and never decides completeness: it either produces the full finite branch list (or the INERT/all-empty verdict) or fails typed.

Acceptance criteria

Every table below was produced by the specification above and cross-checked against exhaustive search (k*b^n - D tested for squareness with exact integer arithmetic) on Sage 10.7; the union of the branch hits equals the brute-force solution set in each case.

  • Fixture (D, k, b) = (7, 1, 2) (regime B-split, h = 1). d0 = 7, f = 1, delta = 2, s = 2, F = 1, beta = sqrt(-7), h = 1, w = 2, sigma_0((1)) = 1, #A = 1, t = 1, r = 0, tau = 1, 4 branches, each with T = 1, N = 2:

    Q_2 mu u (A_0, B_0) (A_1, B_1) hits (q, x, n)
    ((1-sqrt(-7))/2) (1-sqrt(-7))/2 1 (1, 0) (1, -1) (3,-5,5), (5,11,7), (13,-181,15)
    ((1-sqrt(-7))/2) (1-sqrt(-7))/2 -1 (-1, 0) (-1, 1) (1,-1,3), (2,3,4)
    ((1+sqrt(-7))/2) (1+sqrt(-7))/2 1 (1, 0) (0, 1) (1,1,3), (2,-3,4)
    ((1+sqrt(-7))/2) (1+sqrt(-7))/2 -1 (-1, 0) (0, -1) (3,5,5), (5,-11,7), (13,181,15)

    Union of (|x|, n): exactly {(1, 3), (3, 4), (5, 5), (11, 7), (181, 15)}it must agree with the hardwired Nagell 1961 answer, and the test asserts that equality. (mu's minimal polynomial is x^2 - x + 2.)

  • Fixture (D, k, b) = (5, 1, 3) (regime A, class number h = 2). d0 = 5, f = 1, delta = 1, s = 0, F = 1, beta = 2*sqrt(-5), w = 2, #A = 1, t = 2, r = 0, tau = 1, 4 branches, each with T = -4, N = 9:

    Q_3 mu u (A_0, B_0) (A_1, B_1) hits (q, x, n)
    (3, a+1) a - 2 1 (1, 0) (-2, 1) (1, -2, 2)
    (3, a+1) a - 2 -1 (-1, 0) (2, -1)
    (3, a+2) -a - 2 1 (1, 0) (-2, -1)
    (3, a+2) -a - 2 -1 (-1, 0) (2, 1) (1, 2, 2)

    (a = sqrt(-5); mu's minimal polynomial is x^2 + 4*x + 9.) Union: exactly {(2, 2)}, matching brute force for n <= 300. This fixture locks the class-number->1 behaviour: [C_3] has order t = 2, so only even n can occur and the odd-n half of the problem is closed by the "non-principal ⟹ provably empty" step, not by search.

  • Fixture (D, k, b) = (2, 2, 3) (regime A, k > 1, non-trivial tau). d0 = 2, f = 1, delta = 1, s = 0, F = 1, beta = 2*sqrt(-2), h = 1, w = 2, sigma_0((2)) = 3, #A = 1 (the ramified A = (sqrt(-2)), so tau = sqrt(-2) != 1), t = 1, r = 0, 4 branches, each with T = -2, N = 3:

    Q_3 mu u (A_0, B_0) (A_1, B_1) hits (q, x, n)
    (-a-1) -a - 1 1 (0, 1) (2, -1) (0, 0, 0)
    (-a-1) -a - 1 -1 (0, -1) (-2, 1) (1,-2,1), (2,4,2), (5,22,5)
    (a-1) a - 1 1 (0, 1) (-2, -1) (0, 0, 0)
    (a-1) a - 1 -1 (0, -1) (2, 1) (1,2,1), (2,-4,2), (5,-22,5)

    Union: exactly {(0, 0), (2, 1), (4, 2), (22, 5)}, matching brute force for n <= 60.

  • Inert regression. (D, k, b) = (1, 1, 3): 3 is inert in QQ(i), so Lemma INERT applies and the complete answer is the direct check at n = 0, namely {(0, 0)} (brute force for n <= 300 agrees). Same for (D, k, b) = (3, 4, 5) (5 inert in QQ(sqrt(-3)), answer {(1, 0)}) and (7, 1, 3), (9, 4, 7), (12, 1, 5) (answer empty).

  • All-branches-empty regression. (D, k, b) = (6, 5, 7): h = 2, #A = 2, sigma_0((5)) = 4, and no (pattern, A) pair admits a residue r — every branch is provably empty, so the instance is proved to have no solutions at all (kind="empty", completeness="proved"), with no search involved. Brute force for n <= 40 agrees. Same verdict, with non-empty branch lists this time, for (14, 9, 5) (h = 4, sigma_0((9)) = 9, #A = 3, 12 branches, no hits) and (2, 9, 11) (h = 1, sigma_0((9)) = 9, #A = 3, 12 branches, no hits).

  • F != 1 regression. (11, 1, 3): d0 = 11 ≡ 3 (mod 4), f = 1, so F = 2; 4 branches; answer {(4, 3)}. (4, 1, 5): d0 = 1, f = 2, so F = 2 and w = 4 (8 branches); answer {(1, 1), (11, 3)}. (1, 1, 5): w = 4, 8 branches, answer {(0, 0), (2, 1)}. These lock the f > 1 and d0 ≡ 3 (mod 4) coordinate targets and the w ∈ {4, 6} unit sets.

  • Branch-count bound. For every fixture above the test asserts len(branches) <= 2^omega(b) * sigma_0((k)) * w (Lemma FINITE).

  • Determinism. The branch tuple (including generator choices and ordering) is reproduced bit for bit on repeated runs; the tables above are committed as the expected values.

  • Bibliography: add PethoDeWeger1986 to data/references.bib — Attila Pethő, Benjamin M. M. de Weger, Products of prime powers in binary recurrence sequences I. The hyperbolic case, with an application to the generalized Ramanujan–Nagell equation, Mathematics of Computation, volume 47, 1986, pages 713–727 (tools/check_references.py's REQUIRED_FIELDS are author, title, year — all pinned here, together with journal, volume and pages. The optional doi/url fields are not pinned in this issue and are not load-bearing: add them only if verified at commit time by tools/check_references.py with network access, and add a url only if it points to a legally free copy.) Add the key to the ramanujan-nagell family YAML with why = "the binary-recurrence reduction of the generalized Ramanujan–Nagell equation implemented by the branch enumeration". make references and make registry-docs must pass.

  • Every function (private helpers included) has a Sage-convention docstring with INPUT/OUTPUT and EXAMPLES passing sage -t; make test, make doctest, make coverage (docstring coverage stays 100%) all clean.

Out of scope

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions