diff --git a/sage/period_computation.sage b/sage/period_computation.sage index 35c75a3..cc18808 100644 --- a/sage/period_computation.sage +++ b/sage/period_computation.sage @@ -1,6 +1,8 @@ import logging from sage.all import sage_eval, PolynomialRing, QQ, SR, log, var +load("sage/util.py") + # Logger logger = logging.getLogger(__name__) logging.basicConfig(level=logging.DEBUG) @@ -10,25 +12,66 @@ class PFOperator: A class representing a Picard-Fuchs operator in a single variable z. Independent of the given order, the variables are assumed to be left of the derivatives. + + The variables extra_locals are needed for symbolic coefficients used for + operator Ansätze. """ - def _operator_from_string(self, operator_string: str) -> "sage.rings.polynomial.polynomial_ring.Polynomial": + def _operator_from_string(self, operator_string: str, extra_locals: dict = None): z_names = ['z%d' % i for i in range(self.no_variables)] theta_names = ['theta%d' % i for i in range(self.no_variables)] - self.ring = PolynomialRing(QQ, z_names + theta_names) + base_ring = SR if extra_locals else QQ + self.ring = PolynomialRing(base_ring, z_names + theta_names) self.z_gens = self.ring.gens()[:self.no_variables] self.theta_gens = self.ring.gens()[self.no_variables:] + parse_locals = dict(self.ring.gens_dict()) + if extra_locals: + parse_locals.update(extra_locals) + try: - operator = sage_eval(operator_string, locals=self.ring.gens_dict()) + operator = sage_eval(operator_string, locals=parse_locals) + return operator except Exception as e: raise ValueError("Invalid operator string: %s" % e) - return self.ring(operator) - def __init__(self, operator_string: str, no_variables: int = 1): + def __init__(self, operator_string: str, no_variables: int = 1, extra_locals: dict = None): self.no_variables = no_variables - self.operator = self._operator_from_string(operator_string) + self.operator_string = operator_string + self.operator = self._operator_from_string(operator_string, extra_locals=extra_locals) logger.info("Initialised PFOperator: %s", self.operator) +class PFOperatorAnsatz(PFOperator): + """ + A class for Picard-Fuchs operator Ansätze, characterised by number of variables and their z- and theta-multi-degrees. + """ + def __init__(self, no_variables: int, theta_degree: int, z_degree: int): + self.no_variables = no_variables + self.theta_degree = theta_degree + self.z_degree = z_degree + + z_indices = _multi_indices(self.no_variables, z_degree) + theta_indices = _multi_indices(self.no_variables, theta_degree) + + # Every (z_index, theta_index) pair allowed by the degree bounds gets its own + # fresh unknown, to be solved for once the Ansatz is applied to a period. + operator_terms = [ + (var("b_" + "_".join(str(x) for x in z_index + theta_index)), (z_index, theta_index)) + for z_index in z_indices + for theta_index in theta_indices + ] + + def _monomial_factors(index, name): + return [f"{name}{i}^{exp}" for i, exp in enumerate(index) if exp > 0] + + operator_string = " + ".join( + "*".join([str(coeff), *_monomial_factors(z_index, "z"), *_monomial_factors(theta_index, "theta")]) + for coeff, (z_index, theta_index) in operator_terms + ) + + extra_locals = {str(coeff): coeff for coeff, _ in operator_terms} + super().__init__(operator_string=operator_string, no_variables=no_variables, extra_locals=extra_locals) + + class Period: """ A class representing a period as a formal power series in z-variables and their logs. @@ -211,25 +254,13 @@ class PeriodAnsatz(Period): A class for period Ansätze, characterised by number of variables and their z- and log-multi-degrees. """ - def _multi_indices(self, total_degree: int) -> list: - # All no_variables-tuples of non-negative integers whose entries sum to at most total_degree. - def helper(remaining_vars, remaining_degree): - if remaining_vars == 0: - yield () - return - for first in range(remaining_degree + 1): - for rest in helper(remaining_vars - 1, remaining_degree - first): - yield (first,) + rest - - return list(helper(self.no_variables, total_degree)) - def __init__(self, no_variables: int, z_degree: int, log_degree: int): self.no_variables = no_variables self.z_degree = z_degree self.log_degree = log_degree - log_indices = self._multi_indices(log_degree) - z_indices = self._multi_indices(z_degree) + log_indices = _multi_indices(self.no_variables, log_degree) + z_indices = _multi_indices(self.no_variables, z_degree) # Every (log_index, z_index) pair allowed by the degree bounds gets its own # fresh unknown, to be solved for once a PFOperator is applied to the Ansatz. @@ -246,4 +277,11 @@ class PeriodAnsatz(Period): logger.info( "Initialised PeriodAnsatz with %d unknown coefficient(s).", sum(len(z_dict) for z_dict in self.expansion_coefficients.values()), - ) \ No newline at end of file + ) + + +""" +To do: + - add method to Period that finds operators that annihilate it (using operator ansatz class) + - add class PFideal containing a list of PFOperators +""" \ No newline at end of file diff --git a/sage/util.py b/sage/util.py new file mode 100644 index 0000000..5e1aa0f --- /dev/null +++ b/sage/util.py @@ -0,0 +1,16 @@ +""" +Utility functions for the Calabi-Yau period geometry project. +""" + +def _multi_indices(no_variables, total_degree: int) -> list: + # All no_variables-tuples of non-negative integers whose entries sum to at most total_degree. + def helper(remaining_vars, remaining_degree): + if remaining_vars == 0: + yield () + return + for first in range(remaining_degree + 1): + for rest in helper(remaining_vars - 1, remaining_degree - first): + yield (first,) + rest + + return list(helper(no_variables, total_degree)) +