PF ideals and their power series solutions
This commit is contained in:
@@ -99,3 +99,75 @@ def test_simplify_factorises_theta_polynomial_per_z_monomial():
|
||||
# The underlying (expanded) operator is unchanged - only the display string differs.
|
||||
assert simplified.operator == op.operator
|
||||
assert simplified.operator_string == "-5*(5*theta0 + 4)*(5*theta0 + 3)*(5*theta0 + 2)*(5*theta0 + 1)*z0 + theta0^4"
|
||||
|
||||
|
||||
def test_find_power_series_solution_recovers_quintic_period():
|
||||
# The quintic's Picard-Fuchs operator has indicial equation theta0^4 = 0 at z0 = 0
|
||||
# (a quadruple root at 0), so its holomorphic (non-logarithmic) power series solution
|
||||
# is found at indicial 0. Up to normalisation this is the classic quintic period
|
||||
# 1 + 120*z0 + 113400*z0^2 + ...
|
||||
op = PFOperator(
|
||||
"theta0^4 - 5*z0*(5*theta0 + 1)*(5*theta0 + 2)*(5*theta0 + 3)*(5*theta0 + 4)",
|
||||
no_variables=1,
|
||||
)
|
||||
ideal = PFIdeal([op])
|
||||
|
||||
solutions = ideal.find_power_series_solution(indicials=[0], order=3)
|
||||
|
||||
assert len(solutions) == 1
|
||||
assert solutions[0].coefficients == {(0,): {(0,): 1, (1,): 120, (2,): 113400, (3,): 168168000}}
|
||||
|
||||
|
||||
def test_find_power_series_solution_handles_rational_indicial():
|
||||
# theta0 - 1/2 kills z0^(1/2 + k) only when k = 0, since its eigenvalue is 1/2 + k;
|
||||
# so at indicial 1/2 there is exactly one solution (a constant multiple of sqrt(z0)),
|
||||
# while at indicial 0 no power series solution exists at all.
|
||||
op = PFOperator("theta0 - 1/2", no_variables=1)
|
||||
ideal = PFIdeal([op])
|
||||
|
||||
solutions = ideal.find_power_series_solution(indicials=[1/2], order=3)
|
||||
assert len(solutions) == 1
|
||||
assert solutions[0].coefficients == {(0,): {(0,): 1, (1,): 0, (2,): 0, (3,): 0}}
|
||||
assert solutions[0].period_string == "sqrt(z0)"
|
||||
|
||||
assert ideal.find_power_series_solution(indicials=[0], order=3) == []
|
||||
|
||||
|
||||
def test_ideal_finds_holomorphic_solution_and_recovers_first_operator():
|
||||
# Ideal generated by two operators (paper's z1, z2, theta1, theta2 <-> code's z0, z1,
|
||||
# theta0, theta1):
|
||||
# M1 = theta2*(-2*theta1+2*theta2-1) + 2*(theta1-2*theta2-1)*(theta1-2*theta2)*z2
|
||||
# M2 = theta1^2*(2*(theta1-2*theta2)*z2-theta2) - 16*(2*theta1+1)*(4*theta1+1)*(4*theta1+3)*z1*z2
|
||||
M1 = PFOperator(
|
||||
"theta1*(-2*theta0 + 2*theta1 - 1) + 2*(theta0 - 2*theta1 - 1)*(theta0 - 2*theta1)*z1",
|
||||
no_variables=2,
|
||||
)
|
||||
M2 = PFOperator(
|
||||
"theta0^2*(2*(theta0 - 2*theta1)*z1 - theta1) - 16*(2*theta0 + 1)*(4*theta0 + 1)*(4*theta0 + 3)*z0*z1",
|
||||
no_variables=2,
|
||||
)
|
||||
ideal = PFIdeal([M1, M2])
|
||||
|
||||
solutions = ideal.find_power_series_solution(indicials=[0, QQ(1) / 2], order=6)
|
||||
assert len(solutions) == 1
|
||||
|
||||
period = solutions[0]
|
||||
assert period.apply_operator(M1).coefficients == {}
|
||||
assert period.apply_operator(M2).coefficients == {}
|
||||
# Normalisation convention: the free parameter at the leading (0, 0) coefficient is 1.
|
||||
assert period.coefficients[(0, 0)][(0, 0)] == 1
|
||||
assert period.coefficients[(0, 0)][(1, 1)] == -32
|
||||
|
||||
# M1 lives entirely within z_degree <= 1, theta_degree <= 2 (a single bare factor of
|
||||
# z2, quadratic in theta), so this is the natural Ansatz level to look for it at.
|
||||
recovered = period.find_annihilating_operators(z_degree=1, theta_degree=2)
|
||||
assert len(recovered) == 1
|
||||
|
||||
# recovered[0] should be a scalar multiple of M1 - compare via the coefficient of the
|
||||
# bare theta2 (theta1 in code) monomial, which is nonzero in M1.
|
||||
ratio = (
|
||||
recovered[0].operator.monomial_coefficient(M1.theta_gens[1])
|
||||
/ M1.operator.monomial_coefficient(M1.theta_gens[1])
|
||||
)
|
||||
assert ratio != 0
|
||||
assert recovered[0].operator == ratio * M1.operator
|
||||
|
||||
Reference in New Issue
Block a user