import numpy as np import logging import os import json from datetime import datetime, timezone logger = logging.getLogger(__name__) logging.basicConfig(level=logging.DEBUG) # or logging.DEBUG to see both # For grep-commands import re find_zs = re.compile('z\d+') find_ls = re.compile('l\d+') find_index_z = re.compile('\d+') class Polytope: """ Class representing a lattice polytope. Objects contain a list of defining points and a list of points in the convex hull of the polytope. Points inside codimension are excluded from the convex hull and stored in a separate list. """ ############################# # Initialisation ############################# def _validate_points_and_get_dimension(self) -> int: if len(self.defining_points) < 1: raise ValueError("At least one point is required.") elif len(set([len(p) for p in self.defining_points])) != 1: raise ValueError("All points must have the same dimension.") elif len(self.defining_points) <= len(self.defining_points[0]): raise ValueError("The number of points must be greater than the dimension of the points.") return len(self.defining_points[0]) def _points_to_polytope(self) -> tuple: """ Takes the defining points of the polytope and returns the points inside its convex hull while removing and returning points inside faces of co-dimension one as a second return value. """ lattice_polytope = LatticePolytope(self.defining_points) points_in_convex_hull = [list(m) for m in lattice_polytope.points()] points_in_codim1_faces = [] # is populated below for facet in lattice_polytope.facets(): for i in facet.interior_point_indices(): points_inside_facet = [list(m) for m in facet.points(i)][0] if points_inside_facet in points_in_convex_hull: points_in_convex_hull.remove(points_inside_facet) # remove those inside codim1 faces points_in_codim1_faces.append(points_inside_facet) # save omitted points in pcodim1 # Moving zeros to the end points_in_convex_hull.remove(self.origin) points_in_convex_hull.append(self.origin) return lattice_polytope, points_in_convex_hull, points_in_codim1_faces def __init__(self, points): self.defining_points = points self.dimension = self._validate_points_and_get_dimension() self.origin = [0] * self.dimension self.lattice_polytope, self.relevant_lattice_points, self.points_in_codim1_faces = self._points_to_polytope() class ToricPolytope(Polytope): """ Used to represent the anti-canonical Weyl divisor -K of a mirror family. By definition, it is also given by the polar dual polytope to the toric polytope describing the original family's ambient space. Example: For the mirror quintic, the vertices are e_1, ..., e_4, -e_1-...-e_4. """ ############################## # Initialisation ############################## def _set_fine_star_triangulations(self): """ Returns a list of fine star triangulations of the polytope. """ self.point_configuration = PointConfiguration(self.relevant_lattice_points) star_triangulations = self.point_configuration.restrict_to_star_triangulations(self.origin) fine_star_triangulations = star_triangulations.restrict_to_fine_triangulations() if len(fine_star_triangulations.triangulations_list()) == 0: raise ValueError("No fine star triangulation found for the given polytope.") if len(fine_star_triangulations.triangulations_list()) > 1: logger.warning("More than one fine star triangulation! (%d)", len(fine_star_triangulations.triangulations_list())) self.triangulations = fine_star_triangulations.triangulations_list() def set_triangulation(self, no_triangulation): """ Sets the triangulation of the polytope based on the provided index, ensuring that the index is valid. """ if no_triangulation < 0 or no_triangulation >= len(self.triangulations): raise IndexError("Invalid triangulation index {} (available: 0..{}).".format(no_triangulation, len(self.triangulations) - 1)) logger.info("Using triangulation index %d of [0..%d].", no_triangulation, len(self.triangulations) - 1) self.triangulation = self.triangulations[no_triangulation] def set_nef_partition(self, nef_partition): """ Sets the nef partition for the polytope, ensuring that it is valid. """ if nef_partition is not None: if sorted(flatten(nef_partition)) != list(range(len(self.relevant_lattice_points) - 1)): raise ValueError("Invalid nef partition: {}".format(nef_partition)) self.nef_partition = nef_partition else: self.nef_partition = [range(len(self.relevant_lattice_points) - 1)] # Default to a trivial partition if none is provided def set_Mori_cone(self, lvec = None): """ Sets the Mori cone of the toric variety associated with the polytope. """ default_Mori_cone = self.ToricVariety.Mori_cone() if lvec is None: self.Mori_cone = default_Mori_cone else: if not isinstance(lvec, (list, tuple, Matrix)): raise TypeError("lvec must be a list, tuple, or Matrix.") elif len(set([len(row) for row in lvec])) != 1: raise ValueError("All rows in lvec must have the same length.") elif len(lvec[0]) != len(self.relevant_lattice_points): raise ValueError("The length of the l-vectors is invalid.") elif matrix(matrix(default_Mori_cone.rays()).stack(matrix(lvec))).rank() != matrix(default_Mori_cone.rays()).rank(): raise ValueError("The provided l-vectors are not in the linear span of the original Mori cone.") else: self.Mori_cone = Cone(lvec) if len(matrix(self.Mori_cone.rays()).kernel().gens()) > 0: logger.warning("Non-simplicial Mori-cone encountered.") logger.info("Using the first %d linearly independent vectors.", len(self.Mori_cone.rays())) self.Mori_cone = Cone( transpose( transpose(self.Mori_cone.rays()) * matrix( transpose(matrix(self.Mori_cone.rays()).kernel().gens()) ).kernel().gens() ).transpose() ) self.lvec = matrix(self.Mori_cone.rays()) def __init__( self, points, no_triangulation = 0, model_name = None, nef_partition = None, lvec = None, # Note that the inner point is the last entry ): super().__init__(points) self.model_name = model_name self._set_fine_star_triangulations() self.set_triangulation(no_triangulation) self.set_nef_partition(nef_partition) self.fan = self.triangulation.fan(self.origin) self.ToricVariety = ToricVariety(self.fan) self.set_Mori_cone(lvec = lvec) ############################### # Discriminant Computation ############################### def _collect_linear_dependencies(self, only_strong_coupling): # Compute linear relations in all faces and map them to global coordinates. all_linear_dependencies_among_points = [] polytope_faces = self.lattice_polytope.faces() relevant_faces = polytope_faces[:-1] if only_strong_coupling else polytope_faces for faces_of_given_dimension in relevant_faces: relations_at_given_dimension = [] for face in faces_of_given_dimension: points_in_face = [list(point) for point in matrix(face.points())] if self.origin in points_in_face: points_in_face.remove(self.origin) # Keep only points that survived codim-1 face filtering. points_in_face = [point for point in points_in_face if point in self.relevant_lattice_points] if len(points_in_face) == 0: continue # In principle, the relations are just the kernel of the matrix of points in the face, but # to express the discriminants in Batyrev coordinates, we need to map them to the global coordinates # of the polytope. point_index_dic = {str(point): self.relevant_lattice_points.index(point) for point in points_in_face} linear_dependencies_in_face = matrix(points_in_face).kernel().gens() for generator in linear_dependencies_in_face: # Exclude origin here; its weight is appended later as minus the sum. generator_as_global_relation = [0] * (len(self.relevant_lattice_points) - 1) for idx in range(len(generator)): generator_as_global_relation[point_index_dic[str(points_in_face[idx])]] = generator[idx] relations_at_given_dimension.append(generator_as_global_relation) if len(relations_at_given_dimension) == 0: unique_relations = [] else: unique_relations = np.unique(np.matrix(relations_at_given_dimension), axis=0).tolist() all_linear_dependencies_among_points.append(unique_relations) return all_linear_dependencies_among_points def _append_origin_weight(self, all_linear_dependencies_among_points): # Adding the origin weight to each relation. for relations_at_dim in all_linear_dependencies_among_points: for relation in relations_at_dim: if len(relation) > 0: relation.append(-sum(relation)) def _setup_discriminant_symbols(self): mori_rays = self.Mori_cone.rays() a_vars = [var("a_{}".format(u), latex_name="a_{{}}".format(u)) for u in (1..len(mori_rays))] z_vars = var('z', n=len(mori_rays)+1, latex_name='z') # z[0] is superfluous lambda_vars = var('l', n=len(mori_rays)+1, latex_name='l') # l[0] is superfluous a_row = matrix(a_vars) mori_matrix = matrix(mori_rays) return a_vars, z_vars, lambda_vars, a_row, mori_matrix def _build_equation_system(self, relations_at_given_dimension, a_vars, z_vars, lambda_vars, a_row, mori_matrix): equation_system = [] number_of_points = len(relations_at_given_dimension[0]) # Eq. (8)-style building blocks: linear forms in lambda_a for each point index i. linear_forms = [ sum([ relations_at_given_dimension[relation_id][point_id] * lambda_vars[relation_id] for relation_id in range(len(relations_at_given_dimension)) ]) for point_id in range(number_of_points) ] for relation in relations_at_given_dimension: # This determines the Batyrev coordinate the dependence represents: solution = solve((a_row * mori_matrix - matrix(relation)).list(), a_row.list()) z_index = [abs(ai.subs(solution)) for ai in a_vars].index(1) + 1 equation_system.append( z_vars[z_index] - prod([linear_forms[point_id] ** relation[point_id] for point_id in range(number_of_points)]) ) return equation_system def _eliminate_and_normalize(self, equation_system, lambda_vars): lambda_names = sorted(list(set(find_ls.findall(str(equation_system))))) lambda_symbols = [lambda_vars[int(name[1:])] for name in lambda_names] reverse_solve = False try: polynomials = maxima.eliminate(equation_system, lambda_symbols[1:]).sage() if polynomials[0] == 0: polynomials = maxima.eliminate(equation_system, lambda_symbols[:-1]).sage() reverse_solve = True except: # In one-parameter cases there may be nothing to eliminate. polynomials = equation_system try: if len(lambda_symbols) == 0: raise ValueError("No lambda variables found in elimination system.") if not reverse_solve: lambda0 = lambda_symbols[0] polynomials = [poly / lambda0 ** (poly.degree(lambda0)) for poly in polynomials] else: last_lambda = lambda_symbols[-1] polynomials = [poly / last_lambda ** (poly.degree(last_lambda)) for poly in polynomials] except: pass return polynomials def _append_unique_discriminants(self, discriminants, seen_discriminants, polynomials, index, total_relation_blocks, only_strong_coupling): for polynomial in polynomials: if polynomial == 0: continue polynomial_key = str(polynomial) if polynomial_key in seen_discriminants: continue seen_discriminants.add(polynomial_key) if only_strong_coupling: codim = total_relation_blocks - index else: codim = total_relation_blocks - 1 - index discriminants.append([polynomial, codim]) def disc(self, only_strong_coupling=False, no_triangulation=0): """ Computes the A-discriminant for the toric polytope (following Aspinwall, Plesser, Wang), which gives the singular loci of the moduli space in Batyrev coordinates. Parameters: - only_strong_coupling: If True, only gives loci arising from edges (one-dimensional faces). - no_triangulation: Index of the triangulation to use. Returns: A list of tuples [disc_i, codim_i] of discriminant factors disc_i coming from a relation inside a face of codimension codim_i. """ all_linear_dependencies_among_points = self._collect_linear_dependencies(only_strong_coupling) self._append_origin_weight(all_linear_dependencies_among_points) a_vars, z_vars, lambda_vars, a_row, mori_matrix = self._setup_discriminant_symbols() all_linear_dependencies_among_points = [relations for relations in all_linear_dependencies_among_points if relations] discriminants = [] seen_discriminants = set() total_relation_blocks = len(all_linear_dependencies_among_points) for index, relations_at_given_dimension in enumerate(all_linear_dependencies_among_points): if len(relations_at_given_dimension) == 0: continue equation_system = self._build_equation_system( relations_at_given_dimension, a_vars, z_vars, lambda_vars, a_row, mori_matrix, ) polynomials = self._eliminate_and_normalize(equation_system, lambda_vars) self._append_unique_discriminants( discriminants, seen_discriminants, polynomials, index, total_relation_blocks, only_strong_coupling, ) if only_strong_coupling: # Insert an empty codimension-0 entry for compatibility with prior behavior. discriminants = [[]] + discriminants return discriminants ############################## # Topological Data Computation ############################## def _compute_Kahler_generators(self): """ Computes the Kähler cone generators J_a dual to the Mori cone generators l^a. The toric divisors D_i satisfy ∑_i l^a_i D_i = 0 in cohomology, so only a subset of them are linearly independent. We identify the free generators via the z-variables appearing in the cohomology ring, extract the corresponding columns of the Mori matrix l, invert it, and express J = Bs * D_basis. """ z_names = list(set(find_zs.findall(str(self.D)))) z_indices = sorted([int(find_index_z.search(name).group()) for name in z_names]) Bsinv = self.lvec.matrix_from_columns(z_indices) if Bsinv.det() == 0: raise ValueError("l-vectors are not independent in the divisor basis. Supply them manually via lvec=...") Bs = transpose(Bsinv**(-1)) return Bs * vector([self.D[i] for i in z_indices]) def _compute_intersection_ring(self): """ Builds the intersection ring polynomial by adding up the intersection numbers in self.intersection_numbers_CY. """ # Full ring: ordered tuples, so t_i*t_j and t_j*t_i both contribute. intring = sum( product([self.t[i] for i in tl]) * self.intersection_numbers_CY[tuple(sorted(tl))] for tl in Tuples(range(self.no_divs), self.cy_dimension) ) # Ring without multiplicities: each distinct monomial once. intringnomults = sum( product([self.t[i] for i in tl]) * val for tl, val in self.intersection_numbers_CY.items() ) return intring, intringnomults def _intersection_substitution_rules(self): """ Builds monomial substitution rules from intersection numbers, e.g. t0^3 -> kappa_(0,0,0), t0*t1^2 -> kappa_(0,1,1). """ rules = {} for idx_tuple, value in self.intersection_numbers_CY.items(): monomial = product([self.t[i] for i in idx_tuple]).expand() rules[monomial] = value return rules def _replace_intersection_monomials(self, expression): """ Replaces top-degree Kähler monomials in an expression by CY intersection numbers. """ expanded_expression = expression.expand() replaced_expression = expanded_expression # Replace each degree-n monomial by its corresponding intersection number, # preserving the polynomial coefficient in front of that monomial. for idx_tuple, value in self.intersection_numbers_CY.items(): monomial = product([self.t[i] for i in idx_tuple]).expand() coeff = expanded_expression.coefficient(monomial) if coeff != 0: replaced_expression -= coeff * monomial replaced_expression += coeff * value return replaced_expression.expand() def _compute_Chern_character(self): """ Computes the total Chern class of the CY via the adjunction formula: c(Y) = c(TX) / c(N_{Y/X}) where c(TX) = ∏_i (1 + D_i) and c(N_{Y/X}) = ∏_k (1 + Y_k*) with Y_k* the nef partition divisor classes. Everything is expressed in the Kähler basis by solving lift(J_i) = t_i for the cohomology ring z-variables. """ z_names = find_zs.findall(str([self.J[i] for i in range(self.no_divs)])) var(z_names) zs = [eval(z) for z in z_names] tsubs = solve([lift(self.J[i]) == self.t[i] for i in range(self.no_divs)], zs) numerator = product([1 + eval(str(lift(d))).subs(tsubs[0]) for d in self.D]) denominator = product([ 1 + sum([eval(str(lift(self.HH(self.D[p])))) for p in part]) for part in self.nef_partition ]).subs(tsubs[0]) return numerator / denominator def _compute_Chern_polynomials(self, Chern_character): """ Computes the Chern polynomials of the CY by expanding the total Chern character and substituting intersection numbers. Returns both a Chern polynomial for each degree restricted to suitable intersections of Kähler divisors and their integrated versions. """ c = var('c') Chern_character_taylored = (Chern_character).subs({t: c * t for t in self.t}).taylor(c, 0, self.cy_dimension) Chern_polynomials = [ [ (prod(self.t[i] for i in tple) * Chern_character_taylored.coefficient(c, k)).expand() for tple in UnorderedTuples(range(self.no_divs), self.cy_dimension - k) ] for k in range(1, self.cy_dimension + 1) # for each degree ] integrated_Chern_classes = [ [ self._replace_intersection_monomials(poly) for poly in polys ] for polys in Chern_polynomials ] return Chern_polynomials, integrated_Chern_classes def _check_for_fibrations(self): """ Checks for possible elliptic fibrations via the condition elliptic in direction i <=> J_i^n == 0 but J_i^(n-1)*J_j != 0 for some j != i. """ messages = [] for i in range(self.no_divs): self_intersection = self.intersection_numbers_CY[tuple([i] * self.cy_dimension)] if self_intersection != 0: continue has_nonzero_neighbor = any( self.intersection_numbers_CY[tuple(sorted([i] * (self.cy_dimension - 1) + [j]))] != 0 for j in range(self.no_divs) if j != i ) if has_nonzero_neighbor: messages.append("Possible elliptic fibration in the divisor class dual to t{}.".format(i)) if messages: logger.info("\n\n--- Elliptic fibrations --------------------------\n" + "\n".join(messages)) return messages def _get_polytope_and_glsm_table(self): """ Builds a list of lines showing the polytope points as columns of a (dimension x n) matrix (one column per point), with the GLSM charge l-vectors listed directly underneath: one row per Mori cone generator, with each l-vector entry aligned below the point coordinate it belongs to. Returned as a list of lines (rather than a single newline-joined string) so that each line becomes its own JSON array entry and stays on its own line when written to file. To-do: add inner point and CICY functionality. """ points = list(self.relevant_lattice_points) # create a copy points.remove(self.origin) # Remove the origin point n_outer_points = len(points) nef_unit_vectors = [tuple([1 if i == j else 0 for j in range(len(self.nef_partition))]) for i in range(len(self.nef_partition))] for i, partition in enumerate(self.nef_partition): for p in partition: points[p] = tuple(list(nef_unit_vectors[i]) + list(points[p])) # Append nef partition index to each point points.append(tuple(list(nef_unit_vectors[i]) + [0] * self.dimension)) # Add a point for the nef partition itself point_rows = [[str(point[row]) for point in points] for row in range(self.dimension + len(self.nef_partition))] lvec_rows = self._get_lvec_rows() lvec_rows_string = [[str(entry) for entry in row] for row in lvec_rows] # Exclude the last entry of each l-vector (origin point) column_widths = [ max(len(row[col]) for row in point_rows + lvec_rows_string) for col in range(n_outer_points + len(self.nef_partition)) ] def format_row(row_entries, separator): # Double the separator right before the trailing nef-partition-marker columns. row_string = row_entries[0].rjust(column_widths[0]) for col in range(1, len(row_entries)): sep = separator * 2 if col == n_outer_points else separator row_string += sep + row_entries[col].rjust(column_widths[col]) return row_string point_lines = [format_row(row, "|") for row in point_rows] lvec_lines = [format_row(row, ",") for row in lvec_rows_string] lvec_lines = [line.replace(',,', '; ') for line in lvec_lines] # Blank line after the leading nef-partition-indicator rows. num_nef_rows = len(self.nef_partition) point_lines = point_lines[:num_nef_rows] + [""] + point_lines[num_nef_rows:] return point_lines + ["-" * len(point_lines[-1])] + lvec_lines def _get_lvec_rows(self): """ Returns the GLSM charge l-vectors (one row per Mori cone generator), with the nef-partition weight(s) appended, as a plain list of lists of ints - accessible as lvec[0], lvec[1], ... once loaded from the JSON output. """ lvec_rows = [list(row[:-1]) for row in self.lvec.rows()] # strip the total weight entry for row in lvec_rows: for partition in self.nef_partition: row.append(-sum([row[j] for j in partition])) # Append the weight of the NEF partition return lvec_rows def _get_output(self): """ Returns a dictionary containing all relevant topological data of the CY. """ output = { "polytope_and_glsm_table": self._get_polytope_and_glsm_table(), "lvec": self._get_lvec_rows(), "kahler_cone_generators": [str(j) for j in self.J], "intersection_numbers_CY": dict(self.intersection_numbers_CY), "intersection_numbers_ambient_space": dict(self.intersection_numbers_ambient_space), "intersection_ring": self.intersection_ring, "intersection_ring_no_multiplicities": self.intersection_ring_no_multiplicities, "chern_polynomials": self.Chern_polynomials, "integrated_Chern_classes": self.integrated_Chern_classes, } debug_string = "\n\n--- Kähler cone generators (ambient space) ---\n" + str(output["kahler_cone_generators"]) + \ "\n\n--- Intersection numbers ambient space -------\n" + str(output["intersection_numbers_ambient_space"]) info_string = "\n\n--- Polytope and GLSM table ------------------\n" + "\n".join(output["polytope_and_glsm_table"]) + \ "\n\n--- L-vectors (GLSM charges) -----------------\n" + str(output["lvec"]) + \ "\n\n--- Intersection numbers CY ------------------\n" + str(output["intersection_numbers_CY"]) + \ "\n\n--- Intersection ring ------------------------\n" + str(output["intersection_ring"]) + \ "\n\n--- Intersection ring no multiplicities ------\n" + str(output["intersection_ring_no_multiplicities"]) + \ "\n\n--- Chern polynomials ------------------------\n" + str(output["chern_polynomials"]) + \ "\n\n--- Integrated Chern classes -----------------\n" + str(output["integrated_Chern_classes"]) logger.info(info_string) logger.debug(debug_string) return output @staticmethod def _to_json_safe(value): """ Recursively converts a value into JSON-safe primitives: dicts/lists are recursed into (tuple dict keys become strings), numeric Sage objects (Integer, Rational, ...) become plain Python int/float, and anything else (e.g. symbolic expressions) is stringified. """ if isinstance(value, dict): return {str(k): ToricPolytope._to_json_safe(v) for k, v in value.items()} if isinstance(value, (list, tuple)): return [ToricPolytope._to_json_safe(v) for v in value] if value is None or isinstance(value, (bool, str, int, float)): return value try: as_int = int(value) if as_int == value: return as_int except (TypeError, ValueError): pass try: return float(value) except (TypeError, ValueError): pass return str(value) def _write_output_json(self, output): """ Writes the given output dictionary (from _get_output, i.e. exactly what is logged) to data/.json (relative to the repository root), if a model_name is given. Plain JSON, so it is directly loadable in Python (json.load), C++ (e.g. nlohmann::json), or any other language without any custom parsing code; lvec loads back as a plain list of lists, e.g. lvec[0]. """ if not self.model_name: logger.info("No model_name given, skipping writing topological data to JSON.") return None repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) out_dir = os.path.join(repo_root, "data/topdata") os.makedirs(out_dir, exist_ok=True) out_path = os.path.join(out_dir, "{}.json".format(self.model_name)) with open(out_path, "w") as f: json.dump(self._to_json_safe(output), f, indent=2) logger.info("Wrote topological data to %s", out_path) return out_path def topdata(self): """ Computes topological data for hypersurfaces and CICYs in toric ambient spaces. For hypersurfaces, nef_partition should remain untouched (=0). For CICYs with d polynomials, a nef_partition has to be supplied: its format should be a list of d lists as in the examples below which is a decomposition of the N points of $(polytope); the number should corresponds to an enumeration of the points of $(polytope) with the inner point omitted. Conditions for possible fibrations are: - elliptic: if t_i^n = 0 and t_i^{n-1} != 0 - K3 (only 3-folds): if c2.t_i = 24 Input: - (for CICYs:) nef-partition - (optional:) set of l-vectors to be used - (optional:) index of triangulation """ no_polys = len(self.nef_partition) self.cy_dimension = self.dimension - no_polys # Each point gives a toric divisor, each "column" gives one linear relation self.no_divs = len(self.relevant_lattice_points) - self.dimension - 1 # Cohomology ring and toric divisors D_0, ..., D_{N-1}. self.HH = self.ToricVariety.cohomology_ring() self.D = [self.HH(self.ToricVariety.divisor(i)) for i in range(len(self.relevant_lattice_points) - 1)] # Kähler cone generators J_a (dual to Mori cone generators l^a). self.J = self._compute_Kahler_generators() # Poincaré dual of the CY: product over nef partition parts. self.Ydualform = product([sum([self.D[p] for p in part]) for part in self.nef_partition]) # Kähler parameter symbols t_0, ..., t_{nodivs-1}. self.t = var('t', n=self.no_divs, latex_name='t') # Intersection numbers κ_{i_1...i_n} = ∫_Y J_{i_1}∧...∧J_{i_n}. self.intersection_numbers_CY = { tuple(tl): self.ToricVariety.integrate(self.Ydualform * product([self.J[tl[j]] for j in range(self.cy_dimension)])) for tl in UnorderedTuples(range(self.no_divs), self.cy_dimension) # Unordered means only increasing tuples } self.intersection_numbers_ambient_space = { tuple(tl): self.ToricVariety.integrate(product([self.J[tl[j]] for j in range(self.dimension)])) for tl in UnorderedTuples(range(self.no_divs), self.dimension) } # Intersection ring polynomial and version without multiplicities. self.intersection_ring, self.intersection_ring_no_multiplicities = self._compute_intersection_ring() Chern_character = self._compute_Chern_character() self.Chern_polynomials, self.integrated_Chern_classes = self._compute_Chern_polynomials(Chern_character) output = self._get_output() self._write_output_json(output) self._check_for_fibrations() class ToricPolytopeCICY(ToricPolytope): """ Computes topological data for CICYs in toric ambient spaces. """ def _CICY_to_points(self, CICY): """ Converts a CICY configuration matrix to a list of points in the ambient space. Each row of the CICY corresponds to a projective space, and each column corresponds to a polynomial. The entries give the weights of the polynomials in the respective projective spaces. """ ambients=(np.array(CICY).transpose()).sum(axis=0)-1 if ambients in ZZ: ambients=[ambients] dimp=sum(ambients) polytope=identity_matrix(int(dimp)) zeros=[0]*dimp polytope=polytope.insert_row(0,zeros) offset=0 for i in range(len(ambients)): newrow=[0]*dimp entry=[] for j in range(ambients[i]): newrow[offset]=-1 offset+=1 entry.append(offset) polytope=polytope.insert_row(sum([ambients[n]+1 for n in [0..i]]),newrow) partition=[] eqs=matrix(CICY).transpose() counters=[0]*len(ambients) for eq in eqs: part=[] for j in range(len(eq)): part.append([sum([ambients[m]+1 for m in [0..(j-1)]])+counters[j]+a for a in [0..(eq[j]-1)]]) counters[j]+=len(part[j]) partition.append(flatten(part)) return list(polytope), partition def __init__(self, CICY, no_triangulation=0, model_name=None, lvec=None): points, nef_partition = self._CICY_to_points(CICY) print(points, nef_partition) super().__init__( points, no_triangulation=no_triangulation, model_name=model_name, nef_partition=nef_partition, lvec=lvec )