From 660037968160f21207512b75931807981c55240a Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 27 Oct 2026 14:43:37 +0100 Subject: [PATCH] Adding holo from l-vectors (#20) --------- Co-authored-by: Julian Piribauer Reviewed-on: https://gitea.piribauer.ch/julian/Calabi-Yau-Period-Geometry/pulls/20 --- README.md | 7 +- ...period_computation.sage => operators.sage} | 262 +------------ sage/period.sage | 345 ++++++++++++++++++ tests/test_period_computation.py | 2 +- 4 files changed, 351 insertions(+), 265 deletions(-) rename sage/{period_computation.sage => operators.sage} (62%) create mode 100644 sage/period.sage diff --git a/README.md b/README.md index 1b5b95a..d1330b8 100644 --- a/README.md +++ b/README.md @@ -82,12 +82,13 @@ The result is saved in the folder `data/topdata` as a JSON file — giving a track of these outputs. Note that for Calabi–Yau dimensions larger than four, the additional -## period_computation +## Operators and periods -`period_computation.sage` provides classes for working with Picard–Fuchs operators and their +`operators.sage` and `period.sage` provide classes for working with Picard–Fuchs operators and their period solutions: `PFOperator`, `PFIdeal` and `Period`, together with Ansatz variants of the first and last (`PFOperatorAnsatz`, `PeriodAnsatz`) used to search for unknown operators or periods of a given -z- and theta-degree. +z- and theta-degree. The operator classes live in `operators.sage`, the period classes in `period.sage`; +since the two depend on each other, load `period.sage`, which also loads `operators.sage`. A `PFOperator` is parsed from a string in the variables `z0, ..., z` and `theta0, ..., theta`, the logarithmic derivatives theta_i = z_i d/dz_i. For example, the quintic's Picard–Fuchs operator: diff --git a/sage/period_computation.sage b/sage/operators.sage similarity index 62% rename from sage/period_computation.sage rename to sage/operators.sage index 44b8530..c72c392 100644 --- a/sage/period_computation.sage +++ b/sage/operators.sage @@ -1,6 +1,6 @@ import copy import logging -from sage.all import sage_eval, PolynomialRing, QQ, SR, function, log, matrix, prod, solve, var +from sage.all import sage_eval, PolynomialRing, QQ, SR, function, prod, solve, var load("sage/util.py") @@ -384,263 +384,3 @@ class PFIdeal: "Found %d power series solution(s) for indicials %s at order %d.", len(solutions), indicials, order ) return solutions - - -class Period: - """ - A class representing a period as a formal power series in z-variables and their logs. - - The coefficients are stored in a dictionary of dictionaries, where the first key is the multi-index - of the logarithmic part and the second key is the multi-index of the z-variables. - So, for example, the coefficient of - - log(z0)^2 * log(z1) * z0^3 * z1^2 would be stored as coefficients[(2, 1)][(3, 2)] - - """ - - def _initialise_ring(self): - z_names = ['z%d' % i for i in range(self.no_variables)] - log_names = ['L%d' % i for i in range(self.no_variables)] - ring = PolynomialRing(QQ, z_names + log_names) - z_gens = ring.gens()[:self.no_variables] - log_gens = ring.gens()[self.no_variables:] - - return ring, z_gens, log_gens # type: (PolynomialRing, list, list) - - def _period_from_string(self, period_string: str) -> dict: - ring, z_gens, log_gens = self._initialise_ring() - log_of_z = dict(zip(z_gens, log_gens)) - - def log(zi): - try: - return log_of_z[zi] - except (KeyError, TypeError): - raise ValueError( - "log(...) may only be applied to one of the z-variables z0, ..., z%d" - % (self.no_variables - 1) - ) - - parse_locals = dict(ring.gens_dict()) - parse_locals['log'] = log - - try: - expression = ring(sage_eval(period_string, locals=parse_locals)) - except Exception as e: - raise ValueError("Invalid period string: %s" % e) - - coefficients = {} - for coeff, monomial in expression: - exponents = monomial.exponents()[0] - z_index = tuple(exponents[:self.no_variables]) - log_index = tuple(exponents[self.no_variables:]) - coefficients.setdefault(log_index, {})[z_index] = coeff - - return coefficients - - def _period_to_string(self) -> str: - ring, z_gens, log_gens = self._initialise_ring() - expression = ring(0) - for log_index, z_dict in self.coefficients.items(): - for z_index, coeff in z_dict.items(): - monomial = coeff - for i in range(self.no_variables): - monomial *= (log_gens[i] ** log_index[i]) * (z_gens[i] ** z_index[i]) - expression += monomial - - log_substitutions = {log_gens[i]: log(SR(z_gens[i])) for i in range(self.no_variables)} - result = SR(expression).subs(log_substitutions) - - # A nonzero indicial ρ_i means the coefficients above are for z_i^k, but the - # actual solution is z_i^(ρ_i + k); make that explicit in the printed form. - indicial_prefactor = prod( - (SR(z_gens[i]) ** self.indicials[i] for i in range(self.no_variables) if self.indicials[i] != 0), - SR(1), - ) - if indicial_prefactor != 1: - result *= indicial_prefactor - - return str(result) - - def _max_z_degree(self) -> int: - # Highest total z-degree (sum of the z-multi-index) among all coefficients. - if not self.coefficients: - return 0 - return max( - sum(z_index) - for z_dict in self.coefficients.values() - for z_index in z_dict - ) - - def _truncate_coefficients(self, order: int) -> dict: - # Drop every (log_index, z_index) entry whose total z-degree exceeds order, - # removing it from the dictionary rather than merely zeroing it out. - truncated = {} - for log_index, z_dict in self.coefficients.items(): - kept = {z_index: coeff for z_index, coeff in z_dict.items() if sum(z_index) <= order} - if kept: - truncated[log_index] = kept - return truncated - - def _apply_theta(self, coefficients: dict, index: int) -> dict: - """ - Applies the logarithmic derivative theta_i = z_i * d/dz_i once to a coefficients dict of the same - shape as self.coefficients. It uses the product rule - - theta_i(z^a log(z)^k) = a_i * z^a log(z)^k + k_i * z^a log(z)^(k - e_i). - - z_index entries are offsets from self.indicials: a stored z_index of a really means - z^(a + self.indicials[index]), so theta_i's eigenvalue is a_i + self.indicials[index] - rather than the bare a_i (self.indicials is all-zero unless explicitly given, in which - case this reduces to the ordinary power-series rule). - """ - result = {} - for log_index, z_dict in coefficients.items(): - for z_index, coeff in z_dict.items(): - a_i = z_index[index] + self.indicials[index] - if a_i != 0: - inner = result.setdefault(log_index, {}) - inner[z_index] = inner.get(z_index, 0) + a_i * coeff - - k_i = log_index[index] - if k_i != 0: - lowered_log_index = log_index[:index] + (k_i - 1,) + log_index[index + 1:] - inner = result.setdefault(lowered_log_index, {}) - inner[z_index] = inner.get(z_index, 0) + k_i * coeff - return result - - def apply_operator(self, pf_operator: PFOperator) -> "Period": - """ - Applies a PFOperator to this period and returns the result as a new Period, - truncated to self.order. Each monomial of the operator is normalised as - coeff * z^p * theta^q (see PFOperator's docstring), so theta^q is applied - to the period first and the result is then multiplied by coeff * z^p. - """ - if pf_operator.no_variables != self.no_variables: - raise ValueError( - "Variable count mismatch: period has %d variable(s), operator has %d." - % (self.no_variables, pf_operator.no_variables) - ) - - result_coefficients = {} - for monomial_coeff, monomial in pf_operator.operator: - exponents = monomial.exponents()[0] - z_exponents = exponents[:self.no_variables] - theta_exponents = exponents[self.no_variables:] - - term = self.coefficients - for i, power in enumerate(theta_exponents): - for _ in range(power): - term = self._apply_theta(term, i) - - for log_index, z_dict in term.items(): - inner = result_coefficients.setdefault(log_index, {}) - for z_index, coeff in z_dict.items(): - shifted_z_index = tuple(z_index[i] + z_exponents[i] for i in range(self.no_variables)) - inner[shifted_z_index] = inner.get(shifted_z_index, 0) + monomial_coeff * coeff - - result_coefficients = { - log_index: {z_index: c for z_index, c in z_dict.items() if c != 0} - for log_index, z_dict in result_coefficients.items() - } - result_coefficients = {log_index: z_dict for log_index, z_dict in result_coefficients.items() if z_dict} - - return Period( - no_variables=self.no_variables, - coefficients=result_coefficients, - order=self.order, - indicials=self.indicials, - ) - - def find_annihilating_operators(self, z_degree: int, theta_degree: int) -> list: - """ - Finds operators annihilating this period among PFOperator Ansätze of the given - z- and theta-degree. - """ - ansatz = PFOperatorAnsatz(self.no_variables, theta_degree, z_degree) - unknowns = ansatz.unknowns - - equations = [ - coeff - for z_dict in self.apply_operator(ansatz).coefficients.values() - for coeff in z_dict.values() - ] - - coefficient_rows = [[SR(equation).coefficient(b) for b in unknowns] for equation in equations] - kernel_basis = matrix(QQ, coefficient_rows, ncols=len(unknowns)).right_kernel().basis() - - operators = [] - for basis_vector in kernel_basis: - solution = {unknowns[j]: basis_vector[j] for j in range(len(unknowns))} - solved_operator = ansatz.operator.map_coefficients(lambda c: SR(c).subs(solution)) - operators.append(PFOperator(str(solved_operator), no_variables=self.no_variables)) - - return operators - - def __init__( - self, - no_variables: int = 1, - coefficients: dict = None, - period_string: str = None, - order: int = None, - indicials: list = None, - ): - self.no_variables = no_variables - # indicials[i] is the (rational) Frobenius exponent ρ_i of z_i: a stored - # z-index of a really represents z^(a + indicials[i]). - self.indicials = list(indicials) if indicials is not None else [0] * no_variables - - # Use coefficients or period_string to initialize the period - if period_string is not None: - if coefficients is not None: - raise ValueError("Provide either coefficients or period_string, not both.") - self.coefficients = self._period_from_string(period_string) - self.period_string = period_string - logger.debug("Using string for initialisation.") - elif coefficients is None: - self.coefficients = {} - else: - self.coefficients = coefficients - self.period_string = self._period_to_string() - logger.debug("Using coefficients for initialisation.") - - if order is None: - self.order = self._max_z_degree() - logger.debug("Order not provided, using maximum z-degree: %d", self.order) - else: - self.coefficients = self._truncate_coefficients(order) - self.order = order - self.period_string = self._period_to_string() - logger.debug("Truncated coefficients to order %d.", self.order) - - logger.info("Initialised Period in %d variables at order %d.", self.no_variables, self.order) - - -class PeriodAnsatz(Period): - """ - A class for period Ansätze, characterised by number of variables and their z- and log-multi-degrees. - """ - - def __init__(self, no_variables: int, z_degree: int, log_degree: int, indicials: list = None): - self.no_variables = no_variables - self.z_degree = z_degree - self.log_degree = log_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. - coefficients = { - log_index: { - z_index: var("a_" + "_".join(str(x) for x in log_index + z_index)) - for z_index in z_indices - } - for log_index in log_indices - } - - super().__init__(no_variables=no_variables, coefficients=coefficients, order=z_degree, indicials=indicials) - self.expansion_coefficients = self.coefficients - 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 diff --git a/sage/period.sage b/sage/period.sage new file mode 100644 index 0000000..b407bbe --- /dev/null +++ b/sage/period.sage @@ -0,0 +1,345 @@ +import logging +from sage.all import sage_eval, PolynomialRing, QQ, SR, ceil, factorial, floor, log, matrix, prod, var + +load("sage/operators.sage") + +# Logger +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.DEBUG) + + +class Period: + """ + A class representing a period as a formal power series in z-variables and their logs. + + The coefficients are stored in a dictionary of dictionaries, where the first key is the multi-index + of the logarithmic part and the second key is the multi-index of the z-variables. + So, for example, the coefficient of + + log(z0)^2 * log(z1) * z0^3 * z1^2 would be stored as coefficients[(2, 1)][(3, 2)] + + """ + + def _initialise_ring(self): + z_names = ['z%d' % i for i in range(self.no_variables)] + log_names = ['L%d' % i for i in range(self.no_variables)] + ring = PolynomialRing(QQ, z_names + log_names) + z_gens = ring.gens()[:self.no_variables] + log_gens = ring.gens()[self.no_variables:] + + return ring, z_gens, log_gens # type: (PolynomialRing, list, list) + + def _period_from_string(self, period_string: str) -> dict: + ring, z_gens, log_gens = self._initialise_ring() + log_of_z = dict(zip(z_gens, log_gens)) + + def log(zi): + try: + return log_of_z[zi] + except (KeyError, TypeError): + raise ValueError( + "log(...) may only be applied to one of the z-variables z0, ..., z%d" + % (self.no_variables - 1) + ) + + parse_locals = dict(ring.gens_dict()) + parse_locals['log'] = log + + try: + expression = ring(sage_eval(period_string, locals=parse_locals)) + except Exception as e: + raise ValueError("Invalid period string: %s" % e) + + coefficients = {} + for coeff, monomial in expression: + exponents = monomial.exponents()[0] + z_index = tuple(exponents[:self.no_variables]) + log_index = tuple(exponents[self.no_variables:]) + coefficients.setdefault(log_index, {})[z_index] = coeff + + return coefficients + + def _period_to_string(self) -> str: + ring, z_gens, log_gens = self._initialise_ring() + expression = ring(0) + for log_index, z_dict in self.coefficients.items(): + for z_index, coeff in z_dict.items(): + monomial = coeff + for i in range(self.no_variables): + monomial *= (log_gens[i] ** log_index[i]) * (z_gens[i] ** z_index[i]) + expression += monomial + + log_substitutions = {log_gens[i]: log(SR(z_gens[i])) for i in range(self.no_variables)} + result = SR(expression).subs(log_substitutions) + + # A nonzero indicial ρ_i means the coefficients above are for z_i^k, but the + # actual solution is z_i^(ρ_i + k); make that explicit in the printed form. + indicial_prefactor = prod( + (SR(z_gens[i]) ** self.indicials[i] for i in range(self.no_variables) if self.indicials[i] != 0), + SR(1), + ) + if indicial_prefactor != 1: + result *= indicial_prefactor + + return str(result) + + def _max_z_degree(self) -> int: + # Highest total z-degree (sum of the z-multi-index) among all coefficients. + if not self.coefficients: + return 0 + return max( + sum(z_index) + for z_dict in self.coefficients.values() + for z_index in z_dict + ) + + def _truncate_coefficients(self, order: int) -> dict: + # Drop every (log_index, z_index) entry whose total z-degree exceeds order, + # removing it from the dictionary rather than merely zeroing it out. + truncated = {} + for log_index, z_dict in self.coefficients.items(): + kept = {z_index: coeff for z_index, coeff in z_dict.items() if sum(z_index) <= order} + if kept: + truncated[log_index] = kept + return truncated + + def _apply_theta(self, coefficients: dict, index: int) -> dict: + """ + Applies the logarithmic derivative theta_i = z_i * d/dz_i once to a coefficients dict of the same + shape as self.coefficients. It uses the product rule + + theta_i(z^a log(z)^k) = a_i * z^a log(z)^k + k_i * z^a log(z)^(k - e_i). + + z_index entries are offsets from self.indicials: a stored z_index of a really means + z^(a + self.indicials[index]), so theta_i's eigenvalue is a_i + self.indicials[index] + rather than the bare a_i (self.indicials is all-zero unless explicitly given, in which + case this reduces to the ordinary power-series rule). + """ + result = {} + for log_index, z_dict in coefficients.items(): + for z_index, coeff in z_dict.items(): + a_i = z_index[index] + self.indicials[index] + if a_i != 0: + inner = result.setdefault(log_index, {}) + inner[z_index] = inner.get(z_index, 0) + a_i * coeff + + k_i = log_index[index] + if k_i != 0: + lowered_log_index = log_index[:index] + (k_i - 1,) + log_index[index + 1:] + inner = result.setdefault(lowered_log_index, {}) + inner[z_index] = inner.get(z_index, 0) + k_i * coeff + return result + + def apply_operator(self, pf_operator: PFOperator) -> "Period": + """ + Applies a PFOperator to this period and returns the result as a new Period, + truncated to self.order. Each monomial of the operator is normalised as + coeff * z^p * theta^q (see PFOperator's docstring), so theta^q is applied + to the period first and the result is then multiplied by coeff * z^p. + """ + if pf_operator.no_variables != self.no_variables: + raise ValueError( + "Variable count mismatch: period has %d variable(s), operator has %d." + % (self.no_variables, pf_operator.no_variables) + ) + + result_coefficients = {} + for monomial_coeff, monomial in pf_operator.operator: + exponents = monomial.exponents()[0] + z_exponents = exponents[:self.no_variables] + theta_exponents = exponents[self.no_variables:] + + term = self.coefficients + for i, power in enumerate(theta_exponents): + for _ in range(power): + term = self._apply_theta(term, i) + + for log_index, z_dict in term.items(): + inner = result_coefficients.setdefault(log_index, {}) + for z_index, coeff in z_dict.items(): + shifted_z_index = tuple(z_index[i] + z_exponents[i] for i in range(self.no_variables)) + inner[shifted_z_index] = inner.get(shifted_z_index, 0) + monomial_coeff * coeff + + result_coefficients = { + log_index: {z_index: c for z_index, c in z_dict.items() if c != 0} + for log_index, z_dict in result_coefficients.items() + } + result_coefficients = {log_index: z_dict for log_index, z_dict in result_coefficients.items() if z_dict} + + return Period( + no_variables=self.no_variables, + coefficients=result_coefficients, + order=self.order, + indicials=self.indicials, + ) + + def find_annihilating_operators(self, z_degree: int, theta_degree: int) -> list: + """ + Finds operators annihilating this period among PFOperator Ansätze of the given + z- and theta-degree. + """ + ansatz = PFOperatorAnsatz(self.no_variables, theta_degree, z_degree) + unknowns = ansatz.unknowns + + equations = [ + coeff + for z_dict in self.apply_operator(ansatz).coefficients.values() + for coeff in z_dict.values() + ] + + coefficient_rows = [[SR(equation).coefficient(b) for b in unknowns] for equation in equations] + kernel_basis = matrix(QQ, coefficient_rows, ncols=len(unknowns)).right_kernel().basis() + + operators = [] + for basis_vector in kernel_basis: + solution = {unknowns[j]: basis_vector[j] for j in range(len(unknowns))} + solved_operator = ansatz.operator.map_coefficients(lambda c: SR(c).subs(solution)) + operators.append(PFOperator(str(solved_operator), no_variables=self.no_variables)) + + return operators + + def __init__( + self, + no_variables: int = 1, + coefficients: dict = None, + period_string: str = None, + order: int = None, + indicials: list = None, + ): + self.no_variables = no_variables + # indicials[i] is the (rational) Frobenius exponent ρ_i of z_i: a stored + # z-index of a really represents z^(a + indicials[i]). + self.indicials = list(indicials) if indicials is not None else [0] * no_variables + + # Use coefficients or period_string to initialize the period + if period_string is not None: + if coefficients is not None: + raise ValueError("Provide either coefficients or period_string, not both.") + self.coefficients = self._period_from_string(period_string) + self.period_string = period_string + logger.debug("Using string for initialisation.") + elif coefficients is None: + self.coefficients = {} + else: + self.coefficients = coefficients + self.period_string = self._period_to_string() + logger.debug("Using coefficients for initialisation.") + + if order is None: + self.order = self._max_z_degree() + logger.debug("Order not provided, using maximum z-degree: %d", self.order) + else: + self.coefficients = self._truncate_coefficients(order) + self.order = order + self.period_string = self._period_to_string() + logger.debug("Truncated coefficients to order %d.", self.order) + + logger.info("Initialised Period in %d variables at order %d.", self.no_variables, self.order) + + @classmethod + def from_l_vectors(cls, l_vectors: list, order: int) -> "Period": + """ + Computes the holomorphic (fundamental) period directly from the GLSM/Mori charge + vectors l^(1), ..., l^(m) of a toric Calabi-Yau (m = number of moduli), via the + hypergeometric-type sum + + varpi_0(z) = sum_{a in Z_{>=0}^m} + (prod_j Gamma(1 - l_num^(j).a)) / (prod_i Gamma(1 + l_den^(i).a)) * z^a, + + see e.g. Hosono-Klemm-Theisen-Yau. l_vectors is a list of the m charge vectors, each + given as [numerator part, denominator part]: the components landing in the numerator + resp. denominator Gamma-functions (for a Calabi-Yau hypersurface/CICY, these are the + nef-partition weight(s) resp. the toric divisor charges). For m = 1 the outer list may + be dropped, e.g. the quintic is given by [[-5], [1, 1, 1, 1, 1]], while P^4_{1,1,2,2,6}[12] + is given by [[[-6], [3, 2, 1, 0, 0, 0]], [[0], [0, 0, -3, 1, 1, 1]]]. + + For a Calabi-Yau charge vector the numerator Gamma-functions never hit a pole, since + their argument only grows as a grows; only the denominator factors can vanish - at a + non-positive-integer argument - which is exactly what truncates each a_k to a finite + range once the others are fixed. This makes summing directly over that range far + cheaper than solving the full Picard-Fuchs system term by term. + """ + # A single charge vector [numerator part, denominator part] has integer entries in its parts. + if l_vectors and l_vectors[0] and not isinstance(l_vectors[0][0], (list, tuple)): + l_vectors = [l_vectors] + if any(len(l_vector) != 2 for l_vector in l_vectors): + raise ValueError("Each charge vector must be given as [numerator part, denominator part].") + m = len(l_vectors) + numerator_charges = [l_vector[0] for l_vector in l_vectors] + denominator_charges = [l_vector[1] for l_vector in l_vectors] + len0 = len(numerator_charges[0]) if m else 0 + len1 = len(denominator_charges[0]) if m else 0 + + # Each denominator Gamma-factor is linear in a; pivot it on the smallest index j with + # a nonzero coefficient there. Since a is built up from a_{m-1} down to a_0, every + # other index still appearing in that linear form is then already fixed, turning the + # factor's pole at non-positive integers into a concrete bound on a_j. + pivots = [[] for _ in range(m)] + for i in range(len1): + charges = [denominator_charges[k][i] for k in range(m)] + j = next(k for k in range(m) if charges[k] != 0) + pivots[j].append(charges) + + coefficients = {} + a = [0] * m + + def recurse(j, num_partial, den_partial, degree): + if j < 0: + numerator = prod(factorial(-x) for x in num_partial) + denominator = prod(factorial(x) for x in den_partial) + coefficients[tuple(a)] = QQ(numerator) / QQ(denominator) + return + + lower, upper = 0, order - degree + for charges in pivots[j]: + c = charges[j] + bound = -QQ(sum(charges[k] * a[k] for k in range(j + 1, m))) / QQ(c) + if c < 0: + upper = min(upper, floor(bound)) + else: + lower = max(lower, ceil(bound)) + + for a_j in range(lower, upper + 1): + a[j] = a_j + recurse( + j - 1, + [num_partial[jj] + numerator_charges[j][jj] * a_j for jj in range(len0)], + [den_partial[ii] + denominator_charges[j][ii] * a_j for ii in range(len1)], + degree + a_j, + ) + + recurse(m - 1, [0] * len0, [0] * len1, 0) + + logger.info("Computed holomorphic period from l-vectors for %d moduli at order %d.", m, order) + return cls(no_variables=m, coefficients={tuple([0] * m): coefficients}, order=order) + + +class PeriodAnsatz(Period): + """ + A class for period Ansätze, characterised by number of variables and their z- and log-multi-degrees. + """ + + def __init__(self, no_variables: int, z_degree: int, log_degree: int, indicials: list = None): + self.no_variables = no_variables + self.z_degree = z_degree + self.log_degree = log_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. + coefficients = { + log_index: { + z_index: var("a_" + "_".join(str(x) for x in log_index + z_index)) + for z_index in z_indices + } + for log_index in log_indices + } + + super().__init__(no_variables=no_variables, coefficients=coefficients, order=z_degree, indicials=indicials) + self.expansion_coefficients = self.coefficients + 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 diff --git a/tests/test_period_computation.py b/tests/test_period_computation.py index 277a701..253a5b4 100644 --- a/tests/test_period_computation.py +++ b/tests/test_period_computation.py @@ -2,7 +2,7 @@ import os from sage.all import * # noqa: F401 -load(os.path.join(os.path.dirname(__file__), "..", "sage", "period_computation.sage")) +load(os.path.join(os.path.dirname(__file__), "..", "sage", "period.sage")) def test_apply_operator_univariate():