From 0ccacceabe4b559e607c5ffd059138854ba41a87 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Fri, 8 Aug 2025 16:12:08 -0700 Subject: [PATCH 01/64] add pplite bsa file --- .../spam/basic_semialgebraic_pplite.py | 365 ++++++++++++++++++ requirements.txt | 1 + 2 files changed, 366 insertions(+) create mode 100644 cutgeneratingfunctionology/spam/basic_semialgebraic_pplite.py diff --git a/cutgeneratingfunctionology/spam/basic_semialgebraic_pplite.py b/cutgeneratingfunctionology/spam/basic_semialgebraic_pplite.py new file mode 100644 index 000000000..36e910e5e --- /dev/null +++ b/cutgeneratingfunctionology/spam/basic_semialgebraic_pplite.py @@ -0,0 +1,365 @@ +from cutgeneratingfunctionology.spam.basic_semialgebraic import BasicSemialgebraicSet_polyhedral + +from pplite import Variable as pplite_Var, Constraint as pplite_Con, Linear_Expression as pplite_Lin_Expr, Affine_Expression as pplite_Aff_expr, NNC_Polyhedron as pplite_NNC_Polyhedron, PPliteGenerator, Polyhedron_Constraint_Rel, Polyhedron_Generator_Rel + +poly_is_included_pplite = Polyhedron_Constraint_Rel.is_included() +point_is_included_pplite = Polyhedron_Generator_Rel.subsumes() + +class BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(BasicSemialgebraicSet_polyhedral): + + r""" + A (possibly half-open) polyhedral basic semialgebraic set, + represented by a PPLite ``NNC_Polyhedron`` + + """ + + def __init__(self, ambient_dim=None, polyhedron=None, base_ring=None, poly_ring=None, **options): + r""" + Initialize a basic semialgebraic set as the universe in + ``ambient_dim``, or, if ``polyhedron`` (an ``NNC_Polyhedron``, + which after that belongs to this object) is provided, as + that. + + TEST:: + + sage: from cutgeneratingfunctionology.spam.basic_semialgebraic import * + sage: P = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(2) + sage: P.add_linear_constraint([0,1],0,operator.ge) + sage: P.add_linear_constraint([1,0],0,operator.ge) + sage: P.add_linear_constraint([2,3],-6,operator.lt) + sage: P + BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron([x1>=0, x0>=0, -2*x0-3*x1+6>0], names=[x0, x1]) + sage: sorted(P.eq_poly()) + [] + sage: sorted(P.lt_poly()) + [2*x0 + 3*x1 - 6] + sage: sorted(P.le_poly()) + [-x1, -x0] + """ + if ambient_dim is None and polyhedron is not None: + ambient_dim = polyhedron.space_dimension() + if base_ring is None and poly_ring is None: + base_ring = QQ + poly_ring, base_ring, ambient_dim, names = self._poly_ring_from_options( + ambient_dim=ambient_dim, base_ring=base_ring, poly_ring=poly_ring, **options) + if base_ring is not QQ: + raise ValueError("only base_ring=QQ is supported") + super(BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron, self).__init__(poly_ring=poly_ring) + if polyhedron is None: + self._polyhedron = pplite_NNC_Polyhedron(dim_type=int(ambient_dim), spec_elem='universe', topology="nnc") # To work with pplite, ambient_dim is required to be type int + else: + self._polyhedron = polyhedron + + @staticmethod + def _pplite_constraint(lhs, cst, op): + r""" + Make a PPL ``Constraint`` ``lhs`` * x + cst ``op`` 0, + where ``lhs`` is be a vector of length ambient_dim. + """ + lcd = lcm(lcm(x.denominator() for x in lhs), cst.denominator()) + lin_expr = sum([int((lcd*lhs[i]))*pplite_Var(i) for i in range(len(lhs))]) + aff_expr = pplite_Aff_expr(lin_expr, int(lcd*cst)) + #linexpr = pplite_Lin_Expr(lhs * lcd, cst * lcd) + if op == operator.lt: + return (aff_expr < 0) + elif op == operator.gt: + return (aff_expr > 0) + elif op == operator.eq: + return (aff_expr == 0) + elif op == operator.le: + return (aff_expr <= 0) + elif op == operator.ge: + return (aff_expr >= 0) + else: + raise ValueError("{} is not a supported operator".format(op)) + + def __copy__(self): + r""" + Make a copy of ``self``. + + TESTS: + + Test that it is actually making a copy of the (mutable!) NNC_Polyhedron:: + + sage: from cutgeneratingfunctionology.spam.basic_semialgebraic import * + sage: P = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(2) + sage: P._polyhedron is copy(P)._polyhedron + False + """ + return self.__class__(polyhedron=pplite_NNC_Polyhedron(nnc_poly=self._polyhedron), poly_ring=self.poly_ring()) + + def _repr_(self): + constraints = self._polyhedron.constraints() + names = list(self.poly_ring().gens()) + return 'BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron({}, names={})'.format( + constraints, names) + + def closure(self, bsa_class='formal_closure'): + r""" + Return the basic semialgebraic set that is the topological closure + of ``self``. + """ + # Because our description consists of minimized constraints, the closure is + # just the formal closure. + return self.formal_closure(bsa_class=bsa_class) + + def relint(self, bsa_class='formal_relint'): + r""" + Return the basic semialgebraic set that is the topological relative interior + of ``self``. + """ + # Because our description consists of minimized constraints, the relint is + # just the formal relint. + return self.formal_relint(bsa_class=bsa_class) + + def eq_poly(self): + r""" + Return a list of the polynomials `f` in equations `f(x) = 0` + in the description of ``self``. + + Together, ``eq_poly``, ``lt_poly``, and ``le_poly`` describe ``self``. + """ + # add tests + for c in self._polyhedron.constraints(): + if c.is_equality(): + coeff = [c.coefficient(pplite_Var(i)) for i in range(c.space_dimension())] + # observe: coeffients in a constraint of NNC_Polyhedron could have gcd != 1. + gcd_c = gcd(gcd(coeff), c.inhomogeneous_term()) + t = sum(QQ(x)/gcd_c*y for x, y in zip(coeff, self.poly_ring().gens())) + QQ(c.inhomogeneous_term())/gcd_c # not type stable, make it type stable + yield self.poly_ring()(t) + + def lt_poly(self): + r""" + Return a list of the polynomials `f` in strict inequalities `f(x) < 0` + in the description of ``self``. + + Together, ``eq_poly``, ``lt_poly``, and ``le_poly`` describe ``self``. + """ + for c in self._polyhedron.constraints(): + if c.is_strict_inequality(): + coeff = [c.coefficient(pplite_Var(i)) for i in range(c.space_dimension())] + gcd_c = gcd(gcd(coeff), c.inhomogeneous_term()) + # constraint is written with '>', while lt_poly records '<' relation + t = sum(-QQ(x)/gcd_c*y for x, y in zip(coeff, self.poly_ring().gens())) - QQ(c.inhomogeneous_term())/gcd_c + yield self.poly_ring()(t) + + def le_poly(self): + r""" + Return a list of the polynomials `f` in inequalities `f(x) \leq 0` + in the description of ``self``. + + Together, ``eq_poly``, ``lt_poly``, and ``le_poly`` describe ``self``. + """ + for c in self._polyhedron.constraints(): + if c.is_nonstrict_inequality(): + coeff = [c.coefficient(pplite_Var(i)) for i in range(c.space_dimension())] + gcd_c = gcd(gcd(coeff), c.inhomogeneous_term()) + # constraint is written with '>=', while lt_poly records '<=' relation + t = sum(-QQ(x)/gcd_c*y for x, y in zip(coeff, self.poly_ring().gens())) - QQ(c.inhomogeneous_term())/gcd_c + yield self.poly_ring()(t) + + # override the default implementation + def __contains__(self, point): + r""" + Whether the set contains the ``point`` (vector). + """ + rational_list = [ QQ(x) for x in point ] + num_list = [x.numerator() for x in rational_list] + den_list = [x.denominator() for x in rational_list] + common_den = lcm(den_list) + coef = [common_den // den_list[i] * num_list[i] for i in range(len(rational_list))] + pt = ppl_point(Linear_Expression(coef, 0), common_den) + return self._polyhedron.relation_with(pt).implies(point_is_included_pplite) + + # override the abstract methods + def find_point(self): + r""" + Find a point in ``self``. + + EXAMPLES:: + + sage: from cutgeneratingfunctionology.spam.basic_semialgebraic import * + sage: P = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(2) + sage: P.add_linear_constraint([0,1],0,operator.ge) + sage: P.add_linear_constraint([1,0],0,operator.ge) + sage: P.add_linear_constraint([2,3],-6,operator.lt) + sage: P.find_point() + (1, 2/3) + """ + # pplite has a different representation points, closure points, of NNC polys compared to ppl + # so the find_point method yields different results + + def to_point(g, ambient_dim): + den = g.divisor() + # g.set_space_dimension(ambient_dim) # PPlite generators have space dim of largest dimension of variables in point expression. + # To sum points as vectors in sagemath vectors need to have the same dimension. + # To fix, update the points space dim to be a defined ambient dimension. + # TODO: update after this gets fixed in pplite. + return vector(QQ, (QQ(x)/den for x in [g.coefficient(v) for v in range(ambient_dim)])) # based on email this should in theory works + + def to_vector(g, ambient_dim): + den = g.divisor() + # g.set_space_dimension(ambient_dim) + return vector(QQ, (QQ(x)/den for x in [g.coefficient(v) for v in range(ambient_dim)])) + points = [to_point(g, self._polyhedron.space_dimension()) for g in self._polyhedron.generators() + if g.is_point() or g.is_closure_point()] + rays = [to_vector(g, self._polyhedron.space_dimension()) for g in self._polyhedron.generators() + if g.is_ray()] + if points: + p = sum(points) / len(points) + if rays: + p += sum(rays) / len(rays) + return p + raise NotImplementedError("find_test_point implementation cannot handle this case") + + def add_space_dimensions_and_embed(self, space_dim_to_add): + r""" + Mutate ``self`` by injecting it into a higher dimensional space. + + EXAMPLES:: + + sage: from cutgeneratingfunctionology.spam.basic_semialgebraic import * + sage: P = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(2) + sage: P.add_linear_constraint([0,1],0,operator.ge) + sage: P.add_linear_constraint([1,0],0,operator.ge) + sage: P.add_linear_constraint([2,3],-6,operator.lt) + sage: P.add_space_dimensions_and_embed(2) + sage: P + BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron([x1>=0, x0>=0, -2*x0-3*x1+6>0], names=[x0, x1, x2, x3]) + sage: P.ambient_dim() + 4 + """ + super(BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron, self).add_space_dimensions_and_embed(space_dim_to_add) + self._polyhedron.add_space_dimensions(int(space_dim_to_add), False) + + @staticmethod + def _pplite_constraint(lhs, cst, op): + r""" + Make a PPLite ``Constraint`` ``lhs`` * x + cst ``op`` 0, + where ``lhs`` is be a vector of length ambient_dim. + + TESTS:: + + sage: from cutgeneratingfunctionology.spam.basic_semialgebraic import * + sage: from pplite import Constraint as pplite_Con + sage: test_constraint = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron._pplite_constraint([0,1], 0, operator.ge) + sage: test_constraint + x1>=0 + sage: isinstance(test_constraint, pplite_Con) + True + + """ + lcd = lcm(lcm(x.denominator() for x in lhs), cst.denominator()) + aff_expr = sum([int((lcd*lhs[i]))*pplite_Var(i) for i in range(len(lhs))]) + int(lcd*cst) + if op == operator.lt: + return (aff_expr < 0) + elif op == operator.gt: + return (aff_expr > 0) + elif op == operator.eq: + return (aff_expr == 0) + elif op == operator.le: + return (aff_expr <= 0) + elif op == operator.ge: + return (aff_expr >= 0) + else: + raise ValueError("{} is not a supported operator".format(op)) + + def linear_function_upper_bound(self, form): + r""" + Find an upper bound for ``form`` (a vector) on ``self``. + This upper bound is the supremum. + + If ``self`` is empty, it returns -oo + """ + + def to_point(g): + den = g.divisor() + return vector(QQ, (QQ(x)/den for x in [g.coefficient(v) for v in range(g.space_dimision())])) + + def to_vector(g): + return vector(QQ, (QQ(x) for x in [g.coefficient(v) for v in range(g.space_dimision())])) + if self._polyhedron.is_empty(): + return -Infinity + form = vector(form) + for g in self._polyhedron.generators(): + if g.is_line(): + if to_vector(g) * form != 0: + return +Infinity + if g.is_ray(): + if to_vector(g) * form > 0: + return +Infinity + points = [to_point(g) for g in self._polyhedron.generators() + if g.is_point() or g.is_closure_point()] + return max(p * form for p in points) + + def is_linear_constraint_valid(self, lhs, cst, op): + r""" + Whether the constraint ``lhs`` * x + cst ``op`` 0 + is satisfied for all points of ``self``, + where ``lhs`` is be a vector of length ambient_dim. + + EXAMPLES:: + + sage: from cutgeneratingfunctionology.spam.basic_semialgebraic import * + sage: P = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(2) + sage: P.add_linear_constraint([0,1],0,operator.ge) + sage: P.add_linear_constraint([1,0],0,operator.ge) + sage: P.add_linear_constraint([2,3],-6,operator.lt) + sage: P.is_linear_constraint_valid([1,1],-3,operator.lt) + True + sage: P.is_linear_constraint_valid([0,1],0,operator.gt) + False + """ + lhs = vector(lhs) + constraint = self._pplite_constraint(lhs, cst, op) + return self._polyhedron.relation_with(constraint).implies(poly_is_included_pplite) + + def add_linear_constraint(self, lhs, cst, op): + r""" + Add the constraint ``lhs`` * x + cst ``op`` 0, + where ``lhs`` is a vector of length ambient_dim, and + ``op`` is one of ``operator.lt``, ``operator.gt``, ``operator.eq``, + ``operator.le``, ``operator.ge`` + + EXAMPLES:: + + sage: from cutgeneratingfunctionology.spam.basic_semialgebraic import * + sage: P = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(2) + sage: P.add_linear_constraint([2,3],-6,operator.gt) + sage: sorted(P.lt_poly()) + [-2*x0 - 3*x1 + 6] + """ + lhs = vector(lhs) + constraint = self._pplite_constraint(lhs, cst, op) + self._polyhedron.add_constraint(constraint) + + def is_empty(self): + """ + EXAMPLES:: + + sage: from cutgeneratingfunctionology.spam.basic_semialgebraic import * + sage: S = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(1) + sage: S.add_linear_constraint([1], -1, operator.ge) + sage: S.is_empty() + False + sage: S.add_linear_constraint([1], +1, operator.le) + sage: S.is_empty() + True + """ + return self._polyhedron.is_empty() + + def is_universe(self): + """ + EXAMPLES:: + + sage: from cutgeneratingfunctionology.spam.basic_semialgebraic import * + sage: S = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(1) + sage: S.add_linear_constraint([0], 0, operator.eq) + sage: S.is_universe() + True + sage: S.add_linear_constraint([1], 1, operator.le) + sage: S.is_universe() + False + """ + self._polyhedron.minimize() + return self._polyhedron.is_universe() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index a54e3a07a..aba99df62 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ sphinx sphinxcontrib-bibtex sphinxcontrib-websupport pynormaliz +pplitepy From 257832ef9f9171ccd5ada439c804f1dcd377231b Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Thu, 14 Aug 2025 15:22:08 -0700 Subject: [PATCH 02/64] calcuate slopes in common field of breakpoints and values --- cutgeneratingfunctionology/igp/functions.sage | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/cutgeneratingfunctionology/igp/functions.sage b/cutgeneratingfunctionology/igp/functions.sage index 30dce5cd0..1890eae5f 100644 --- a/cutgeneratingfunctionology/igp/functions.sage +++ b/cutgeneratingfunctionology/igp/functions.sage @@ -1513,9 +1513,15 @@ def piecewise_function_from_breakpoints_slopes_and_values(bkpt, slopes, values, if field is None: field = default_field # global symb_values - symb_values = bkpt + slopes + values - field_values = nice_field_values(symb_values, field) - bkpt, slopes, values = field_values[0:len(bkpt)], field_values[len(bkpt):len(bkpt)+len(slopes)], field_values[-len(values):] + if slopes is None: + symb_values = bkpt + values + field_values = nice_field_values(symb_values, field) + bkpt, values = field_values[0:len(bkpt)], field_values[-len(values):] + slopes = [(values[i+1]-values[i])/(bkpt[i+1]-bkpt[i]) if bkpt[i+1] != bkpt[i] else 0 for i in range(len(bkpt)-1)] + else: + symb_values = bkpt + slopes + values + field_values = nice_field_values(symb_values, field) + bkpt, slopes, values = field_values[0:len(bkpt)], field_values[len(bkpt):len(bkpt)+len(slopes)], field_values[-len(values):] intercepts = [ values[i] - slopes[i]*bkpt[i] for i in range(len(slopes)) ] # Make numbers nice ## slopes = [ canonicalize_number(slope) for slope in slopes ] @@ -1546,8 +1552,7 @@ def piecewise_function_from_breakpoints_and_values(bkpt, values, field=None, mer """ if len(bkpt)!=len(values): raise ValueError("Need to have the same number of breakpoints and values.") - slopes = [ (values[i+1]-values[i])/(bkpt[i+1]-bkpt[i]) if bkpt[i+1] != bkpt[i] else 0 for i in range(len(bkpt)-1) ] - return piecewise_function_from_breakpoints_slopes_and_values(bkpt, slopes, values, field, merge=merge) + return piecewise_function_from_breakpoints_slopes_and_values(bkpt, None, values, field, merge=merge) def piecewise_function_from_breakpoints_and_slopes(bkpt, slopes, field=None, merge=True): r""" From 32a406965780e73a8b3f0435792492fec0e96bab Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Thu, 14 Aug 2025 16:45:20 -0700 Subject: [PATCH 03/64] start of minimal functions cashe --- .../igp/minimal_function_cashe.sage | 372 ++++++++++++++++++ 1 file changed, 372 insertions(+) create mode 100644 cutgeneratingfunctionology/igp/minimal_function_cashe.sage diff --git a/cutgeneratingfunctionology/igp/minimal_function_cashe.sage b/cutgeneratingfunctionology/igp/minimal_function_cashe.sage new file mode 100644 index 000000000..62b9be6e3 --- /dev/null +++ b/cutgeneratingfunctionology/igp/minimal_function_cashe.sage @@ -0,0 +1,372 @@ +from copy import deepcopy +from itertools import pairwise +from cutgeneratingfunctionology.igp import * +import csv +import os + + +ppl_bsa = BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(0), polynomial_map=[], poly_ring=sym_ring, v_dict={}) +pplite_bsa = BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(0), polynomial_map=[], poly_ring=sym_ring, v_dict={}) + +def mod_one(x): + if x >= 0: + return x - int(x) + return x - int(x) + 1 + +def add_breakpoint(bkpt): + """ + Given a breakpoint sequence, creates a list of breakpoint sequences of length one more such that + each the breakpoint complex of each breakpoint sequence is not guaranteed to be isomorphic to any + other breakpoint sequence in the list. + + + INPUT: Assume vector or list of n breakpoints with lambda_0=0 and lambda_i>> add_breakpoint([0]) + [[0, 3/4], [0, 1/2], [0, 1/4]] + >>> add_breakpoint([0,1/3]) + [[0, 1/12, 1/3], [0, 1/6, 1/3], [0, 1/4, 1/3], [0, 1/3, 2/3], [0, 1/3, 5/12], [0, 1/3, 1/2], [0, 1/3, 7/12], [0, 1/3, 5/6]] + """ + possible_new_seqs = [] + possible_eq_lambda_star = [b/2 for b in bkpt+[1]] + [(1+b)/2 for b in bkpt] + intervals_along_y_eq_x = sorted(list(set(tuple(bkpt + [1] + [b/2 for b in bkpt+[1]] + [(1+b)/2 for b in bkpt])))) + possible_ne_lambda_star = [] + for lower_bound, upper_bound in pairwise(intervals_along_y_eq_x): + possible_ne_lambda_star.append(1/2 * (lower_bound + upper_bound)) + possible_lambda_star = possible_eq_lambda_star + possible_ne_lambda_star + for lambda_star in possible_eq_lambda_star + possible_ne_lambda_star: + temp_bkpt = deepcopy(bkpt) + temp_bkpt.append(mod_one(lambda_star)) + possible_new_seqs.append(sorted(temp_bkpt)) + possible_new_seqs = [list(y) for y in set([tuple(x) for x in possible_new_seqs]) if len(set(y)) == len(y)] + return possible_new_seqs + + + +def make_bkpts_with_len_n(n, k=1, bkpts=None): + """ + Produce representative elements of every isomorphism class of breakpoints complexes for breakpoint sequences of length n. + + Note, this function does not check that the data input is correct and assume it is being used correctly. + + INPUT: n, length of breakpoint sequence, k, length of every element, bkpts, an iterable of breakpoints all of length k. + + OUTPUT: A list of representative elements of every isomorphism class of breakpoints complexes for breakpoint sequences of length n extrapolated from bkpts. + """ + # Look into using a directed tree as an underlying data structure for generating elements. + new_bkpts = [] + if n < 2: + raise ValueError("n>=2") + if k == n: + raise ValueError("k= 2) + coord_names = [] + bkpt_vals = bkpt + vals = bkpt_vals[0:n] + for i in range(0,n): + coord_names.append('lambda'+str(i)) + K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, partial_test_point_mode=True, bsa=bsa) + logging.disable(logging.INFO) + K.gens()[0] == 0 + for i in range(n-1): + K.gens()[i] < K.gens()[i+1] + K.gens()[n-1] < 1 + h = piecewise_function_from_breakpoints_and_values([0] + K.gens()[0:n-1] + [1], [0]*(n+1), merge=False) + for vert in generate_type_1_vertices_continuous(h, operator.ge, [0] + K.gens()[0:n-1] + [1]): + vert + for vert in generate_type_2_vertices_continuous(h, operator.ge, [0] + K.gens()[0:n-1] + [1]): + vert + return K.make_proof_cell().bsa + + +class BreakpointComplexClassContainer: + """ + A container for the family of breakpoint complexes for peicewise linear functions + with at most n breakpoints. + """ + def __init__(self, n, **kwrds): + self._n = n + assert(self._n >= 2) + if "backend" in kwrds.keys(): + if kwrds[backend] == "pplite": + self._backend = "pplite" + else: + self._backend = None + if "load_rep_elem_data" in kwrds.keys(): + if kwrds[load_rep_elem_data] is None: + logging.warning("Generating representative elements. This might take a while.") + self._data = make_bkpts_with_len_n(self._n) + else: + file_names = kwrds["load_bkpt_data"].split(",") + self._data = [] + for file_name in file_names: + file = open(file_name, "r") + self._data += [eval(preparse(data)) for data in list(csv.reader(file))] + file.close() + if "gen_elems_from_data" in kwrds.keys(): + if kwrds[gen_elems_from_data] == True: + k = len(self._data[0]) + if k < n: + self._data = make_bkpts_with_len_n(n, k, self._data) + else: + logging.warning("Generating representative elements. This might take a while.") + self._data = make_bkpts_with_len_n(self._n) + + def __repr__(self): + return "Container of a family of breakpoint" + + def get_rep_elems(self): + for bkpt in self._data: + yield bkpt + + def get_nnc_poly_from_bkpt(self): + for bkpt in self._data: + yield nnc_poly_from_bkpt(bkpt, self._backend) + + def num_rep_elems(self): + return len(self._data) + + def add_one_bkpt_to_all(self): + logging.warning("Generating representative elements. This might take a while.") + self._data = make_bkpts_with_len_n(self._n+1, self._n, self._data) + + def write_data(self, output_file_name_style=None, max_rows=None): + """ + Writes representative element data to a `.csv` file with one column and rows of representative elements. + Optionally, write many `.csv` files with at `most max_rows` rows per file. + Files are named output_file_file_name_style_filenumber.csv. + The default output_file_name_style="bkpts_of_len_n". + """ + # TODO: Future, support writing different types of data such as polyhedra data. + if output_file_name_style is None: + file_name_base = "bkpts_of_len_{}".format(self._n) + else: + file_name_base =output_file_name_style + if max_rows is not None: + num_files = len(self._data)//max_rows + 1 + file_name_base = file_name_base + "_part_0" + if max_rows is None: + max_rows = 0 + output_file = file_name_base +".csv" + for file_number in range(num_files): + out_file = open(output_file, "w") + data_writer = csv.writer(out_file, csv.QUOTE_NONE) + for row in range(max_row): + data_writer.writerow(self._data[max_row * file_number + row]) + out_file.close() + output_file = file_name_base[:-1]+"{}".format(file_number) + + +def generate_assumed_symmetric_vertices_continuous(fn, f, bkpt): + """ + Assumes the symmetry condition holds for all vertices (x,y) in bkpt's breakpoints complex + such that x+y equiv f. + """ + for i in range(len(bkpt)): + x = bkpt[i] + if x == f: + continue + if x < f: + y = f - x + else: + y = 1 + f - x + fn(x) + fn(y) == 1 + yield (x, y, 0, 0) + + +def assume_minimality(bkpt, f_index, backend=None): + """ + Given a breakpoint sequence, bkpt, and an index for f, f_index, determine if there is a (rep_bkpt, v) + such that pi_(rep_bkpt,v) is minimal, pi_(bkpt,v)(lambda_f_index)=1, and rep_bkpt's breakpoint complex + is isomorphic to bkpt's breakpoint complex. + + INPUT: bkpt a list or vector of length n. bkpt is assumed to be breakpoint sequence, f_index an integer. + + OUTPUT: (rep_bkpt, v), a pair of lists of length n with the described property. + """ + n = len(bkpt) + if backend is None: + bsa = ppl_bsa.copy() + if backend == "pplite": + bsa = pplite_bsa.copy() + assert(n >= 2) + assert(f_index >= 1) + assert(f_index <= n - 1) + coord_names = [] + bkpt_vals = bkpt + vals = bkpt_vals[1:n]+ [None]*(n-1) + for i in range(1,n): + coord_names.append('lambda'+str(i)) + for i in range(1,n): + coord_names.append('gamma'+str(i)) + logging.disable(logging.INFO) + K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, partial_test_point_mode=True, bsa=bsa) + for i in range(n-1): + K.gens()[i+n-1] <=1 + K.gens()[i+n-1] > 0 + h = piecewise_function_from_breakpoints_and_values([0] + K.gens()[0:n-1] + [1], [0] + K.gens()[n-1:2*n-2] + [0], merge=False) + for vert in generate_type_1_vertices_continuous(h, operator.ge, [0] + K.gens()[0:n-1] + [1]): + vert + for vert in generate_type_2_vertices_continuous(h, operator.ge, [0] + K.gens()[0:n-1] + [1]): + vert + for vert in generate_assumed_symmetric_vertices_continuous(h, K.gens()[f_index-1], [0] + K.gens()[0:n-1] + [1]): + vert + for i in range(n): + K.gens()[i] == bkpt[i] + K.find_test_point() + h_2 = piecewise_function_from_breakpoints_and_values([0]+list(K._values[0:n-1])+[1], [0] + list(K._values[n-1:2*n-2])+[0]) + is_minimal = minimality_test(h_2) + if is_minimal: + rep_bkpt = [0] + list(K._values[0:n-1]) + v = [0] + list(K._values[n-1:2*n-2]) + return (rep_bkpt, v) + + + +def bsa_of_rep_element(bkpt, vals): + """ + Given pi_(bkpt, vals) is {minimal, not minimal}, find BSA subset of R^(2n) such that (bkpt, vals) in BSA and for all p + in BSA, pi_p is {minimal, not minimal}. + + INPUT: (bkpt, vals) are lists or vectors of length n and bkpt is a proper breakpoints sequence and vals + is the corresponding value parameters. + + OUTPUT: A basic semialgebraic set. + """ + n = len(bkpt) + assert(n>=2) + coord_names = [] + for i in range(0,n): + coord_names.append('lambda'+str(i)) + for i in range(0,n): + coord_names.append('gamma'+str(i)) + logging.disable(logging.INFO) + K = ParametricRealField(names=coord_names, values = bkpt+vals, big_cells=True) + h = piecewise_function_from_breakpoints_and_values([0] + K.gens()[1:n] + [1], [0] + K.gens()[n+1:2*n] + [0], merge=False) + minimality_test(h) + return K.make_proof_cell().bsa + + +def find_minimal_function_reps_from_bkpts(bkpts, backend=None): + """ + Finds representative elements of minimal functions if they exist from the given breakpoint sequence. + """ + data = [] + for bkpt in bkpts: + for i in range(1, len(bkpt)): + result = assume_minimality(bkpt, i, backend) + if result is not None: + data.append(result) + return data + +class PiMinContContainer: + """ + A container for the space of continuous piecewise linear minimal functions with at + most n breakpoints paramaterized by breakpoints and values using semialgebraic sets. + + TESTS:: + + >>> PiMin_at_most_4_breakpoints = PiMinContContainer(4) + >>> all([minimality_test(pi) for PiMin_at_most_4_breakpoints.get_rep_functions()]) + True + >>> + """ + def __init__(self, n, **kwrds): + self._n = n + assert(self._n >= 2) + if "backend" in kwrds.keys(): + if kwrds[backend] == "pplite": + self._backend = "pplite" + else: + self._backend = None + if "load_bkpt_data" in kwrds.keys() and "load_rep_elem_data" not in kwrds.keys(): + file_names = kwrds["load_bkpt_data"].split(",") + bkpts = [] + for file_name in file_names: + file = open(file_name, "r") + bkpts += [eval(preparse(data)) for data in list(csv.reader(file))] + close(file) + self._data = find_minimal_function_reps_from_bkpts(bkpts) + elif "load_bkpt_data" not in kwrds.keys() and "load_rep_elem_data" in kwrds.keys(): + file_names = kwrds["load_rep_elem_data"].split(",") + self._data = [] + for file_name in file_names: + file = open(file_name, "r") + self._data += [(eval(preparse(data[0])), eval(preparse(data[1]))) for data in list(csv.reader(file))] + file.close() + else: + logging.warning("Generating representative elements. This might take a while.") + bkpts = make_bkpts_with_len_n(self._n) + self._data = find_minimal_function_reps_from_bkpts(bkpts, self._backend) + + def __repr__(self): + return "Space of minimal functions with at most {} breakpoints parameterized by breakpoints and values using semialgebraic sets.".format(self._n) + + def get_semialgebraic_sets(self): + for b, v in self._data: + yield bsa_of_rep_element(b, v) + + def get_rep_elems(self): + for b, v in self._data: + yield (b, v) + + def get_rep_functions(self): + for b, v in self._data: + yield piecewise_function_from_breakpoints_and_values(list(b)+[1], list(v)+[0]) + + def n(self): + return self._n + + def covers_space(self): + raise NotImplementedError + + def refine_space(self): + raise NotImplementedError + + def write_data(self, output_file_name_style=None, max_rows=None): + """ + Writes representative element data to a `.csv` file with one column and rows of representative elements. + Optionally, write many `.csv` files with at `most max_rows` rows per file. + Files are named output_file_file_name_style_filenumber.csv. + The default output_file_name_style="Pi_Min_n". + """ + # TODO: Future, support writing different types of data such as polyhedra data. + if output_file_name_style is None: + file_name_base = "Pi_Min_{}".format(self._n) + else: + file_name_base =output_file_name_style + if max_rows is not None: + num_files = len(self._data)//max_rows + 1 + file_name_base = file_name_base + "_part_0" + if max_rows is None: + max_rows = 0 + output_file = file_name_base +".csv" + for file_number in range(num_files): + out_file = open(output_file, "w") + data_writer = csv.writer(out_file, csv.QUOTE_NONE) + for row in range(max_row): + data_writer.writerow(self._data[max_row * file_number + row]) + out_file.close() + output_file = file_name_base[:-1]+"{}".format(file_number) + + From 4648c62be2bc36ff41ceb4ead3c0bb40b292ec96 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Thu, 21 Aug 2025 18:12:20 -0700 Subject: [PATCH 04/64] add _partial_eval method to ParametricRealField --- .../igp/parametric.sage | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/cutgeneratingfunctionology/igp/parametric.sage b/cutgeneratingfunctionology/igp/parametric.sage index 908b10e9a..b5bd9da43 100644 --- a/cutgeneratingfunctionology/igp/parametric.sage +++ b/cutgeneratingfunctionology/igp/parametric.sage @@ -562,6 +562,28 @@ class ParametricRealField(Field): except TypeError: # 'None' components pass raise FactorUndetermined("{} cannot be evaluated because the test point is not complete".format(fac)) + + def _partial_eval_factor(self, fac): + """ + Partially evaluate ``fac`` on the test point. + + This function is only intended to be called after ``FactorUndetermined`` is raised from ``_eval_factor``. + """ + val_dict = {sym:val for sym, val zip([symb.sym() for symb in self._gens], self._values) if val is not None} + return fac.subs(val_dict) + +# Returns a symbolic expression or raises an ``EvaluationSuccessfulFlag``. +# Receiving an ``EvaluationSuccessfulFlag`` means ``fac`` can be evaluated with the known values of the +# test point. +# base_ring = self._sym_field.base_ring() +# if fac in base_ring: +# raise EvaluationSuccessfulFlag("{} can be evaluated in the base_ring. Use _eval_factor instead.".format(fac)) +# try: +# fac(self._values) +# raise EvaluationSuccessfulFlag("{} can be evaluated with the test point. Use _eval_factor instead.".format(fac)) +# except TypeError: +# val_dict = {sym:val for sym, val zip([symb.sym() for symb in self._gens], self._values) if val is not None} +# return fac.subs(val_dict) def _factor_sign(self, fac): """ From dbdfb61139b362e949a79e045d990d1b9b13a6d6 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Thu, 21 Aug 2025 18:13:22 -0700 Subject: [PATCH 05/64] update doc to design goals for partial test points --- cutgeneratingfunctionology/igp/parametric.sage | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/cutgeneratingfunctionology/igp/parametric.sage b/cutgeneratingfunctionology/igp/parametric.sage index b5bd9da43..04f688b69 100644 --- a/cutgeneratingfunctionology/igp/parametric.sage +++ b/cutgeneratingfunctionology/igp/parametric.sage @@ -158,25 +158,22 @@ class ParametricRealField(Field): sage: f[0]*f[1] <= 4 True - Test-point free mode (limited functionality and MUCH slower because of many more polynoial - evaluations via libsingular):: + Test-point free descriptions can be written which every comparison is assumed to be true. + MUCH slower because of many more polynoial evaluations via libsingular:: sage: K. = ParametricRealField(None, mutable_values=True) sage: a <= 2 - Traceback (most recent call last): - ... - FactorUndetermined: a cannot be evaluated because the test point is not complete + True sage: K.assume_comparison(a.sym(), operator.le, 3) - Partial test point mode:: + Comparisons with test-points that are partially defined are supported. Comparisons made in + unspecified variables are assumed to be true:: sage: K. = ParametricRealField([None, 1], mutable_values=True) sage: a <= 2 - Traceback (most recent call last): - ... - FactorUndetermined: a cannot be evaluated because the test point is not complete - sage: b <= 11 True + sage: b <= 11 + True """ Element = ParametricRealFieldElement From e47fcd5f5891eb6a8fdd88fbb9096109519e8f9c Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Thu, 21 Aug 2025 18:18:16 -0700 Subject: [PATCH 06/64] change assume_comparison to support partial evaluations --- cutgeneratingfunctionology/igp/parametric.sage | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/cutgeneratingfunctionology/igp/parametric.sage b/cutgeneratingfunctionology/igp/parametric.sage index 04f688b69..7628e7541 100644 --- a/cutgeneratingfunctionology/igp/parametric.sage +++ b/cutgeneratingfunctionology/igp/parametric.sage @@ -174,7 +174,6 @@ class ParametricRealField(Field): True sage: b <= 11 True - """ Element = ParametricRealFieldElement @@ -859,14 +858,24 @@ class ParametricRealField(Field): try: comparison_val = comparison.val() except FactorUndetermined: - comparison_val = None + # partial test point evaluation; assume evaluation is true + # so record the assumed factor in the BSA + # it is the responsibility of the BSA to know if it is empty or not + # most implementations of BSAs cannot do this for non-linear cases. + assumed_fac = self._partial_eval_factor(comparison) + if not is_factor_known(assumed_fac): + record_comparision(assumed_fac, op) + return comparison = comparison.sym() else: comparison = self._sym_field(comparison) try: comparison_val = self._eval_factor(comparison) except FactorUndetermined: - comparison_val = None + assumed_fac = self._partial_eval_factor(comparison) + if not is_factor_known(assumed_fac): + record_comparision(assumed_fac, op) + return if comparison_val is not None: if not op(comparison_val, 0): if comparison in base_ring: From 44cf014f6ea22948f65db308aeda3360da89c65c Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Fri, 22 Aug 2025 14:34:21 -0700 Subject: [PATCH 07/64] fix syntax --- cutgeneratingfunctionology/igp/parametric.sage | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cutgeneratingfunctionology/igp/parametric.sage b/cutgeneratingfunctionology/igp/parametric.sage index 7628e7541..78d96b260 100644 --- a/cutgeneratingfunctionology/igp/parametric.sage +++ b/cutgeneratingfunctionology/igp/parametric.sage @@ -565,7 +565,9 @@ class ParametricRealField(Field): This function is only intended to be called after ``FactorUndetermined`` is raised from ``_eval_factor``. """ - val_dict = {sym:val for sym, val zip([symb.sym() for symb in self._gens], self._values) if val is not None} + syms = [symb.sym() for symb in self._gens] + val_dict = {sym:val for sym, val in zip(syms , self._values) if val is not None} + print(val_dict) return fac.subs(val_dict) # Returns a symbolic expression or raises an ``EvaluationSuccessfulFlag``. From 96f0921d0bf645da3a8dc55bd623d500579e1d56 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Fri, 22 Aug 2025 14:52:21 -0700 Subject: [PATCH 08/64] move FactorUndetermined to its own file to make it a shared resouce --- cutgeneratingfunctionology/igp/parametric.sage | 4 +--- cutgeneratingfunctionology/shared/EvaluationExceptions.py | 2 ++ .../spam/parametric_real_field_element.py | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 cutgeneratingfunctionology/shared/EvaluationExceptions.py diff --git a/cutgeneratingfunctionology/igp/parametric.sage b/cutgeneratingfunctionology/igp/parametric.sage index 78d96b260..8facd9031 100644 --- a/cutgeneratingfunctionology/igp/parametric.sage +++ b/cutgeneratingfunctionology/igp/parametric.sage @@ -20,6 +20,7 @@ from cutgeneratingfunctionology.spam.basic_semialgebraic_local import BasicSemia from cutgeneratingfunctionology.spam.semialgebraic_mathematica import BasicSemialgebraicSet_mathematica, from_mathematica from cutgeneratingfunctionology.spam.basic_semialgebraic_groebner_basis import BasicSemialgebraicSet_groebner_basis from cutgeneratingfunctionology.spam.polyhedral_complex import PolyhedralComplex +from cutgeneratingfunctionology.shared.EvaluationExceptions import FactorUndetermined from .parametric_family import Classcall, ParametricFamily_base, ParametricFamily debug_new_factors = False @@ -58,9 +59,6 @@ class ParametricRealFieldRefinementError(ValueError): from contextlib import contextmanager -class FactorUndetermined(Exception): - pass - allow_refinement_default = True big_cells_default = 'if_not_allow_refinement' mutable_values_default = False diff --git a/cutgeneratingfunctionology/shared/EvaluationExceptions.py b/cutgeneratingfunctionology/shared/EvaluationExceptions.py new file mode 100644 index 000000000..7e7dd6e04 --- /dev/null +++ b/cutgeneratingfunctionology/shared/EvaluationExceptions.py @@ -0,0 +1,2 @@ +class FactorUndetermined(Exception): # FactorUndetermined is raised when an expression can not be evaluated with a test point. + pass \ No newline at end of file diff --git a/cutgeneratingfunctionology/spam/parametric_real_field_element.py b/cutgeneratingfunctionology/spam/parametric_real_field_element.py index c6688eeeb..40c1a4ea9 100644 --- a/cutgeneratingfunctionology/spam/parametric_real_field_element.py +++ b/cutgeneratingfunctionology/spam/parametric_real_field_element.py @@ -9,6 +9,7 @@ from sage.rings.real_mpfr import RR from sage.functions.other import ceil, floor from sage.functions.generalized import sign +from cutgeneratingfunctionology.shared.EvaluationExceptions import FactorUndetermined import operator def richcmp_op_negation(op): @@ -20,7 +21,7 @@ def richcmp_op_negation(op): return op_NE elif op == op_NE: return op_EQ - elif op == op_GT: + elif op == op_GT:s return op_LE elif op == op_GE: return op_LT From 6391ecd7b33789ee5b17f3d5caea85e333bbd5b5 Mon Sep 17 00:00:00 2001 From: Acadia Larsen <102884863+ComboProblem@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:39:23 -0700 Subject: [PATCH 09/64] Update setup.py Remove Setup Tools --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 909a1072a..15f87fd8e 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,9 @@ ## -*- encoding: utf-8 -*- import os import sys -from setuptools import setup +# from setuptools import setup from codecs import open # To open the README file with proper encoding -from setuptools.command.test import test as TestCommand # for tests +# from setuptools.command.test import test as TestCommand # for tests # Get information from separate files (README, VERSION) From 0df59894d678a41b97662193c98aefacb3c4538a Mon Sep 17 00:00:00 2001 From: Acadia Larsen <102884863+ComboProblem@users.noreply.github.com> Date: Thu, 28 Aug 2025 12:41:20 -0700 Subject: [PATCH 10/64] Update setup.py iteration of testing --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 15f87fd8e..f1f16957e 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ ## -*- encoding: utf-8 -*- import os import sys -# from setuptools import setup +from setuptools import setup from codecs import open # To open the README file with proper encoding # from setuptools.command.test import test as TestCommand # for tests From 387f17799fa34c8ef917fce9943c8d1ffecdc015 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Thu, 28 Aug 2025 15:13:15 -0700 Subject: [PATCH 11/64] inprogress --- .../igp/parametric.sage | 30 +++++++++++-------- .../spam/parametric_real_field_element.py | 17 +++++++---- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/cutgeneratingfunctionology/igp/parametric.sage b/cutgeneratingfunctionology/igp/parametric.sage index 8facd9031..4dbc7b660 100644 --- a/cutgeneratingfunctionology/igp/parametric.sage +++ b/cutgeneratingfunctionology/igp/parametric.sage @@ -563,9 +563,7 @@ class ParametricRealField(Field): This function is only intended to be called after ``FactorUndetermined`` is raised from ``_eval_factor``. """ - syms = [symb.sym() for symb in self._gens] - val_dict = {sym:val for sym, val in zip(syms , self._values) if val is not None} - print(val_dict) + val_dict = {sym:val for sym, val in zip(fac.parent().gens() , self._values) if val is not None} return fac.subs(val_dict) # Returns a symbolic expression or raises an ``EvaluationSuccessfulFlag``. @@ -858,14 +856,21 @@ class ParametricRealField(Field): try: comparison_val = comparison.val() except FactorUndetermined: - # partial test point evaluation; assume evaluation is true - # so record the assumed factor in the BSA - # it is the responsibility of the BSA to know if it is empty or not - # most implementations of BSAs cannot do this for non-linear cases. + # Partial test point evaluation assumes the partially + # evaluated factor is True. + # So, we record the assumed factor in the BSA without checking if the factor + # should be addeded or not. + # It becomes the responsibility of the BSA to detemined if the recorded factors + # so far repersent a non-empty BSA. + # Most implementations of BSAs cannot do this for non-linear cases. + # With a BSA that is equipped with a first order logic solver like QPEAD + # should be able to do this. assumed_fac = self._partial_eval_factor(comparison) - if not is_factor_known(assumed_fac): - record_comparision(assumed_fac, op) - return + self.record_factor(assumed_fac, op) + print(assumed_fac) + if self._bsa.is_empty(): + raise ParametricRealFieldInconsistencyError("Assumed constraint {} derivied from the comparision {} {} {} is inconsistent with already recoreded constraints".format(assumed_fac, lhs, op, rhs)) + return comparison = comparison.sym() else: comparison = self._sym_field(comparison) @@ -873,8 +878,9 @@ class ParametricRealField(Field): comparison_val = self._eval_factor(comparison) except FactorUndetermined: assumed_fac = self._partial_eval_factor(comparison) - if not is_factor_known(assumed_fac): - record_comparision(assumed_fac, op) + if not self.is_factor_known(assumed_fac, op): + self.record_factor(assumed_fac, op) + print("here", assumed_fac) return if comparison_val is not None: if not op(comparison_val, 0): diff --git a/cutgeneratingfunctionology/spam/parametric_real_field_element.py b/cutgeneratingfunctionology/spam/parametric_real_field_element.py index 40c1a4ea9..99d6a82fc 100644 --- a/cutgeneratingfunctionology/spam/parametric_real_field_element.py +++ b/cutgeneratingfunctionology/spam/parametric_real_field_element.py @@ -21,7 +21,7 @@ def richcmp_op_negation(op): return op_NE elif op == op_NE: return op_EQ - elif op == op_GT:s + elif op == op_GT: return op_LE elif op == op_GE: return op_LT @@ -77,9 +77,13 @@ def sym(self): def val(self): try: return self._val - except AttributeError: - return self.parent()._eval_factor(self._sym) - + except AttributeError: # with imutable values, this fales because we get some hash map weirdness + try: + return self.parent()._eval_factor(self._sym) + except FactorUndetermined: + possible_val = self.parent()._partial_eval_factor(self._sym) + if possible_val in possible_val.base_ring(): + return possible_val def _richcmp_(left, right, op): r""" Examples for traditional cmp semantics:: @@ -127,7 +131,10 @@ def _richcmp_(left, right, op): # shouldn't really happen, within coercion raise TypeError("comparing elements from different fields") if left.parent()._big_cells: - result = richcmp(left.val(), right.val(), op) + try: + result = richcmp(left.val(), right.val(), op) + except FactorUndetermined: + result = True if result: true_op = op else: From 56baa49e5eb3edc6f25bb93f81ec3ff670edbb89 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Sat, 30 Aug 2025 10:39:31 -0700 Subject: [PATCH 12/64] working partial test point more, next step fix doc string tests --- .../igp/parametric.sage | 31 ++++++------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/cutgeneratingfunctionology/igp/parametric.sage b/cutgeneratingfunctionology/igp/parametric.sage index 4dbc7b660..2ae87bb7f 100644 --- a/cutgeneratingfunctionology/igp/parametric.sage +++ b/cutgeneratingfunctionology/igp/parametric.sage @@ -171,7 +171,7 @@ class ParametricRealField(Field): sage: a <= 2 True sage: b <= 11 - True + True """ Element = ParametricRealFieldElement @@ -856,38 +856,27 @@ class ParametricRealField(Field): try: comparison_val = comparison.val() except FactorUndetermined: - # Partial test point evaluation assumes the partially - # evaluated factor is True. - # So, we record the assumed factor in the BSA without checking if the factor - # should be addeded or not. - # It becomes the responsibility of the BSA to detemined if the recorded factors - # so far repersent a non-empty BSA. - # Most implementations of BSAs cannot do this for non-linear cases. - # With a BSA that is equipped with a first order logic solver like QPEAD - # should be able to do this. - assumed_fac = self._partial_eval_factor(comparison) - self.record_factor(assumed_fac, op) - print(assumed_fac) - if self._bsa.is_empty(): - raise ParametricRealFieldInconsistencyError("Assumed constraint {} derivied from the comparision {} {} {} is inconsistent with already recoreded constraints".format(assumed_fac, lhs, op, rhs)) - return + comparison_val = None comparison = comparison.sym() else: comparison = self._sym_field(comparison) try: comparison_val = self._eval_factor(comparison) except FactorUndetermined: - assumed_fac = self._partial_eval_factor(comparison) - if not self.is_factor_known(assumed_fac, op): - self.record_factor(assumed_fac, op) - print("here", assumed_fac) - return + comparison_val = None if comparison_val is not None: if not op(comparison_val, 0): if comparison in base_ring: raise ParametricRealFieldInconsistencyError("New constant constraint {} {} {} is not satisfied".format(lhs, op, rhs)) else: raise ParametricRealFieldInconsistencyError("New constraint {} {} {} is not satisfied by the test point".format(lhs, op, rhs)) + else: #A numerical evaluation of the expression has failed. Assume the partial evaluation of the expression holds. + comparison_val_or_expr = self._partial_eval_factor(comparison) + if not op(comparison_val_or_expr, 0): #The partial evual + if comparison_val_or_expr in base_ring: + raise ParametricRealFieldInconsistencyError("New constant constraint {} {} {} is not satisfied".format(lhs, op, rhs)) + else: # comparision_val_or_expr is algebraic expresion, the only "true" comparision here is comparionsion val_or_expr + comparison = comparison_val_or_expr if comparison in base_ring: return if comparison.denominator() == 1 and comparison.numerator().degree() == 1: From 921178f667a152f7845692ec201a9f0cc0ae2999 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:12:41 -0700 Subject: [PATCH 13/64] finish comments; clean up tests. --- cutgeneratingfunctionology/igp/parametric.sage | 17 ++++++++--------- .../spam/parametric_real_field_element.py | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/cutgeneratingfunctionology/igp/parametric.sage b/cutgeneratingfunctionology/igp/parametric.sage index 2ae87bb7f..631226716 100644 --- a/cutgeneratingfunctionology/igp/parametric.sage +++ b/cutgeneratingfunctionology/igp/parametric.sage @@ -162,7 +162,8 @@ class ParametricRealField(Field): sage: K. = ParametricRealField(None, mutable_values=True) sage: a <= 2 True - sage: K.assume_comparison(a.sym(), operator.le, 3) + sage: K. = ParametricRealField(None, mutable_values=True) + sage: K.assume_comparison(a.sym(), operator.le, 2) Comparisons with test-points that are partially defined are supported. Comparisons made in unspecified variables are assumed to be true:: @@ -312,7 +313,7 @@ class ParametricRealField(Field): sage: sqrt2, = nice_field_values([sqrt(2)]) sage: K. = ParametricRealField([0], base_ring=sqrt2.parent()) sage: f + sqrt2 - (f + (a))~ + (f + a)~ This currently does not work for Sage's built-in embedded number field elements... """ @@ -404,9 +405,7 @@ class ParametricRealField(Field): ....: with K.temporary_assumptions(): ....: K.assume_comparison(a.sym(), operator.le, 3) ....: a <= 4 - Traceback (most recent call last): - ... - FactorUndetermined: a cannot be evaluated because the test point is not complete... + True """ self._values = [ None for n in self._names ] @@ -872,11 +871,11 @@ class ParametricRealField(Field): raise ParametricRealFieldInconsistencyError("New constraint {} {} {} is not satisfied by the test point".format(lhs, op, rhs)) else: #A numerical evaluation of the expression has failed. Assume the partial evaluation of the expression holds. comparison_val_or_expr = self._partial_eval_factor(comparison) - if not op(comparison_val_or_expr, 0): #The partial evual - if comparison_val_or_expr in base_ring: + if comparison_val_or_expr in base_ring: + if not op(comparison_val_or_expr, 0): #The partial evaluation is ture, means raise ParametricRealFieldInconsistencyError("New constant constraint {} {} {} is not satisfied".format(lhs, op, rhs)) - else: # comparision_val_or_expr is algebraic expresion, the only "true" comparision here is comparionsion val_or_expr - comparison = comparison_val_or_expr + else: # comparision_val_or_expr is algebraic expresion, asume the comparision here is comparionsion_val_or_expr + comparison = comparison_val_or_expr if comparison in base_ring: return if comparison.denominator() == 1 and comparison.numerator().degree() == 1: diff --git a/cutgeneratingfunctionology/spam/parametric_real_field_element.py b/cutgeneratingfunctionology/spam/parametric_real_field_element.py index 99d6a82fc..656734c4a 100644 --- a/cutgeneratingfunctionology/spam/parametric_real_field_element.py +++ b/cutgeneratingfunctionology/spam/parametric_real_field_element.py @@ -133,7 +133,7 @@ def _richcmp_(left, right, op): if left.parent()._big_cells: try: result = richcmp(left.val(), right.val(), op) - except FactorUndetermined: + except FactorUndetermined: # Partial evauation is happen, assume the result is True. result = True if result: true_op = op From 81d9b6cfb19d269274eeceae9f05ad156bcbb977 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:19:42 -0700 Subject: [PATCH 14/64] remove some comments --- .../spam/parametric_real_field_element.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cutgeneratingfunctionology/spam/parametric_real_field_element.py b/cutgeneratingfunctionology/spam/parametric_real_field_element.py index 656734c4a..b50c7d8d1 100644 --- a/cutgeneratingfunctionology/spam/parametric_real_field_element.py +++ b/cutgeneratingfunctionology/spam/parametric_real_field_element.py @@ -77,7 +77,7 @@ def sym(self): def val(self): try: return self._val - except AttributeError: # with imutable values, this fales because we get some hash map weirdness + except AttributeError: try: return self.parent()._eval_factor(self._sym) except FactorUndetermined: From a9536d0bfffc17abec6d0232911e99bdad653f80 Mon Sep 17 00:00:00 2001 From: Acadia Larsen <102884863+ComboProblem@users.noreply.github.com> Date: Fri, 26 Sep 2025 13:02:44 -0700 Subject: [PATCH 15/64] remove setuptools test not needed for containerized set ups --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index f1f16957e..cfc589fbd 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,6 @@ import sys from setuptools import setup from codecs import open # To open the README file with proper encoding -# from setuptools.command.test import test as TestCommand # for tests # Get information from separate files (README, VERSION) From cf4b10b9b01fd1ddf4a8a98f7f75c5fa67fef30a Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Mon, 29 Sep 2025 12:32:50 -0700 Subject: [PATCH 16/64] fix spelling of cache in file name; add minimal_function_cashe to load directory --- cutgeneratingfunctionology/igp/__init__.py | 1 + .../{minimal_function_cashe.sage => minimal_function_cache.sage} | 0 2 files changed, 1 insertion(+) rename cutgeneratingfunctionology/igp/{minimal_function_cashe.sage => minimal_function_cache.sage} (100%) diff --git a/cutgeneratingfunctionology/igp/__init__.py b/cutgeneratingfunctionology/igp/__init__.py index 4f59bc709..3c0e74f14 100644 --- a/cutgeneratingfunctionology/igp/__init__.py +++ b/cutgeneratingfunctionology/igp/__init__.py @@ -74,6 +74,7 @@ def igp_load(fpath): igp_load(igp_dir + "plot_options.sage") igp_load(igp_dir + "faster_subadditivity_test.sage") igp_load(igp_dir + "faster_subadditivity_test_discontinuous.sage") +igp_load(igp_dir + "minimal_function_cache.sage") from . import extreme_functions, procedures diff --git a/cutgeneratingfunctionology/igp/minimal_function_cashe.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage similarity index 100% rename from cutgeneratingfunctionology/igp/minimal_function_cashe.sage rename to cutgeneratingfunctionology/igp/minimal_function_cache.sage From e187f24bca8f051dede1b4f5b0f21e0349c61b8c Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Mon, 29 Sep 2025 13:05:04 -0700 Subject: [PATCH 17/64] move polyhedra backedn to parametric.sage --- .../igp/parametric.sage | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/cutgeneratingfunctionology/igp/parametric.sage b/cutgeneratingfunctionology/igp/parametric.sage index 73b49bda0..ad7cefbe2 100644 --- a/cutgeneratingfunctionology/igp/parametric.sage +++ b/cutgeneratingfunctionology/igp/parametric.sage @@ -183,7 +183,7 @@ class ParametricRealField(Field): def __init__(self, values=None, names=None, allow_coercion_to_float=True, mutable_values=None, allow_refinement=None, big_cells=None, - base_ring=None, sym_ring=None, bsa=None): + base_ring=None, sym_ring=None, bsa=None, default_backend=None): Field.__init__(self, self) if mutable_values is None: @@ -253,7 +253,10 @@ class ParametricRealField(Field): # do the computation of the polyhedron incrementally, # rather than first building a huge list and then in a second step processing it. # the upstairs polyhedron defined by all constraints in self._eq/lt_factor - polyhedron = BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(0) + if default_backend = "pplite": + polyhedron = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(0) + else: + polyhedron = BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(0) # monomial_list records the monomials that appear in self._eq/lt_factor. # v_dict is a dictionary that maps each monomial to the index of its corresponding Variable in polyhedron bsa = BasicSemialgebraicSet_veronese(polyhedron, polynomial_map=[], poly_ring=sym_ring, v_dict={}) @@ -312,7 +315,7 @@ class ParametricRealField(Field): TypeError: unsupported operand parent(s)... Test that real number field elements can be upgraded to ``ParametricRealFieldElement``s. - Note that this requires setting up the ParametricRealField with a specific base ring, + Note that this requires setting up the ParametricRealField with a specific base ring, because there is no common parent of QQ(x) and a RealNumberField``:: sage: sqrt2, = nice_field_values([sqrt(2)]) @@ -884,7 +887,7 @@ class ParametricRealField(Field): return if len(factors) == 1 and factors[0][1] == 1 and comparison_val is not None: the_fac, d = factors[0] - the_sign = sign(factors.unit() * comparison_val) + the_sign = sign(factors.unit() * comparison_val) def factor_sign(fac): if fac == the_fac: return the_sign @@ -1108,7 +1111,7 @@ class ParametricRealField(Field): def make_proof_cell(self, **opt): r""" Make a :class:`SemialgebraicComplexComponent` from a :class:`ParametricRealField`. - + In **opt, one can provide: region_type, function, find_region_type, default_var_bound, bddbsa, kwds_dict. EXAMPLES:: @@ -1447,7 +1450,7 @@ class SemialgebraicComplexComponent(SageObject): # FIXME: Rename this to be m pt = find_point_flip_ineq_heuristic(self.var_value, l, list(bsa.lt_poly())+list(bsa.le_poly()), flip_ineq_step) if pt is not None: # Find a new point, use polynomial map to recover the values of those eliminated variables. - pt_across_wall = tuple(p(pt) for p in self.polynomial_map) + pt_across_wall = tuple(p(pt) for p in self.polynomial_map) if wall_crossing_method == 'mathematica' or wall_crossing_method == 'heuristic_then_mathematica' and (pt_across_wall is None): bsa_mathematica = bsa.formal_relint(bsa_class='mathematica') # was BasicSemialgebraicSet_mathematica.from_bsa(bsa) bsa_mathematica.add_polynomial_constraint(l, operator.gt) @@ -1662,7 +1665,7 @@ class ProofCell(SemialgebraicComplexComponent, Classcall): sage: C_copy._init_args == C._init_args True - (We do not test for equality C_copy == C -- we have not even decided yet what the semantics + (We do not test for equality C_copy == C -- we have not even decided yet what the semantics of equality of bsa is.) """ @@ -1807,7 +1810,7 @@ class SemialgebraicComplex(SageObject): sage: complex.is_complete() # optional - mathematica True - + Example with non-linear wall:: sage: complex = SemialgebraicComplex(lambda x,y: max(x,y^2), ['x','y'], find_region_type=result_symbolic_expression, default_var_bound=(-3,3)) # optional - mathematica @@ -1902,7 +1905,7 @@ class SemialgebraicComplex(SageObject): to a "type" of the parameter region, for example: - :func:`find_region_type_igp` (the default). The result of the computation is a Gomory-Johnson - function `h`; it is passed to :func:`find_region_type_igp` as 2nd arg, + function `h`; it is passed to :func:`find_region_type_igp` as 2nd arg, and then :func:`find_region_type_igp`which classifies the region of the parameter by returning one of the strings ``'is_constructible'``, ``'not_constructible'``, @@ -1981,7 +1984,7 @@ class SemialgebraicComplex(SageObject): r""" Return a random point that satisfies var_bounds and is in self.bsa. - - If var_bounds is not specified, self.default_var_bound is taken. + - If var_bounds is not specified, self.default_var_bound is taken. - var_bounds can be a list of 2-tuples whose length equals to the number of parameters, or lambda functions. - It is used in random shooting method for functions like ``dg_2_step_mir``, which involve floor/ceil operations. We try to plot one layer for each n = floor(...) and superimpose the layers at the end to get the whole picture. @@ -2072,12 +2075,12 @@ class SemialgebraicComplex(SageObject): for c in self.components: if var_value in c.bsa: yield c - + def find_uncovered_random_point(self, var_bounds=None, max_failings=10000): r""" Return a random point that satisfies the bounds and is uncovered by any cells in the complex. Return ``None`` if the number of attemps > max_failings. - + EXAMPLES:: sage: from cutgeneratingfunctionology.igp import * @@ -2106,7 +2109,7 @@ class SemialgebraicComplex(SageObject): The argument formal_closure whether inequalities are treated as <= 0 or as < 0. If such point does not exist, return ``None``. - + EXAMPLES:: sage: from cutgeneratingfunctionology.igp import * @@ -2183,14 +2186,14 @@ class SemialgebraicComplex(SageObject): self.points_to_test[num_eq] = OrderedDict() if not num_eq in self.tested_points: self.tested_points[num_eq] = set([]) - new_component = self._cell_class(self.family, var_value, + new_component = self._cell_class(self.family, var_value, find_region_type=self.find_region_type, bddbsa=bddbsa, polynomial_map=polynomial_map) new_num_eq = len(list(new_component.bsa.eq_poly())) if new_num_eq > num_eq: logging.warning("The cell around %s defined by %s has more equations than boundary %s" %(new_component.var_value, new_component.bsa, bddbsa)) #import pdb; pdb.set_trace() - # bsa is lower dimensional as it has more equations than bddbsa, + # bsa is lower dimensional as it has more equations than bddbsa, # so we try to perturb the testpoint to obtain a # new testpoint in bddbsa that does not fall into a lower dimensional cell. # Heuristic code using gradient desecent. #FIXME. @@ -2255,7 +2258,7 @@ class SemialgebraicComplex(SageObject): Plot the complex and store the graph. - If restart is ``False``, plot the newly added cells on top of the last graph; otherwise, start a new graph. - - If slice_value is given, it is either a polynomial_map that defines a section, or a list of fixed parameter values with two of them being None. Plot the section. + - If slice_value is given, it is either a polynomial_map that defines a section, or a list of fixed parameter values with two of them being None. Plot the section. - plot_points controls the quality of the plotting. EXAMPLES:: @@ -2311,7 +2314,7 @@ class SemialgebraicComplex(SageObject): # # FIXME: zorder is broken in region_plot/ContourPlot. # for c in self.components[self.num_plotted_components::]: # num_eq = len(list(c.bsa.eq_poly())) - # gc = c.plot(alpha=alpha, plot_points=plot_points, slice_value=slice_value, default_var_bound=self.default_var_bound, goto_lower_dim=goto_lower_dim, zorder=num_eq, **kwds) + # gc = c.plot(alpha=alpha, plot_points=plot_points, slice_value=slice_value, default_var_bound=self.default_var_bound, goto_lower_dim=goto_lower_dim, zorder=num_eq, **kwds) # if gc: # need this because (empty g + empty gc) forgets about xmin xmax ymin ymax. # self.graph += gc # Workaround. @@ -2386,7 +2389,7 @@ class SemialgebraicComplex(SageObject): - var_value: the starting point. If not given, start with a random point. - flip_ineq_step: a small positive number that controls the step length in wall-crossing. - check_completion: When check_completion is ``True``, after bfs terminates, check whether the entire parameter space is covered by cells, using Mathematica's ``FindInstance`` (if max_failings=0) or random shooting (if max_failings>0). If an uncovered point has been found, restart the bfs from this point. - - wall_crossing_method: 'mathematica' or 'heuristic' or 'heuristic_then_mathematica' + - wall_crossing_method: 'mathematica' or 'heuristic' or 'heuristic_then_mathematica' - wall_crossing_method='heuristic_then_mathematica': try heuristic method first. If it does not find a new testpoint, then use Mathematica. - goto_lower_dim: whether lower dimensional cells are considered. If goto_lower_dim is ``False`` or if goto_lower_dim is ``True`` and wall_crossing method is 'heuristic' but the wall is non-linear, then find new testpoint across the wall only. @@ -2680,8 +2683,8 @@ def return_result(field, result): def result_concrete_value(field, result): r""" Return the concrete values in result as a tuple. See also ``result_symbolic_expression()``. - - This function can provided to ``find_region_type`` when setting up a :class:`SemialgebraicComplex`. + + This function can provided to ``find_region_type`` when setting up a :class:`SemialgebraicComplex`. In this way, one can compare result of type :class:`ParametricRealFieldElement` or list of :class:`ParametricRealFieldElement` with the previous elements in ``region_type_color_map`` which do not necessarily have the same parent. @@ -2703,8 +2706,8 @@ def result_concrete_value(field, result): def result_symbolic_expression(field, result): r""" Return the symbolic expressions in result as a tuple - - This function can provided to ``find_region_type`` when setting up a :class:`SemialgebraicComplex`. + + This function can provided to ``find_region_type`` when setting up a :class:`SemialgebraicComplex`. In this way, one can compare result of type :class:`ParametricRealFieldElement` or list of :class:`ParametricRealFieldElement` with the previous elements in ``region_type_color_map`` which do not necessarily have the same parent. @@ -2728,7 +2731,7 @@ def result_symbolic_expression(field, result): sage: vol1 == vol2 False sage: sym_exp1 == sym_exp2 - True + True """ symbolic_expression = tuple(elt._sym if hasattr(elt, '_sym') else elt for elt in flatten([result])) return symbolic_expression @@ -2878,11 +2881,11 @@ def color_of_ith_region_type(i): def find_point_flip_ineq_heuristic(current_var_value, ineq, ineqs, flip_ineq_step): r""" - The current_var_value satisfies that l(current_var_value) <= 0 for l=ineq and for every l in ineqs, + The current_var_value satisfies that l(current_var_value) <= 0 for l=ineq and for every l in ineqs, where ineq is a polynomial and ineqs is a list of polynomials. Use heuristic method (gradient descent method with given small positive step length flip_ineq_step) - to find a new_point (type is tuple) such that + to find a new_point (type is tuple) such that ineq(new_point) > 0, l(new_point) < 0 for all l in ineqs Return new_point, or ``None`` if it fails to find one. @@ -3012,8 +3015,8 @@ def find_point_flip_ineq_heuristic(current_var_value, ineq, ineqs, flip_ineq_ste def adjust_pt_to_satisfy_ineqs(current_point, ineq, strict_ineqs, nonstrict_ineqs, flip_ineq_step): r""" - Walk from current_point (type=vector) in the direction perpendicular to - the gradient of ineq with small positive step length flip_ineq_step, + Walk from current_point (type=vector) in the direction perpendicular to + the gradient of ineq with small positive step length flip_ineq_step, while maintaining the value of ineq(*current_point) which is >= 0. until get a new point such that l(new point)<0 for all l in strict_ineqs and l(new point)<=0 for all l in nonstrict_ineqs @@ -3035,7 +3038,7 @@ def adjust_pt_to_satisfy_ineqs(current_point, ineq, strict_ineqs, nonstrict_ineq sage: pt = adjust_pt_to_satisfy_ineqs(vector([11/8, 7/8]), a+b-2, [-a+b^2], [a-1], 1/4); pt is None True - Bug example in cpl Cell 9 with test point (499/1250, 488072439572/4866126017667). Without converting input to QQ, output was (0.333000000000000, 0.111333333333333) with -2*f - 3*z + 1 = -5.55111512312578e-17 , the QQ of the output=(333/1000, 167/1500) has -2*f - 3*z + 1 == 0. Revise the code to take QQ input current point:: + Bug example in cpl Cell 9 with test point (499/1250, 488072439572/4866126017667). Without converting input to QQ, output was (0.333000000000000, 0.111333333333333) with -2*f - 3*z + 1 = -5.55111512312578e-17 , the QQ of the output=(333/1000, 167/1500) has -2*f - 3*z + 1 == 0. Revise the code to take QQ input current point:: sage: P.=QQ[] sage: current_point = vector([0.333000000000000, 0.100300000000000]); ineq = -3*f + 1; strict_ineqs = [2*f + 2*z - 1, f + 5*z - 1, -f - 6*z + 1, -2*f - 3*z + 1]; nonstrict_ineqs = []; flip_ineq_step = 1/1000 @@ -3054,8 +3057,8 @@ def adjust_pt_to_satisfy_ineqs(current_point, ineq, strict_ineqs, nonstrict_ineq Bug example in cpl bigcell 16 with test point (12219/26000, 24/1625). Redo with QQ had infinite loop. Bug comes from find_neighbour_point where it calls bsa_section.upstairs()._polyhedron.is_empty(), which is not strong enough. If we could test bsa_section is empty (perhaps by tighten_upstairs_by_mccormick), then this example should not appear:: - sage: P.=QQ[]; - sage: current_point = vector((71582788/143165577, 4673/377000)) # came from vector((RR(70727/150800), RR(4673/377000))), + sage: P.=QQ[]; + sage: current_point = vector((71582788/143165577, 4673/377000)) # came from vector((RR(70727/150800), RR(4673/377000))), sage: ineq=None; strict_ineqs=[2*f - 1, -9*f + 2]; nonstrict_ineqs=[4*f^2 - 4*f + 1]; flip_ineq_step=1/1000 sage: pt = adjust_pt_to_satisfy_ineqs(current_point, None, strict_ineqs, nonstrict_ineqs, flip_ineq_step=1/1000); pt is None #long time True From ab3871ab9d0375c9aa7297a05ec9d4f41826c4aa Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Mon, 29 Sep 2025 13:08:50 -0700 Subject: [PATCH 18/64] change backend to parametric real field --- .../igp/minimal_function_cache.sage | 46 +++++++------------ 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index 62b9be6e3..8abf347e4 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -4,10 +4,6 @@ from cutgeneratingfunctionology.igp import * import csv import os - -ppl_bsa = BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(0), polynomial_map=[], poly_ring=sym_ring, v_dict={}) -pplite_bsa = BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(0), polynomial_map=[], poly_ring=sym_ring, v_dict={}) - def mod_one(x): if x >= 0: return x - int(x) @@ -17,7 +13,7 @@ def add_breakpoint(bkpt): """ Given a breakpoint sequence, creates a list of breakpoint sequences of length one more such that each the breakpoint complex of each breakpoint sequence is not guaranteed to be isomorphic to any - other breakpoint sequence in the list. + other breakpoint sequence in the list. INPUT: Assume vector or list of n breakpoints with lambda_0=0 and lambda_i= 2) coord_names = [] bkpt_vals = bkpt vals = bkpt_vals[0:n] for i in range(0,n): coord_names.append('lambda'+str(i)) - K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, partial_test_point_mode=True, bsa=bsa) + K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, partial_test_point_mode=True, default_backend=backend) logging.disable(logging.INFO) K.gens()[0] == 0 for i in range(n-1): K.gens()[i] < K.gens()[i+1] - K.gens()[n-1] < 1 + K.gens()[n-1] < 1 h = piecewise_function_from_breakpoints_and_values([0] + K.gens()[0:n-1] + [1], [0]*(n+1), merge=False) for vert in generate_type_1_vertices_continuous(h, operator.ge, [0] + K.gens()[0:n-1] + [1]): vert @@ -153,11 +145,11 @@ class BreakpointComplexClassContainer: def write_data(self, output_file_name_style=None, max_rows=None): """ Writes representative element data to a `.csv` file with one column and rows of representative elements. - Optionally, write many `.csv` files with at `most max_rows` rows per file. + Optionally, write many `.csv` files with at `most max_rows` rows per file. Files are named output_file_file_name_style_filenumber.csv. The default output_file_name_style="bkpts_of_len_n". """ - # TODO: Future, support writing different types of data such as polyhedra data. + # TODO: Future, support writing different types of data such as polyhedra data. if output_file_name_style is None: file_name_base = "bkpts_of_len_{}".format(self._n) else: @@ -205,10 +197,6 @@ def assume_minimality(bkpt, f_index, backend=None): OUTPUT: (rep_bkpt, v), a pair of lists of length n with the described property. """ n = len(bkpt) - if backend is None: - bsa = ppl_bsa.copy() - if backend == "pplite": - bsa = pplite_bsa.copy() assert(n >= 2) assert(f_index >= 1) assert(f_index <= n - 1) @@ -220,10 +208,10 @@ def assume_minimality(bkpt, f_index, backend=None): for i in range(1,n): coord_names.append('gamma'+str(i)) logging.disable(logging.INFO) - K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, partial_test_point_mode=True, bsa=bsa) + K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, partial_test_point_mode=True, default_backend=backend) for i in range(n-1): K.gens()[i+n-1] <=1 - K.gens()[i+n-1] > 0 + K.gens()[i+n-1] > 0 h = piecewise_function_from_breakpoints_and_values([0] + K.gens()[0:n-1] + [1], [0] + K.gens()[n-1:2*n-2] + [0], merge=False) for vert in generate_type_1_vertices_continuous(h, operator.ge, [0] + K.gens()[0:n-1] + [1]): vert @@ -244,9 +232,9 @@ def assume_minimality(bkpt, f_index, backend=None): def bsa_of_rep_element(bkpt, vals): - """ + """ Given pi_(bkpt, vals) is {minimal, not minimal}, find BSA subset of R^(2n) such that (bkpt, vals) in BSA and for all p - in BSA, pi_p is {minimal, not minimal}. + in BSA, pi_p is {minimal, not minimal}. INPUT: (bkpt, vals) are lists or vectors of length n and bkpt is a proper breakpoints sequence and vals is the corresponding value parameters. @@ -269,7 +257,7 @@ def bsa_of_rep_element(bkpt, vals): def find_minimal_function_reps_from_bkpts(bkpts, backend=None): """ - Finds representative elements of minimal functions if they exist from the given breakpoint sequence. + Finds representative elements of minimal functions if they exist from the given breakpoint sequence. """ data = [] for bkpt in bkpts: @@ -289,7 +277,7 @@ class PiMinContContainer: >>> PiMin_at_most_4_breakpoints = PiMinContContainer(4) >>> all([minimality_test(pi) for PiMin_at_most_4_breakpoints.get_rep_functions()]) True - >>> + >>> """ def __init__(self, n, **kwrds): self._n = n @@ -319,12 +307,12 @@ class PiMinContContainer: bkpts = make_bkpts_with_len_n(self._n) self._data = find_minimal_function_reps_from_bkpts(bkpts, self._backend) - def __repr__(self): + def __repr__(self): return "Space of minimal functions with at most {} breakpoints parameterized by breakpoints and values using semialgebraic sets.".format(self._n) def get_semialgebraic_sets(self): for b, v in self._data: - yield bsa_of_rep_element(b, v) + yield bsa_of_rep_element(b, v) def get_rep_elems(self): for b, v in self._data: @@ -346,11 +334,11 @@ class PiMinContContainer: def write_data(self, output_file_name_style=None, max_rows=None): """ Writes representative element data to a `.csv` file with one column and rows of representative elements. - Optionally, write many `.csv` files with at `most max_rows` rows per file. + Optionally, write many `.csv` files with at `most max_rows` rows per file. Files are named output_file_file_name_style_filenumber.csv. The default output_file_name_style="Pi_Min_n". """ - # TODO: Future, support writing different types of data such as polyhedra data. + # TODO: Future, support writing different types of data such as polyhedra data. if output_file_name_style is None: file_name_base = "Pi_Min_{}".format(self._n) else: @@ -368,5 +356,3 @@ class PiMinContContainer: data_writer.writerow(self._data[max_row * file_number + row]) out_file.close() output_file = file_name_base[:-1]+"{}".format(file_number) - - From 49bdd71d14e3b447e0bebe7928ef04eb7d7c0084 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Mon, 29 Sep 2025 13:11:51 -0700 Subject: [PATCH 19/64] fix comparision --- cutgeneratingfunctionology/igp/parametric.sage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cutgeneratingfunctionology/igp/parametric.sage b/cutgeneratingfunctionology/igp/parametric.sage index ad7cefbe2..622d2380c 100644 --- a/cutgeneratingfunctionology/igp/parametric.sage +++ b/cutgeneratingfunctionology/igp/parametric.sage @@ -253,7 +253,7 @@ class ParametricRealField(Field): # do the computation of the polyhedron incrementally, # rather than first building a huge list and then in a second step processing it. # the upstairs polyhedron defined by all constraints in self._eq/lt_factor - if default_backend = "pplite": + if default_backend == "pplite": polyhedron = BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron(0) else: polyhedron = BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(0) From 1b68645a8afa445bfac0689c652dab7fbf73a5e5 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Mon, 29 Sep 2025 18:58:27 -0700 Subject: [PATCH 20/64] finish fixing tests --- .../spam/parametric_real_field_element.py | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/cutgeneratingfunctionology/spam/parametric_real_field_element.py b/cutgeneratingfunctionology/spam/parametric_real_field_element.py index b50c7d8d1..39c6375b0 100644 --- a/cutgeneratingfunctionology/spam/parametric_real_field_element.py +++ b/cutgeneratingfunctionology/spam/parametric_real_field_element.py @@ -84,6 +84,8 @@ def val(self): possible_val = self.parent()._partial_eval_factor(self._sym) if possible_val in possible_val.base_ring(): return possible_val + else: + raise FactorUndetermined def _richcmp_(left, right, op): r""" Examples for traditional cmp semantics:: @@ -132,7 +134,7 @@ def _richcmp_(left, right, op): raise TypeError("comparing elements from different fields") if left.parent()._big_cells: try: - result = richcmp(left.val(), right.val(), op) + result = richcmp((left-right).val(), 0, op) except FactorUndetermined: # Partial evauation is happen, assume the result is True. result = True if result: @@ -158,13 +160,40 @@ def _richcmp_(left, right, op): return result else: # Traditional cmp semantics. - if (left.val() == right.val()): - left.parent().assume_comparison(right, operator.eq, left) - elif (left.val() < right.val()): - left.parent().assume_comparison(left, operator.lt, right) + try: + expr_val = (left-right).val() + if( expr_val == 0): + left.parent().assume_comparison(right, operator.eq, left) + elif (expr_val < 0): + left.parent().assume_comparison(left, operator.lt, right) + else: + left.parent().assume_comparison(right, operator.lt, left) + return richcmp(left.val(), right.val(), op) + except FactorUndetermined: + result = True + if result: + true_op = op + else: + true_op = richcmp_op_negation(op) + # left.sym() - right.sym() may cancel denominators, but that is + # OK because _div_ makes sure that denominators are nonzero. + if true_op == op_LT: + left.parent().assume_comparison(left - right, operator.lt) + elif true_op == op_GT: + left.parent().assume_comparison(left - right, operator.gt) + elif true_op == op_EQ: + left.parent().assume_comparison(right - left, operator.eq) + elif true_op == op_LE: + left.parent().assume_comparison(left - right, operator.le) + elif true_op == op_GE: + left.parent().assume_comparison(left - right, operator.ge) + elif true_op == op_NE: + left.parent().assume_comparison(right - left, operator.ne) else: - left.parent().assume_comparison(right, operator.lt, left) - return richcmp(left.val(), right.val(), op) + raise ValueError("{} is not a valid richcmp operator".format(op)) + return True + + def __abs__(self): """ From d27845a0fbf4c4811c3fa649adea5cdc5b11deb7 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Tue, 30 Sep 2025 10:07:00 -0700 Subject: [PATCH 21/64] linting --- .../igp/parametric.sage | 146 +++++++++--------- .../spam/parametric_real_field_element.py | 18 +-- 2 files changed, 81 insertions(+), 83 deletions(-) diff --git a/cutgeneratingfunctionology/igp/parametric.sage b/cutgeneratingfunctionology/igp/parametric.sage index 631226716..4b0734922 100644 --- a/cutgeneratingfunctionology/igp/parametric.sage +++ b/cutgeneratingfunctionology/igp/parametric.sage @@ -37,12 +37,12 @@ def bigcellify_igp(): import cutgeneratingfunctionology.spam.big_cells as big_cells big_cells.bigcellify_module(igp) + ############################### # Parametric Real Number Field ############################### from cutgeneratingfunctionology.spam.parametric_real_field_element import ParametricRealFieldElement, is_parametric_element - from sage.rings.ring import Field import sage.rings.number_field.number_field_base as number_field_base from sage.structure.coerce_maps import CallableConvertMap @@ -51,12 +51,15 @@ from sage.structure.coerce_maps import CallableConvertMap class ParametricRealFieldFrozenError(ValueError): pass + class ParametricRealFieldInconsistencyError(ValueError): pass + class ParametricRealFieldRefinementError(ValueError): pass + from contextlib import contextmanager allow_refinement_default = True @@ -156,7 +159,7 @@ class ParametricRealField(Field): sage: f[0]*f[1] <= 4 True - Test-point free descriptions can be written which every comparison is assumed to be true. + Test-point free descriptions can be written which every comparison is assumed to be true. MUCH slower because of many more polynoial evaluations via libsingular:: sage: K. = ParametricRealField(None, mutable_values=True) @@ -205,7 +208,7 @@ class ParametricRealField(Field): self._big_cells = big_cells self._zero_element = ParametricRealFieldElement(self, 0) - self._one_element = ParametricRealFieldElement(self, 1) + self._one_element = ParametricRealFieldElement(self, 1) ## REFACTOR: Maybe replace this by an instance of BasicSemialgebraicSet_eq_lt_le_sets - but careful - this class right now assumes polynomials self._eq = set([]) self._lt = set([]) @@ -307,7 +310,7 @@ class ParametricRealField(Field): TypeError: unsupported operand parent(s)... Test that real number field elements can be upgraded to ``ParametricRealFieldElement``s. - Note that this requires setting up the ParametricRealField with a specific base ring, + Note that this requires setting up the ParametricRealField with a specific base ring, because there is no common parent of QQ(x) and a RealNumberField``:: sage: sqrt2, = nice_field_values([sqrt(2)]) @@ -319,7 +322,7 @@ class ParametricRealField(Field): """ if isinstance(S, ParametricRealField) and self is not S: return None - if S is sage.interfaces.mathematica.MathematicaElement or isinstance(S, RealNumberField_absolute) or isinstance(S, RealNumberField_quadratic) or AA.has_coerce_map_from(S): + if S is sage.interfaces.mathematica.MathematicaElement or isinstance(S, RealNumberField_absolute) or isinstance(S, RealNumberField_quadratic) or AA.has_coerce_map_from(S): # Does the test with MathematicaElement actually work? # We test whether S coerces into AA. This rules out inexact fields such as RDF. return True @@ -555,29 +558,16 @@ class ParametricRealField(Field): except TypeError: # 'None' components pass raise FactorUndetermined("{} cannot be evaluated because the test point is not complete".format(fac)) - + def _partial_eval_factor(self, fac): """ Partially evaluate ``fac`` on the test point. - + This function is only intended to be called after ``FactorUndetermined`` is raised from ``_eval_factor``. """ val_dict = {sym:val for sym, val in zip(fac.parent().gens() , self._values) if val is not None} return fac.subs(val_dict) -# Returns a symbolic expression or raises an ``EvaluationSuccessfulFlag``. -# Receiving an ``EvaluationSuccessfulFlag`` means ``fac`` can be evaluated with the known values of the -# test point. -# base_ring = self._sym_field.base_ring() -# if fac in base_ring: -# raise EvaluationSuccessfulFlag("{} can be evaluated in the base_ring. Use _eval_factor instead.".format(fac)) -# try: -# fac(self._values) -# raise EvaluationSuccessfulFlag("{} can be evaluated with the test point. Use _eval_factor instead.".format(fac)) -# except TypeError: -# val_dict = {sym:val for sym, val zip([symb.sym() for symb in self._gens], self._values) if val is not None} -# return fac.subs(val_dict) - def _factor_sign(self, fac): """ Determine the sign of ``fac`` evaluated on the test point. @@ -870,7 +860,7 @@ class ParametricRealField(Field): else: raise ParametricRealFieldInconsistencyError("New constraint {} {} {} is not satisfied by the test point".format(lhs, op, rhs)) else: #A numerical evaluation of the expression has failed. Assume the partial evaluation of the expression holds. - comparison_val_or_expr = self._partial_eval_factor(comparison) + comparison_val_or_expr = self._partial_eval_factor(comparison) if comparison_val_or_expr in base_ring: if not op(comparison_val_or_expr, 0): #The partial evaluation is ture, means raise ParametricRealFieldInconsistencyError("New constant constraint {} {} {} is not satisfied".format(lhs, op, rhs)) @@ -906,7 +896,8 @@ class ParametricRealField(Field): return if len(factors) == 1 and factors[0][1] == 1 and comparison_val is not None: the_fac, d = factors[0] - the_sign = sign(factors.unit() * comparison_val) + the_sign = sign(factors.unit() * comparison_val) + def factor_sign(fac): if fac == the_fac: return the_sign @@ -1130,7 +1121,7 @@ class ParametricRealField(Field): def make_proof_cell(self, **opt): r""" Make a :class:`SemialgebraicComplexComponent` from a :class:`ParametricRealField`. - + In **opt, one can provide: region_type, function, find_region_type, default_var_bound, bddbsa, kwds_dict. EXAMPLES:: @@ -1232,8 +1223,10 @@ def find_polynomial_map(eqs=[], poly_ring=None): # Functions with ParametricRealField K ###################################### + from sage.misc.sageinspect import sage_getargspec, sage_getvariablename + def read_default_args(function, **opt_non_default): r""" Return the default values of arguments of the function. @@ -1261,7 +1254,7 @@ def read_default_args(function, **opt_non_default): default_args = {} if defaults is not None: for i in range(len(defaults)): - default_args[args[-i-1]]=defaults[-i-1] + default_args[args[-i-1]] = defaults[-i-1] for (opt_name, opt_value) in opt_non_default.items(): if opt_name in default_args: default_args[opt_name] = opt_value @@ -1422,7 +1415,7 @@ class SemialgebraicComplexComponent(SageObject): # FIXME: Rename this to be m else: ptcolor = 'white' if (xmin <= pt[0] <= xmax) and (ymin <= pt[1] <= ymax): - g += point(pt, color = ptcolor, size = 2, zorder=10) + g += point(pt, color=ptcolor, size=2, zorder=10) return g def find_neighbour_candidates(self, flip_ineq_step, wall_crossing_method='heuristic', goto_lower_dim=False, pos_poly=None): @@ -1469,7 +1462,7 @@ class SemialgebraicComplexComponent(SageObject): # FIXME: Rename this to be m pt = find_point_flip_ineq_heuristic(self.var_value, l, list(bsa.lt_poly())+list(bsa.le_poly()), flip_ineq_step) if pt is not None: # Find a new point, use polynomial map to recover the values of those eliminated variables. - pt_across_wall = tuple(p(pt) for p in self.polynomial_map) + pt_across_wall = tuple(p(pt) for p in self.polynomial_map) if wall_crossing_method == 'mathematica' or wall_crossing_method == 'heuristic_then_mathematica' and (pt_across_wall is None): bsa_mathematica = bsa.formal_relint(bsa_class='mathematica') # was BasicSemialgebraicSet_mathematica.from_bsa(bsa) bsa_mathematica.add_polynomial_constraint(l, operator.gt) @@ -1556,7 +1549,9 @@ class SemialgebraicComplexComponent(SageObject): # FIXME: Rename this to be m if bsa_section.upstairs()._polyhedron.is_empty(): has_pt_on_wall = False else: - lts = []; les = []; eqs = [] + lts = [] + les = [] + eqs = [] for ll in list(bsa_section.lt_poly()): factors = ll.factor() if len(factors) == 1: @@ -1684,7 +1679,7 @@ class ProofCell(SemialgebraicComplexComponent, Classcall): sage: C_copy._init_args == C._init_args True - (We do not test for equality C_copy == C -- we have not even decided yet what the semantics + (We do not test for equality C_copy == C -- we have not even decided yet what the semantics of equality of bsa is.) """ @@ -1794,8 +1789,10 @@ class ProofCell(SemialgebraicComplexComponent, Classcall): super(ProofCell, self).__init__(K, region_type, bddbsa, polynomial_map) self.family = family + from collections import OrderedDict + class SemialgebraicComplex(SageObject): r""" A proof complex for parameter space analysis. @@ -1829,7 +1826,7 @@ class SemialgebraicComplex(SageObject): sage: complex.is_complete() # optional - mathematica True - + Example with non-linear wall:: sage: complex = SemialgebraicComplex(lambda x,y: max(x,y^2), ['x','y'], find_region_type=result_symbolic_expression, default_var_bound=(-3,3)) # optional - mathematica @@ -1924,7 +1921,7 @@ class SemialgebraicComplex(SageObject): to a "type" of the parameter region, for example: - :func:`find_region_type_igp` (the default). The result of the computation is a Gomory-Johnson - function `h`; it is passed to :func:`find_region_type_igp` as 2nd arg, + function `h`; it is passed to :func:`find_region_type_igp` as 2nd arg, and then :func:`find_region_type_igp`which classifies the region of the parameter by returning one of the strings ``'is_constructible'``, ``'not_constructible'``, @@ -2003,7 +2000,7 @@ class SemialgebraicComplex(SageObject): r""" Return a random point that satisfies var_bounds and is in self.bsa. - - If var_bounds is not specified, self.default_var_bound is taken. + - If var_bounds is not specified, self.default_var_bound is taken. - var_bounds can be a list of 2-tuples whose length equals to the number of parameters, or lambda functions. - It is used in random shooting method for functions like ``dg_2_step_mir``, which involve floor/ceil operations. We try to plot one layer for each n = floor(...) and superimpose the layers at the end to get the whole picture. @@ -2029,11 +2026,11 @@ class SemialgebraicComplex(SageObject): x = QQ(uniform(self.default_var_bound[0], self.default_var_bound[1])) else: if hasattr(var_bounds[i][0], '__call__'): - l = var_bounds[i][0](*var_value) + l = var_bounds[i][0](*var_value) else: l = var_bounds[i][0] if hasattr(var_bounds[i][1], '__call__'): - u = var_bounds[i][1](*var_value) + u = var_bounds[i][1](*var_value) else: u = var_bounds[i][1] if l > u: @@ -2094,12 +2091,12 @@ class SemialgebraicComplex(SageObject): for c in self.components: if var_value in c.bsa: yield c - + def find_uncovered_random_point(self, var_bounds=None, max_failings=10000): r""" Return a random point that satisfies the bounds and is uncovered by any cells in the complex. Return ``None`` if the number of attemps > max_failings. - + EXAMPLES:: sage: from cutgeneratingfunctionology.igp import * @@ -2128,7 +2125,7 @@ class SemialgebraicComplex(SageObject): The argument formal_closure whether inequalities are treated as <= 0 or as < 0. If such point does not exist, return ``None``. - + EXAMPLES:: sage: from cutgeneratingfunctionology.igp import * @@ -2205,18 +2202,18 @@ class SemialgebraicComplex(SageObject): self.points_to_test[num_eq] = OrderedDict() if not num_eq in self.tested_points: self.tested_points[num_eq] = set([]) - new_component = self._cell_class(self.family, var_value, + new_component = self._cell_class(self.family, var_value, find_region_type=self.find_region_type, bddbsa=bddbsa, polynomial_map=polynomial_map) new_num_eq = len(list(new_component.bsa.eq_poly())) - if new_num_eq > num_eq: - logging.warning("The cell around %s defined by %s has more equations than boundary %s" %(new_component.var_value, new_component.bsa, bddbsa)) + if new_num_eq > num_eq: + logging.warning("The cell around %s defined by %s has more equations than boundary %s" % (new_component.var_value, new_component.bsa, bddbsa)) #import pdb; pdb.set_trace() - # bsa is lower dimensional as it has more equations than bddbsa, + # bsa is lower dimensional as it has more equations than bddbsa, # so we try to perturb the testpoint to obtain a # new testpoint in bddbsa that does not fall into a lower dimensional cell. # Heuristic code using gradient desecent. #FIXME. - for l in (set(new_component.bsa.eq_poly())- set(bddbsa.eq_poly())): + for l in (set(new_component.bsa.eq_poly()) - set(bddbsa.eq_poly())): ineqs = list(new_component.bddbsa.lt_poly())+list(new_component.bddbsa.le_poly()) pts = [find_point_flip_ineq_heuristic(var_value, l, ineqs, 1/2017), find_point_flip_ineq_heuristic(var_value, -l, ineqs, 1/2017)] for pt in pts: @@ -2277,7 +2274,7 @@ class SemialgebraicComplex(SageObject): Plot the complex and store the graph. - If restart is ``False``, plot the newly added cells on top of the last graph; otherwise, start a new graph. - - If slice_value is given, it is either a polynomial_map that defines a section, or a list of fixed parameter values with two of them being None. Plot the section. + - If slice_value is given, it is either a polynomial_map that defines a section, or a list of fixed parameter values with two of them being None. Plot the section. - plot_points controls the quality of the plotting. EXAMPLES:: @@ -2333,7 +2330,7 @@ class SemialgebraicComplex(SageObject): # # FIXME: zorder is broken in region_plot/ContourPlot. # for c in self.components[self.num_plotted_components::]: # num_eq = len(list(c.bsa.eq_poly())) - # gc = c.plot(alpha=alpha, plot_points=plot_points, slice_value=slice_value, default_var_bound=self.default_var_bound, goto_lower_dim=goto_lower_dim, zorder=num_eq, **kwds) + # gc = c.plot(alpha=alpha, plot_points=plot_points, slice_value=slice_value, default_var_bound=self.default_var_bound, goto_lower_dim=goto_lower_dim, zorder=num_eq, **kwds) # if gc: # need this because (empty g + empty gc) forgets about xmin xmax ymin ymax. # self.graph += gc # Workaround. @@ -2355,11 +2352,11 @@ class SemialgebraicComplex(SageObject): new_bsa.add_polynomial_constraint(l, operator.eq) self.graph += new_bsa.plot(alpha=alpha, plot_points=plot_points, slice_value=slice_value, color=color, fill_color=color, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax) for c in components: - if len(list(c.bsa.eq_poly()))==1: + if len(list(c.bsa.eq_poly())) == 1: self.graph += c.plot(alpha=alpha, plot_points=plot_points, slice_value=slice_value, default_var_bound=self.default_var_bound, goto_lower_dim=False, zorder=0, **kwds) if goto_lower_dim: for c in components: - if len(list(c.bsa.eq_poly()))==1: + if len(list(c.bsa.eq_poly())) == 1: color = find_region_color(c.region_type) for l in c.bsa.lt_poly(): new_bsa = BasicSemialgebraicSet_eq_lt_le_sets(eq=list(c.bsa.eq_poly())+[l], lt=[ll for ll in c.bsa.lt_poly() if ll != l], le=list(c.bsa.le_poly())) @@ -2369,9 +2366,9 @@ class SemialgebraicComplex(SageObject): new_bsa.add_polynomial_constraint(l, operator.eq) self.graph += new_bsa.plot(alpha=alpha, plot_points=plot_points, slice_value=slice_value, color=color, fill_color=color, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax) for c in components: - if len(list(c.bsa.eq_poly()))==2: + if len(list(c.bsa.eq_poly())) == 2: ptcolor = find_region_color(c.region_type) - self.graph += point(c.var_value, color = ptcolor, zorder=10) + self.graph += point(c.var_value, color=ptcolor, zorder=10) self.num_plotted_components = len(self.components) return self.graph @@ -2408,7 +2405,7 @@ class SemialgebraicComplex(SageObject): - var_value: the starting point. If not given, start with a random point. - flip_ineq_step: a small positive number that controls the step length in wall-crossing. - check_completion: When check_completion is ``True``, after bfs terminates, check whether the entire parameter space is covered by cells, using Mathematica's ``FindInstance`` (if max_failings=0) or random shooting (if max_failings>0). If an uncovered point has been found, restart the bfs from this point. - - wall_crossing_method: 'mathematica' or 'heuristic' or 'heuristic_then_mathematica' + - wall_crossing_method: 'mathematica' or 'heuristic' or 'heuristic_then_mathematica' - wall_crossing_method='heuristic_then_mathematica': try heuristic method first. If it does not find a new testpoint, then use Mathematica. - goto_lower_dim: whether lower dimensional cells are considered. If goto_lower_dim is ``False`` or if goto_lower_dim is ``True`` and wall_crossing method is 'heuristic' but the wall is non-linear, then find new testpoint across the wall only. @@ -2462,10 +2459,10 @@ class SemialgebraicComplex(SageObject): uncovered_pt = self.find_uncovered_random_point(max_failings=max_failings) if uncovered_pt is not None: logging.warning("After bfs, the complex has uncovered point %s." % (uncovered_pt,)) - self.bfs_completion(var_value=uncovered_pt, \ - flip_ineq_step=flip_ineq_step, \ - check_completion=check_completion, \ - wall_crossing_method=wall_crossing_method, \ + self.bfs_completion(var_value=uncovered_pt, + flip_ineq_step=flip_ineq_step, + check_completion=check_completion, + wall_crossing_method=wall_crossing_method, goto_lower_dim=goto_lower_dim) def is_complete(self, formal_closure=False): @@ -2567,9 +2564,9 @@ def gradient(ineq): [3*z^2 + 6*z] """ if hasattr(ineq, 'gradient'): - return ineq.gradient() + return ineq.gradient() else: - return [ineq.derivative()] + return [ineq.derivative()] #################################### # Find region type and region color @@ -2702,8 +2699,8 @@ def return_result(field, result): def result_concrete_value(field, result): r""" Return the concrete values in result as a tuple. See also ``result_symbolic_expression()``. - - This function can provided to ``find_region_type`` when setting up a :class:`SemialgebraicComplex`. + + This function can provided to ``find_region_type`` when setting up a :class:`SemialgebraicComplex`. In this way, one can compare result of type :class:`ParametricRealFieldElement` or list of :class:`ParametricRealFieldElement` with the previous elements in ``region_type_color_map`` which do not necessarily have the same parent. @@ -2725,8 +2722,8 @@ def result_concrete_value(field, result): def result_symbolic_expression(field, result): r""" Return the symbolic expressions in result as a tuple - - This function can provided to ``find_region_type`` when setting up a :class:`SemialgebraicComplex`. + + This function can provided to ``find_region_type`` when setting up a :class:`SemialgebraicComplex`. In this way, one can compare result of type :class:`ParametricRealFieldElement` or list of :class:`ParametricRealFieldElement` with the previous elements in ``region_type_color_map`` which do not necessarily have the same parent. @@ -2750,7 +2747,7 @@ def result_symbolic_expression(field, result): sage: vol1 == vol2 False sage: sym_exp1 == sym_exp2 - True + True """ symbolic_expression = tuple(elt._sym if hasattr(elt, '_sym') else elt for elt in flatten([result])) return symbolic_expression @@ -2775,7 +2772,7 @@ def find_region_type_igp_extreme_big_cells(K, h): h = copy(hcopy) for x in h.values_at_end_points(): if (x < 0) or (x > 1): - is_extreme = False + is_extreme = False break if not is_extreme: assert (x < 0) or (x > 1) @@ -2821,7 +2818,7 @@ def find_region_type_igp_extreme_big_cells(K, h): ucs = generate_uncovered_components(h) f = find_f(h) for uncovered_pt in [f/2, (f+1)/2]: - if any((i[0] == uncovered_pt or i[1] == uncovered_pt) for uc in ucs for i in uc if len(uc)==2): + if any((i[0] == uncovered_pt or i[1] == uncovered_pt) for uc in ucs for i in uc if len(uc) == 2): uncovered_pts = [uncovered_pt] is_extreme = False break @@ -2855,7 +2852,8 @@ def find_region_type_igp_extreme_big_cells(K, h): return False return True -region_type_color_map = [('not_constructible', 'lightgrey'), ('is_constructible', 'black'), ('not_minimal', 'orange'), ('is_minimal', 'darkgrey'),('not_extreme', 'green'), ('is_extreme', 'blue'), ('stop', 'grey'), (True, 'blue'), (False, 'red'), ('constructible', 'darkgrey'), ('extreme', 'red')] + +region_type_color_map = [('not_constructible', 'lightgrey'), ('is_constructible', 'black'), ('not_minimal', 'orange'), ('is_minimal', 'darkgrey'), ('not_extreme', 'green'), ('is_extreme', 'blue'), ('stop', 'grey'), (True, 'blue'), (False, 'red'), ('constructible', 'darkgrey'), ('extreme', 'red')] def find_region_color(region_type): r""" @@ -2900,11 +2898,11 @@ def color_of_ith_region_type(i): def find_point_flip_ineq_heuristic(current_var_value, ineq, ineqs, flip_ineq_step): r""" - The current_var_value satisfies that l(current_var_value) <= 0 for l=ineq and for every l in ineqs, + The current_var_value satisfies that l(current_var_value) <= 0 for l=ineq and for every l in ineqs, where ineq is a polynomial and ineqs is a list of polynomials. Use heuristic method (gradient descent method with given small positive step length flip_ineq_step) - to find a new_point (type is tuple) such that + to find a new_point (type is tuple) such that ineq(new_point) > 0, l(new_point) < 0 for all l in ineqs Return new_point, or ``None`` if it fails to find one. @@ -3010,7 +3008,7 @@ def find_point_flip_ineq_heuristic(current_var_value, ineq, ineqs, flip_ineq_ste ineq_value = ineq(*current_point) try_before_fail -= 1 # print (current_point, RR(ineq_value)) - try_before_fail = max(ceil(2/flip_ineq_step), 2000) # define maximum number of walks. Considered ceil(-2 * ineq_value /flip_ineq_step) but it is too slow in the impossible cases. Added a loop with 2000 times step length when ineq_value is very negative. + try_before_fail = max(ceil(2/flip_ineq_step), 2000) # define maximum number of walks. Considered ceil(-2 * ineq_value /flip_ineq_step) but it is too slow in the impossible cases. Added a loop with 2000 times step length when ineq_value is very negative. while (ineq_value <= 1e-10) and (try_before_fail > 0): ineq_direction = vector(g(*current_point) for g in ineq_gradient) if ineq.degree() == 1: @@ -3034,8 +3032,8 @@ def find_point_flip_ineq_heuristic(current_var_value, ineq, ineqs, flip_ineq_ste def adjust_pt_to_satisfy_ineqs(current_point, ineq, strict_ineqs, nonstrict_ineqs, flip_ineq_step): r""" - Walk from current_point (type=vector) in the direction perpendicular to - the gradient of ineq with small positive step length flip_ineq_step, + Walk from current_point (type=vector) in the direction perpendicular to + the gradient of ineq with small positive step length flip_ineq_step, while maintaining the value of ineq(*current_point) which is >= 0. until get a new point such that l(new point)<0 for all l in strict_ineqs and l(new point)<=0 for all l in nonstrict_ineqs @@ -3057,7 +3055,7 @@ def adjust_pt_to_satisfy_ineqs(current_point, ineq, strict_ineqs, nonstrict_ineq sage: pt = adjust_pt_to_satisfy_ineqs(vector([11/8, 7/8]), a+b-2, [-a+b^2], [a-1], 1/4); pt is None True - Bug example in cpl Cell 9 with test point (499/1250, 488072439572/4866126017667). Without converting input to QQ, output was (0.333000000000000, 0.111333333333333) with -2*f - 3*z + 1 = -5.55111512312578e-17 , the QQ of the output=(333/1000, 167/1500) has -2*f - 3*z + 1 == 0. Revise the code to take QQ input current point:: + Bug example in cpl Cell 9 with test point (499/1250, 488072439572/4866126017667). Without converting input to QQ, output was (0.333000000000000, 0.111333333333333) with -2*f - 3*z + 1 = -5.55111512312578e-17 , the QQ of the output=(333/1000, 167/1500) has -2*f - 3*z + 1 == 0. Revise the code to take QQ input current point:: sage: P.=QQ[] sage: current_point = vector([0.333000000000000, 0.100300000000000]); ineq = -3*f + 1; strict_ineqs = [2*f + 2*z - 1, f + 5*z - 1, -f - 6*z + 1, -2*f - 3*z + 1]; nonstrict_ineqs = []; flip_ineq_step = 1/1000 @@ -3076,8 +3074,8 @@ def adjust_pt_to_satisfy_ineqs(current_point, ineq, strict_ineqs, nonstrict_ineq Bug example in cpl bigcell 16 with test point (12219/26000, 24/1625). Redo with QQ had infinite loop. Bug comes from find_neighbour_point where it calls bsa_section.upstairs()._polyhedron.is_empty(), which is not strong enough. If we could test bsa_section is empty (perhaps by tighten_upstairs_by_mccormick), then this example should not appear:: - sage: P.=QQ[]; - sage: current_point = vector((71582788/143165577, 4673/377000)) # came from vector((RR(70727/150800), RR(4673/377000))), + sage: P.=QQ[]; + sage: current_point = vector((71582788/143165577, 4673/377000)) # came from vector((RR(70727/150800), RR(4673/377000))), sage: ineq=None; strict_ineqs=[2*f - 1, -9*f + 2]; nonstrict_ineqs=[4*f^2 - 4*f + 1]; flip_ineq_step=1/1000 sage: pt = adjust_pt_to_satisfy_ineqs(current_point, None, strict_ineqs, nonstrict_ineqs, flip_ineq_step=1/1000); pt is None #long time True @@ -3092,7 +3090,7 @@ def adjust_pt_to_satisfy_ineqs(current_point, ineq, strict_ineqs, nonstrict_ineq #current_point is a vector if ineq is not None: ineq_gradient = gradient(ineq) - if all(x.parent()==QQ for x in current_point): + if all(x.parent() == QQ for x in current_point): max_walks = min(ceil(2/flip_ineq_step), 20) else: max_walks = min(ceil(2/flip_ineq_step), 200) #1000? # define maximum number of walks. @@ -3154,7 +3152,7 @@ def adjust_pt_to_satisfy_ineqs(current_point, ineq, strict_ineqs, nonstrict_ineq return None if ineq is not None and ineq(*current_point) < 0: return None - if all(x.parent()==QQ for x in current_point): + if all(x.parent() == QQ for x in current_point): return tuple(current_point) else: prec = 30 # We hope to have small denominator for the new point, so we set precision in bits = 30 is about 8 digits. @@ -3215,13 +3213,14 @@ def embed_function_into_family(given_function, parametric_family, check_completi var_name = [] var_value = [] for (name, value) in default_args.items(): - if not isinstance(value, bool) and not value is None: + if not isinstance(value, bool) and value is not None: try: RR(value) var_name.append(name) var_value.append(value) except: pass + def frt(K, h): if h is None: return False @@ -3264,6 +3263,7 @@ def embed_function_into_family(given_function, parametric_family, check_completi # plot_cpl_components(complex.components) return {} + """ EXAMPLES:: diff --git a/cutgeneratingfunctionology/spam/parametric_real_field_element.py b/cutgeneratingfunctionology/spam/parametric_real_field_element.py index 39c6375b0..8c6e4c831 100644 --- a/cutgeneratingfunctionology/spam/parametric_real_field_element.py +++ b/cutgeneratingfunctionology/spam/parametric_real_field_element.py @@ -12,6 +12,7 @@ from cutgeneratingfunctionology.shared.EvaluationExceptions import FactorUndetermined import operator + def richcmp_op_negation(op): if op == op_LT: return op_GE @@ -28,6 +29,7 @@ def richcmp_op_negation(op): else: raise ValueError("{} is not a valid richcmp operator".format(op)) + def format_richcmp_op(op): if op == op_LT: return '<' @@ -44,6 +46,7 @@ def format_richcmp_op(op): else: raise ValueError("{} is not a valid richcmp operator".format(op)) + class ParametricRealFieldElement(FieldElement): r""" A :class:`ParametricRealFieldElement` stores a symbolic expression of the parameters in the problem and a concrete value, which is the evaluation of this expression on the given parameter tuple. @@ -85,7 +88,8 @@ def val(self): if possible_val in possible_val.base_ring(): return possible_val else: - raise FactorUndetermined + raise FactorUndetermined.("{} cannot be evaluated because the test point is not complete".format(self.sym())) + def _richcmp_(left, right, op): r""" Examples for traditional cmp semantics:: @@ -135,7 +139,7 @@ def _richcmp_(left, right, op): if left.parent()._big_cells: try: result = richcmp((left-right).val(), 0, op) - except FactorUndetermined: # Partial evauation is happen, assume the result is True. + except FactorUndetermined: # Partial evaluation has happen, assume the result is True. result = True if result: true_op = op @@ -170,13 +174,8 @@ def _richcmp_(left, right, op): left.parent().assume_comparison(right, operator.lt, left) return richcmp(left.val(), right.val(), op) except FactorUndetermined: - result = True - if result: + # With a partial evaluation, assume the written inequality is true. true_op = op - else: - true_op = richcmp_op_negation(op) - # left.sym() - right.sym() may cancel denominators, but that is - # OK because _div_ makes sure that denominators are nonzero. if true_op == op_LT: left.parent().assume_comparison(left - right, operator.lt) elif true_op == op_GT: @@ -193,8 +192,6 @@ def _richcmp_(left, right, op): raise ValueError("{} is not a valid richcmp operator".format(op)) return True - - def __abs__(self): """ Examples for traditional cmp semantics:: @@ -436,6 +433,7 @@ def __hash__(self): """ return hash(self.val()) + def is_parametric_element(x): # We avoid using isinstance here so that this is robust even if parametric.sage is reloaded. # For example, apparently in the test suite. From bd95f5081ba054673db79d1db91a70bbf9a86db5 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Tue, 30 Sep 2025 10:18:16 -0700 Subject: [PATCH 22/64] spelling fixes --- .../igp/parametric.sage | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/cutgeneratingfunctionology/igp/parametric.sage b/cutgeneratingfunctionology/igp/parametric.sage index 4b0734922..a8b0f9aa3 100644 --- a/cutgeneratingfunctionology/igp/parametric.sage +++ b/cutgeneratingfunctionology/igp/parametric.sage @@ -862,9 +862,9 @@ class ParametricRealField(Field): else: #A numerical evaluation of the expression has failed. Assume the partial evaluation of the expression holds. comparison_val_or_expr = self._partial_eval_factor(comparison) if comparison_val_or_expr in base_ring: - if not op(comparison_val_or_expr, 0): #The partial evaluation is ture, means + if not op(comparison_val_or_expr, 0): raise ParametricRealFieldInconsistencyError("New constant constraint {} {} {} is not satisfied".format(lhs, op, rhs)) - else: # comparision_val_or_expr is algebraic expresion, asume the comparision here is comparionsion_val_or_expr + else: # comparision_val_or_expr is algebraic expression, assume the comparison here is comparionsion_val_or_expr. comparison = comparison_val_or_expr if comparison in base_ring: return @@ -1161,7 +1161,7 @@ class ParametricRealField(Field): ############################### def find_polynomial_map(eqs=[], poly_ring=None): """ - BAD FUCNTION! It is used in 'mathematica' approach for non-linear case. Can we avoid it? + BAD FUNCTION! It is used in 'mathematica' approach for non-linear case. Can we avoid it? Return a polynomial map that eliminates linear variables in eqs, and a dictionary recording which equations were used to eliminate those linear variables. Assume that gaussian elimination has been performed by PPL.minimized_constraints() on the input list of equations eqs. It is only called in SemialgebraicComplex.add_new_component in the case polynomial_map is not provided but bddbsa has equations. @@ -1313,7 +1313,7 @@ class SemialgebraicComplexComponent(SageObject): # FIXME: Rename this to be m sage: sorted(component.bsa.lt_poly()) [-x, 3*x - 4] - In ProofCell region_type should alreay consider the polynomial_map:: + In ProofCell region_type should already consider the polynomial_map:: sage: K. = ParametricRealField([1,1/2]) sage: assert(x == 2*y) @@ -1344,10 +1344,14 @@ class SemialgebraicComplexComponent(SageObject): # FIXME: Rename this to be m # In lower dim proof cell or non-linear equations case, some equations of K._bsa are not presented in polynomial_map. eqs = list(K._bsa.eq_poly()) if not all(l(polynomial_map) == 0 for l in eqs): - polynomial_map = find_polynomial_map(eqs=eqs, poly_ring=poly_ring) + polynomial_map = find_polynomial_map(eqs=eqs, poly_ring=poly_ring)fv #self.bsa = K._bsa.section(polynomial_map, bsa_class='veronese', poly_ring=poly_ring) # this is a bigger_bsa self.bsa = BasicSemialgebraicSet_veronese.from_bsa(BasicSemialgebraicSet_local(K._bsa.section(polynomial_map, poly_ring=poly_ring), self.var_value)) # TODO:, polynomial_map=list(poly_ring.gens())) - # WHY is this input polynomial_map sometimes not compatible with the variable elimination done in bddbsa? Because upstairs ppl bsa eliminates large x_i in the inequalities, and x_i doesn't necessarily correspond to the i-th variable in poly_ring. Since polynomial_map and v_dict were not given at the initialization of veronese, the variable first encounted in the constraints is considered as x0 by upstairs ppl bsa. # In old code, we fixed the order of upstairs variables by adding initial space dimensions. We don't do that in the current code. Instead, we take the section of bddbsa to eliminate the varibles in the equations. # Is the given bddbsa required to be veronese with upstairs being ppl_bsa? Convert it anyway. # It's the same as BasicSemialgebraicSet_veronese.from_bsa(bddbsa.section(self.polynomial_map), poly_ring=poly_ring) + # WHY is this input polynomial_map sometimes not compatible with the variable elimination done in bddbsa? + # Because upstairs ppl bsa eliminates large x_i in the inequalities, and x_i doesn't necessarily correspond to the i-th variable in poly_ring. + # Since polynomial_map and v_dict were not given at the initialization of veronese, the variable first encountered, in the constraints is considered as x0 by upstairs ppl bsa. + # In old code, we fixed the order of upstairs variables by adding initial space dimensions. We don't do that in the current code. Instead, we take the section of bddbsa to eliminate the variables in the equations. + # Is the given bddbsa required to be veronese with upstairs being ppl_bsa? Convert it anyway. # It's the same as BasicSemialgebraicSet_veronese.from_bsa(bddbsa.section(self.polynomial_map), poly_ring=poly_ring) self.bddbsa = BasicSemialgebraicSet_veronese.from_bsa(BasicSemialgebraicSet_local(bddbsa.section(polynomial_map, poly_ring=poly_ring), self.var_value)) # Taking section forgets the equations. Then add back the equations # Finally self.bsa should be the same as K._bsa, but its inequalities don't have variables eliminated by polynomial map, so that heuristic wall crossing can be done later. for i in range(len(self.var_name)): @@ -1427,13 +1431,13 @@ class SemialgebraicComplexComponent(SageObject): # FIXME: Rename this to be m - if goto_lower_dim=False, the cell is considered as its formal closure, so no recursion into test points in lower dimensional cells. - pos_poly is a polynomial. The return test point must satisfy pos_poly(new test point) > 0. - OUTPUT new_points is a dictionary of dictionaries. The new_points[i] is a dictionay whose keys = candidate neighbour testpoints, values = (bddbsa whose eq_poly has i elements, polynomial_map, no_crossing_l) of the candidate neighbour cell that contains the candidate neighbour testpoint. bddbsa is recorded so that (1) complex.bddbsa is always respected; and (2) can recursively go into lower dimensional cells. polynomial_map is recorded and passed to the constructor of the neighbour cell. no_crossing is passed to the neighour cell for its find_neighbour_candidates method. We no longer update self.bsa by removing (obvious) redundant eq, lt, le constraints from its description at the end, even when 'mathematica' is used. + OUTPUT new_points is a dictionary of dictionaries. The new_points[i] is a dictionary whose keys = candidate neighbour testpoints, values = (bddbsa whose eq_poly has i elements, polynomial_map, no_crossing_l) of the candidate neighbour cell that contains the candidate neighbour testpoint. bddbsa is recorded so that (1) complex.bddbsa is always respected; and (2) can recursively go into lower dimensional cells. polynomial_map is recorded and passed to the constructor of the neighbour cell. no_crossing is passed to the neighbour cell for its find_neighbour_candidates method. We no longer update self.bsa by removing (obvious) redundant eq, lt, le constraints from its description at the end, even when 'mathematica' is used. """ bsa_eq_poly = list(self.bsa.eq_poly()) bsa_le_poly = list(self.bsa.le_poly()) bsa_lt_poly = list(self.bsa.lt_poly()) num_eq = len(bsa_eq_poly) #was len(list(self.bddbsa.eq_poly())) - new_points = {} #dictionary with key=num_eq, value=dictionay of pt: (bddbsa, polynomial_map). + new_points = {} #dictionary with key=num_eq, value=dictionary of pt: (bddbsa, polynomial_map). #bddbsa = copy(self.bddbsa) #for l in bsa_eq_poly: # should be already in bddbsa # bddbsa.add_polynomial_constraint(l, operator.eq) @@ -2095,7 +2099,7 @@ class SemialgebraicComplex(SageObject): def find_uncovered_random_point(self, var_bounds=None, max_failings=10000): r""" Return a random point that satisfies the bounds and is uncovered by any cells in the complex. - Return ``None`` if the number of attemps > max_failings. + Return ``None`` if the number of attempts > max_failings. EXAMPLES:: @@ -2311,7 +2315,7 @@ class SemialgebraicComplex(SageObject): self.graph.xmin(kwds['xmin']) xmin = kwds['xmin'] else: - xmin = self.default_var_bound[0] # special treatement in the case goto_lower_dim which uses bsa.plot() instead of component.plot() because zorder is broken in region_plot/ContourPlot. + xmin = self.default_var_bound[0] # special treatment in the case goto_lower_dim which uses bsa.plot() instead of component.plot() because zorder is broken in region_plot/ContourPlot. if 'xmax' in kwds: self.graph.xmax(kwds['xmax']) xmax = kwds['xmax'] @@ -2988,7 +2992,7 @@ def find_point_flip_ineq_heuristic(current_var_value, ineq, ineqs, flip_ineq_ste sage: all(l(pt) < 0 for l in ineqs) and ineq(pt)>0 True - Bug examle from positive definite matrix [a, b; b, 1/4], where ineq is very negative at the test point. Make big moves first, then small moves:: + Bug example from positive definite matrix [a, b; b, 1/4], where ineq is very negative at the test point. Make big moves first, then small moves:: sage: P.=QQ[]; current_var_value = (5, 4); ineq = -4*b^2 + a; ineqs = [-a]; flip_ineq_step=1/100 sage: pt = find_point_flip_ineq_heuristic(current_var_value, ineq, ineqs, flip_ineq_step); # got pt (30943/6018, 17803/15716) From 29ae8eb1e33c40137c368e481b660c20ff9f6d7d Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Mon, 29 Sep 2025 18:58:27 -0700 Subject: [PATCH 23/64] finish fixing tests --- .../spam/parametric_real_field_element.py | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/cutgeneratingfunctionology/spam/parametric_real_field_element.py b/cutgeneratingfunctionology/spam/parametric_real_field_element.py index b50c7d8d1..39c6375b0 100644 --- a/cutgeneratingfunctionology/spam/parametric_real_field_element.py +++ b/cutgeneratingfunctionology/spam/parametric_real_field_element.py @@ -84,6 +84,8 @@ def val(self): possible_val = self.parent()._partial_eval_factor(self._sym) if possible_val in possible_val.base_ring(): return possible_val + else: + raise FactorUndetermined def _richcmp_(left, right, op): r""" Examples for traditional cmp semantics:: @@ -132,7 +134,7 @@ def _richcmp_(left, right, op): raise TypeError("comparing elements from different fields") if left.parent()._big_cells: try: - result = richcmp(left.val(), right.val(), op) + result = richcmp((left-right).val(), 0, op) except FactorUndetermined: # Partial evauation is happen, assume the result is True. result = True if result: @@ -158,13 +160,40 @@ def _richcmp_(left, right, op): return result else: # Traditional cmp semantics. - if (left.val() == right.val()): - left.parent().assume_comparison(right, operator.eq, left) - elif (left.val() < right.val()): - left.parent().assume_comparison(left, operator.lt, right) + try: + expr_val = (left-right).val() + if( expr_val == 0): + left.parent().assume_comparison(right, operator.eq, left) + elif (expr_val < 0): + left.parent().assume_comparison(left, operator.lt, right) + else: + left.parent().assume_comparison(right, operator.lt, left) + return richcmp(left.val(), right.val(), op) + except FactorUndetermined: + result = True + if result: + true_op = op + else: + true_op = richcmp_op_negation(op) + # left.sym() - right.sym() may cancel denominators, but that is + # OK because _div_ makes sure that denominators are nonzero. + if true_op == op_LT: + left.parent().assume_comparison(left - right, operator.lt) + elif true_op == op_GT: + left.parent().assume_comparison(left - right, operator.gt) + elif true_op == op_EQ: + left.parent().assume_comparison(right - left, operator.eq) + elif true_op == op_LE: + left.parent().assume_comparison(left - right, operator.le) + elif true_op == op_GE: + left.parent().assume_comparison(left - right, operator.ge) + elif true_op == op_NE: + left.parent().assume_comparison(right - left, operator.ne) else: - left.parent().assume_comparison(right, operator.lt, left) - return richcmp(left.val(), right.val(), op) + raise ValueError("{} is not a valid richcmp operator".format(op)) + return True + + def __abs__(self): """ From a9cf7cc87759667c5140d9a319c3881c29022022 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Tue, 30 Sep 2025 10:07:00 -0700 Subject: [PATCH 24/64] linting --- .../igp/parametric.sage | 146 +++++++++--------- .../spam/parametric_real_field_element.py | 18 +-- 2 files changed, 81 insertions(+), 83 deletions(-) diff --git a/cutgeneratingfunctionology/igp/parametric.sage b/cutgeneratingfunctionology/igp/parametric.sage index 631226716..4b0734922 100644 --- a/cutgeneratingfunctionology/igp/parametric.sage +++ b/cutgeneratingfunctionology/igp/parametric.sage @@ -37,12 +37,12 @@ def bigcellify_igp(): import cutgeneratingfunctionology.spam.big_cells as big_cells big_cells.bigcellify_module(igp) + ############################### # Parametric Real Number Field ############################### from cutgeneratingfunctionology.spam.parametric_real_field_element import ParametricRealFieldElement, is_parametric_element - from sage.rings.ring import Field import sage.rings.number_field.number_field_base as number_field_base from sage.structure.coerce_maps import CallableConvertMap @@ -51,12 +51,15 @@ from sage.structure.coerce_maps import CallableConvertMap class ParametricRealFieldFrozenError(ValueError): pass + class ParametricRealFieldInconsistencyError(ValueError): pass + class ParametricRealFieldRefinementError(ValueError): pass + from contextlib import contextmanager allow_refinement_default = True @@ -156,7 +159,7 @@ class ParametricRealField(Field): sage: f[0]*f[1] <= 4 True - Test-point free descriptions can be written which every comparison is assumed to be true. + Test-point free descriptions can be written which every comparison is assumed to be true. MUCH slower because of many more polynoial evaluations via libsingular:: sage: K. = ParametricRealField(None, mutable_values=True) @@ -205,7 +208,7 @@ class ParametricRealField(Field): self._big_cells = big_cells self._zero_element = ParametricRealFieldElement(self, 0) - self._one_element = ParametricRealFieldElement(self, 1) + self._one_element = ParametricRealFieldElement(self, 1) ## REFACTOR: Maybe replace this by an instance of BasicSemialgebraicSet_eq_lt_le_sets - but careful - this class right now assumes polynomials self._eq = set([]) self._lt = set([]) @@ -307,7 +310,7 @@ class ParametricRealField(Field): TypeError: unsupported operand parent(s)... Test that real number field elements can be upgraded to ``ParametricRealFieldElement``s. - Note that this requires setting up the ParametricRealField with a specific base ring, + Note that this requires setting up the ParametricRealField with a specific base ring, because there is no common parent of QQ(x) and a RealNumberField``:: sage: sqrt2, = nice_field_values([sqrt(2)]) @@ -319,7 +322,7 @@ class ParametricRealField(Field): """ if isinstance(S, ParametricRealField) and self is not S: return None - if S is sage.interfaces.mathematica.MathematicaElement or isinstance(S, RealNumberField_absolute) or isinstance(S, RealNumberField_quadratic) or AA.has_coerce_map_from(S): + if S is sage.interfaces.mathematica.MathematicaElement or isinstance(S, RealNumberField_absolute) or isinstance(S, RealNumberField_quadratic) or AA.has_coerce_map_from(S): # Does the test with MathematicaElement actually work? # We test whether S coerces into AA. This rules out inexact fields such as RDF. return True @@ -555,29 +558,16 @@ class ParametricRealField(Field): except TypeError: # 'None' components pass raise FactorUndetermined("{} cannot be evaluated because the test point is not complete".format(fac)) - + def _partial_eval_factor(self, fac): """ Partially evaluate ``fac`` on the test point. - + This function is only intended to be called after ``FactorUndetermined`` is raised from ``_eval_factor``. """ val_dict = {sym:val for sym, val in zip(fac.parent().gens() , self._values) if val is not None} return fac.subs(val_dict) -# Returns a symbolic expression or raises an ``EvaluationSuccessfulFlag``. -# Receiving an ``EvaluationSuccessfulFlag`` means ``fac`` can be evaluated with the known values of the -# test point. -# base_ring = self._sym_field.base_ring() -# if fac in base_ring: -# raise EvaluationSuccessfulFlag("{} can be evaluated in the base_ring. Use _eval_factor instead.".format(fac)) -# try: -# fac(self._values) -# raise EvaluationSuccessfulFlag("{} can be evaluated with the test point. Use _eval_factor instead.".format(fac)) -# except TypeError: -# val_dict = {sym:val for sym, val zip([symb.sym() for symb in self._gens], self._values) if val is not None} -# return fac.subs(val_dict) - def _factor_sign(self, fac): """ Determine the sign of ``fac`` evaluated on the test point. @@ -870,7 +860,7 @@ class ParametricRealField(Field): else: raise ParametricRealFieldInconsistencyError("New constraint {} {} {} is not satisfied by the test point".format(lhs, op, rhs)) else: #A numerical evaluation of the expression has failed. Assume the partial evaluation of the expression holds. - comparison_val_or_expr = self._partial_eval_factor(comparison) + comparison_val_or_expr = self._partial_eval_factor(comparison) if comparison_val_or_expr in base_ring: if not op(comparison_val_or_expr, 0): #The partial evaluation is ture, means raise ParametricRealFieldInconsistencyError("New constant constraint {} {} {} is not satisfied".format(lhs, op, rhs)) @@ -906,7 +896,8 @@ class ParametricRealField(Field): return if len(factors) == 1 and factors[0][1] == 1 and comparison_val is not None: the_fac, d = factors[0] - the_sign = sign(factors.unit() * comparison_val) + the_sign = sign(factors.unit() * comparison_val) + def factor_sign(fac): if fac == the_fac: return the_sign @@ -1130,7 +1121,7 @@ class ParametricRealField(Field): def make_proof_cell(self, **opt): r""" Make a :class:`SemialgebraicComplexComponent` from a :class:`ParametricRealField`. - + In **opt, one can provide: region_type, function, find_region_type, default_var_bound, bddbsa, kwds_dict. EXAMPLES:: @@ -1232,8 +1223,10 @@ def find_polynomial_map(eqs=[], poly_ring=None): # Functions with ParametricRealField K ###################################### + from sage.misc.sageinspect import sage_getargspec, sage_getvariablename + def read_default_args(function, **opt_non_default): r""" Return the default values of arguments of the function. @@ -1261,7 +1254,7 @@ def read_default_args(function, **opt_non_default): default_args = {} if defaults is not None: for i in range(len(defaults)): - default_args[args[-i-1]]=defaults[-i-1] + default_args[args[-i-1]] = defaults[-i-1] for (opt_name, opt_value) in opt_non_default.items(): if opt_name in default_args: default_args[opt_name] = opt_value @@ -1422,7 +1415,7 @@ class SemialgebraicComplexComponent(SageObject): # FIXME: Rename this to be m else: ptcolor = 'white' if (xmin <= pt[0] <= xmax) and (ymin <= pt[1] <= ymax): - g += point(pt, color = ptcolor, size = 2, zorder=10) + g += point(pt, color=ptcolor, size=2, zorder=10) return g def find_neighbour_candidates(self, flip_ineq_step, wall_crossing_method='heuristic', goto_lower_dim=False, pos_poly=None): @@ -1469,7 +1462,7 @@ class SemialgebraicComplexComponent(SageObject): # FIXME: Rename this to be m pt = find_point_flip_ineq_heuristic(self.var_value, l, list(bsa.lt_poly())+list(bsa.le_poly()), flip_ineq_step) if pt is not None: # Find a new point, use polynomial map to recover the values of those eliminated variables. - pt_across_wall = tuple(p(pt) for p in self.polynomial_map) + pt_across_wall = tuple(p(pt) for p in self.polynomial_map) if wall_crossing_method == 'mathematica' or wall_crossing_method == 'heuristic_then_mathematica' and (pt_across_wall is None): bsa_mathematica = bsa.formal_relint(bsa_class='mathematica') # was BasicSemialgebraicSet_mathematica.from_bsa(bsa) bsa_mathematica.add_polynomial_constraint(l, operator.gt) @@ -1556,7 +1549,9 @@ class SemialgebraicComplexComponent(SageObject): # FIXME: Rename this to be m if bsa_section.upstairs()._polyhedron.is_empty(): has_pt_on_wall = False else: - lts = []; les = []; eqs = [] + lts = [] + les = [] + eqs = [] for ll in list(bsa_section.lt_poly()): factors = ll.factor() if len(factors) == 1: @@ -1684,7 +1679,7 @@ class ProofCell(SemialgebraicComplexComponent, Classcall): sage: C_copy._init_args == C._init_args True - (We do not test for equality C_copy == C -- we have not even decided yet what the semantics + (We do not test for equality C_copy == C -- we have not even decided yet what the semantics of equality of bsa is.) """ @@ -1794,8 +1789,10 @@ class ProofCell(SemialgebraicComplexComponent, Classcall): super(ProofCell, self).__init__(K, region_type, bddbsa, polynomial_map) self.family = family + from collections import OrderedDict + class SemialgebraicComplex(SageObject): r""" A proof complex for parameter space analysis. @@ -1829,7 +1826,7 @@ class SemialgebraicComplex(SageObject): sage: complex.is_complete() # optional - mathematica True - + Example with non-linear wall:: sage: complex = SemialgebraicComplex(lambda x,y: max(x,y^2), ['x','y'], find_region_type=result_symbolic_expression, default_var_bound=(-3,3)) # optional - mathematica @@ -1924,7 +1921,7 @@ class SemialgebraicComplex(SageObject): to a "type" of the parameter region, for example: - :func:`find_region_type_igp` (the default). The result of the computation is a Gomory-Johnson - function `h`; it is passed to :func:`find_region_type_igp` as 2nd arg, + function `h`; it is passed to :func:`find_region_type_igp` as 2nd arg, and then :func:`find_region_type_igp`which classifies the region of the parameter by returning one of the strings ``'is_constructible'``, ``'not_constructible'``, @@ -2003,7 +2000,7 @@ class SemialgebraicComplex(SageObject): r""" Return a random point that satisfies var_bounds and is in self.bsa. - - If var_bounds is not specified, self.default_var_bound is taken. + - If var_bounds is not specified, self.default_var_bound is taken. - var_bounds can be a list of 2-tuples whose length equals to the number of parameters, or lambda functions. - It is used in random shooting method for functions like ``dg_2_step_mir``, which involve floor/ceil operations. We try to plot one layer for each n = floor(...) and superimpose the layers at the end to get the whole picture. @@ -2029,11 +2026,11 @@ class SemialgebraicComplex(SageObject): x = QQ(uniform(self.default_var_bound[0], self.default_var_bound[1])) else: if hasattr(var_bounds[i][0], '__call__'): - l = var_bounds[i][0](*var_value) + l = var_bounds[i][0](*var_value) else: l = var_bounds[i][0] if hasattr(var_bounds[i][1], '__call__'): - u = var_bounds[i][1](*var_value) + u = var_bounds[i][1](*var_value) else: u = var_bounds[i][1] if l > u: @@ -2094,12 +2091,12 @@ class SemialgebraicComplex(SageObject): for c in self.components: if var_value in c.bsa: yield c - + def find_uncovered_random_point(self, var_bounds=None, max_failings=10000): r""" Return a random point that satisfies the bounds and is uncovered by any cells in the complex. Return ``None`` if the number of attemps > max_failings. - + EXAMPLES:: sage: from cutgeneratingfunctionology.igp import * @@ -2128,7 +2125,7 @@ class SemialgebraicComplex(SageObject): The argument formal_closure whether inequalities are treated as <= 0 or as < 0. If such point does not exist, return ``None``. - + EXAMPLES:: sage: from cutgeneratingfunctionology.igp import * @@ -2205,18 +2202,18 @@ class SemialgebraicComplex(SageObject): self.points_to_test[num_eq] = OrderedDict() if not num_eq in self.tested_points: self.tested_points[num_eq] = set([]) - new_component = self._cell_class(self.family, var_value, + new_component = self._cell_class(self.family, var_value, find_region_type=self.find_region_type, bddbsa=bddbsa, polynomial_map=polynomial_map) new_num_eq = len(list(new_component.bsa.eq_poly())) - if new_num_eq > num_eq: - logging.warning("The cell around %s defined by %s has more equations than boundary %s" %(new_component.var_value, new_component.bsa, bddbsa)) + if new_num_eq > num_eq: + logging.warning("The cell around %s defined by %s has more equations than boundary %s" % (new_component.var_value, new_component.bsa, bddbsa)) #import pdb; pdb.set_trace() - # bsa is lower dimensional as it has more equations than bddbsa, + # bsa is lower dimensional as it has more equations than bddbsa, # so we try to perturb the testpoint to obtain a # new testpoint in bddbsa that does not fall into a lower dimensional cell. # Heuristic code using gradient desecent. #FIXME. - for l in (set(new_component.bsa.eq_poly())- set(bddbsa.eq_poly())): + for l in (set(new_component.bsa.eq_poly()) - set(bddbsa.eq_poly())): ineqs = list(new_component.bddbsa.lt_poly())+list(new_component.bddbsa.le_poly()) pts = [find_point_flip_ineq_heuristic(var_value, l, ineqs, 1/2017), find_point_flip_ineq_heuristic(var_value, -l, ineqs, 1/2017)] for pt in pts: @@ -2277,7 +2274,7 @@ class SemialgebraicComplex(SageObject): Plot the complex and store the graph. - If restart is ``False``, plot the newly added cells on top of the last graph; otherwise, start a new graph. - - If slice_value is given, it is either a polynomial_map that defines a section, or a list of fixed parameter values with two of them being None. Plot the section. + - If slice_value is given, it is either a polynomial_map that defines a section, or a list of fixed parameter values with two of them being None. Plot the section. - plot_points controls the quality of the plotting. EXAMPLES:: @@ -2333,7 +2330,7 @@ class SemialgebraicComplex(SageObject): # # FIXME: zorder is broken in region_plot/ContourPlot. # for c in self.components[self.num_plotted_components::]: # num_eq = len(list(c.bsa.eq_poly())) - # gc = c.plot(alpha=alpha, plot_points=plot_points, slice_value=slice_value, default_var_bound=self.default_var_bound, goto_lower_dim=goto_lower_dim, zorder=num_eq, **kwds) + # gc = c.plot(alpha=alpha, plot_points=plot_points, slice_value=slice_value, default_var_bound=self.default_var_bound, goto_lower_dim=goto_lower_dim, zorder=num_eq, **kwds) # if gc: # need this because (empty g + empty gc) forgets about xmin xmax ymin ymax. # self.graph += gc # Workaround. @@ -2355,11 +2352,11 @@ class SemialgebraicComplex(SageObject): new_bsa.add_polynomial_constraint(l, operator.eq) self.graph += new_bsa.plot(alpha=alpha, plot_points=plot_points, slice_value=slice_value, color=color, fill_color=color, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax) for c in components: - if len(list(c.bsa.eq_poly()))==1: + if len(list(c.bsa.eq_poly())) == 1: self.graph += c.plot(alpha=alpha, plot_points=plot_points, slice_value=slice_value, default_var_bound=self.default_var_bound, goto_lower_dim=False, zorder=0, **kwds) if goto_lower_dim: for c in components: - if len(list(c.bsa.eq_poly()))==1: + if len(list(c.bsa.eq_poly())) == 1: color = find_region_color(c.region_type) for l in c.bsa.lt_poly(): new_bsa = BasicSemialgebraicSet_eq_lt_le_sets(eq=list(c.bsa.eq_poly())+[l], lt=[ll for ll in c.bsa.lt_poly() if ll != l], le=list(c.bsa.le_poly())) @@ -2369,9 +2366,9 @@ class SemialgebraicComplex(SageObject): new_bsa.add_polynomial_constraint(l, operator.eq) self.graph += new_bsa.plot(alpha=alpha, plot_points=plot_points, slice_value=slice_value, color=color, fill_color=color, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax) for c in components: - if len(list(c.bsa.eq_poly()))==2: + if len(list(c.bsa.eq_poly())) == 2: ptcolor = find_region_color(c.region_type) - self.graph += point(c.var_value, color = ptcolor, zorder=10) + self.graph += point(c.var_value, color=ptcolor, zorder=10) self.num_plotted_components = len(self.components) return self.graph @@ -2408,7 +2405,7 @@ class SemialgebraicComplex(SageObject): - var_value: the starting point. If not given, start with a random point. - flip_ineq_step: a small positive number that controls the step length in wall-crossing. - check_completion: When check_completion is ``True``, after bfs terminates, check whether the entire parameter space is covered by cells, using Mathematica's ``FindInstance`` (if max_failings=0) or random shooting (if max_failings>0). If an uncovered point has been found, restart the bfs from this point. - - wall_crossing_method: 'mathematica' or 'heuristic' or 'heuristic_then_mathematica' + - wall_crossing_method: 'mathematica' or 'heuristic' or 'heuristic_then_mathematica' - wall_crossing_method='heuristic_then_mathematica': try heuristic method first. If it does not find a new testpoint, then use Mathematica. - goto_lower_dim: whether lower dimensional cells are considered. If goto_lower_dim is ``False`` or if goto_lower_dim is ``True`` and wall_crossing method is 'heuristic' but the wall is non-linear, then find new testpoint across the wall only. @@ -2462,10 +2459,10 @@ class SemialgebraicComplex(SageObject): uncovered_pt = self.find_uncovered_random_point(max_failings=max_failings) if uncovered_pt is not None: logging.warning("After bfs, the complex has uncovered point %s." % (uncovered_pt,)) - self.bfs_completion(var_value=uncovered_pt, \ - flip_ineq_step=flip_ineq_step, \ - check_completion=check_completion, \ - wall_crossing_method=wall_crossing_method, \ + self.bfs_completion(var_value=uncovered_pt, + flip_ineq_step=flip_ineq_step, + check_completion=check_completion, + wall_crossing_method=wall_crossing_method, goto_lower_dim=goto_lower_dim) def is_complete(self, formal_closure=False): @@ -2567,9 +2564,9 @@ def gradient(ineq): [3*z^2 + 6*z] """ if hasattr(ineq, 'gradient'): - return ineq.gradient() + return ineq.gradient() else: - return [ineq.derivative()] + return [ineq.derivative()] #################################### # Find region type and region color @@ -2702,8 +2699,8 @@ def return_result(field, result): def result_concrete_value(field, result): r""" Return the concrete values in result as a tuple. See also ``result_symbolic_expression()``. - - This function can provided to ``find_region_type`` when setting up a :class:`SemialgebraicComplex`. + + This function can provided to ``find_region_type`` when setting up a :class:`SemialgebraicComplex`. In this way, one can compare result of type :class:`ParametricRealFieldElement` or list of :class:`ParametricRealFieldElement` with the previous elements in ``region_type_color_map`` which do not necessarily have the same parent. @@ -2725,8 +2722,8 @@ def result_concrete_value(field, result): def result_symbolic_expression(field, result): r""" Return the symbolic expressions in result as a tuple - - This function can provided to ``find_region_type`` when setting up a :class:`SemialgebraicComplex`. + + This function can provided to ``find_region_type`` when setting up a :class:`SemialgebraicComplex`. In this way, one can compare result of type :class:`ParametricRealFieldElement` or list of :class:`ParametricRealFieldElement` with the previous elements in ``region_type_color_map`` which do not necessarily have the same parent. @@ -2750,7 +2747,7 @@ def result_symbolic_expression(field, result): sage: vol1 == vol2 False sage: sym_exp1 == sym_exp2 - True + True """ symbolic_expression = tuple(elt._sym if hasattr(elt, '_sym') else elt for elt in flatten([result])) return symbolic_expression @@ -2775,7 +2772,7 @@ def find_region_type_igp_extreme_big_cells(K, h): h = copy(hcopy) for x in h.values_at_end_points(): if (x < 0) or (x > 1): - is_extreme = False + is_extreme = False break if not is_extreme: assert (x < 0) or (x > 1) @@ -2821,7 +2818,7 @@ def find_region_type_igp_extreme_big_cells(K, h): ucs = generate_uncovered_components(h) f = find_f(h) for uncovered_pt in [f/2, (f+1)/2]: - if any((i[0] == uncovered_pt or i[1] == uncovered_pt) for uc in ucs for i in uc if len(uc)==2): + if any((i[0] == uncovered_pt or i[1] == uncovered_pt) for uc in ucs for i in uc if len(uc) == 2): uncovered_pts = [uncovered_pt] is_extreme = False break @@ -2855,7 +2852,8 @@ def find_region_type_igp_extreme_big_cells(K, h): return False return True -region_type_color_map = [('not_constructible', 'lightgrey'), ('is_constructible', 'black'), ('not_minimal', 'orange'), ('is_minimal', 'darkgrey'),('not_extreme', 'green'), ('is_extreme', 'blue'), ('stop', 'grey'), (True, 'blue'), (False, 'red'), ('constructible', 'darkgrey'), ('extreme', 'red')] + +region_type_color_map = [('not_constructible', 'lightgrey'), ('is_constructible', 'black'), ('not_minimal', 'orange'), ('is_minimal', 'darkgrey'), ('not_extreme', 'green'), ('is_extreme', 'blue'), ('stop', 'grey'), (True, 'blue'), (False, 'red'), ('constructible', 'darkgrey'), ('extreme', 'red')] def find_region_color(region_type): r""" @@ -2900,11 +2898,11 @@ def color_of_ith_region_type(i): def find_point_flip_ineq_heuristic(current_var_value, ineq, ineqs, flip_ineq_step): r""" - The current_var_value satisfies that l(current_var_value) <= 0 for l=ineq and for every l in ineqs, + The current_var_value satisfies that l(current_var_value) <= 0 for l=ineq and for every l in ineqs, where ineq is a polynomial and ineqs is a list of polynomials. Use heuristic method (gradient descent method with given small positive step length flip_ineq_step) - to find a new_point (type is tuple) such that + to find a new_point (type is tuple) such that ineq(new_point) > 0, l(new_point) < 0 for all l in ineqs Return new_point, or ``None`` if it fails to find one. @@ -3010,7 +3008,7 @@ def find_point_flip_ineq_heuristic(current_var_value, ineq, ineqs, flip_ineq_ste ineq_value = ineq(*current_point) try_before_fail -= 1 # print (current_point, RR(ineq_value)) - try_before_fail = max(ceil(2/flip_ineq_step), 2000) # define maximum number of walks. Considered ceil(-2 * ineq_value /flip_ineq_step) but it is too slow in the impossible cases. Added a loop with 2000 times step length when ineq_value is very negative. + try_before_fail = max(ceil(2/flip_ineq_step), 2000) # define maximum number of walks. Considered ceil(-2 * ineq_value /flip_ineq_step) but it is too slow in the impossible cases. Added a loop with 2000 times step length when ineq_value is very negative. while (ineq_value <= 1e-10) and (try_before_fail > 0): ineq_direction = vector(g(*current_point) for g in ineq_gradient) if ineq.degree() == 1: @@ -3034,8 +3032,8 @@ def find_point_flip_ineq_heuristic(current_var_value, ineq, ineqs, flip_ineq_ste def adjust_pt_to_satisfy_ineqs(current_point, ineq, strict_ineqs, nonstrict_ineqs, flip_ineq_step): r""" - Walk from current_point (type=vector) in the direction perpendicular to - the gradient of ineq with small positive step length flip_ineq_step, + Walk from current_point (type=vector) in the direction perpendicular to + the gradient of ineq with small positive step length flip_ineq_step, while maintaining the value of ineq(*current_point) which is >= 0. until get a new point such that l(new point)<0 for all l in strict_ineqs and l(new point)<=0 for all l in nonstrict_ineqs @@ -3057,7 +3055,7 @@ def adjust_pt_to_satisfy_ineqs(current_point, ineq, strict_ineqs, nonstrict_ineq sage: pt = adjust_pt_to_satisfy_ineqs(vector([11/8, 7/8]), a+b-2, [-a+b^2], [a-1], 1/4); pt is None True - Bug example in cpl Cell 9 with test point (499/1250, 488072439572/4866126017667). Without converting input to QQ, output was (0.333000000000000, 0.111333333333333) with -2*f - 3*z + 1 = -5.55111512312578e-17 , the QQ of the output=(333/1000, 167/1500) has -2*f - 3*z + 1 == 0. Revise the code to take QQ input current point:: + Bug example in cpl Cell 9 with test point (499/1250, 488072439572/4866126017667). Without converting input to QQ, output was (0.333000000000000, 0.111333333333333) with -2*f - 3*z + 1 = -5.55111512312578e-17 , the QQ of the output=(333/1000, 167/1500) has -2*f - 3*z + 1 == 0. Revise the code to take QQ input current point:: sage: P.=QQ[] sage: current_point = vector([0.333000000000000, 0.100300000000000]); ineq = -3*f + 1; strict_ineqs = [2*f + 2*z - 1, f + 5*z - 1, -f - 6*z + 1, -2*f - 3*z + 1]; nonstrict_ineqs = []; flip_ineq_step = 1/1000 @@ -3076,8 +3074,8 @@ def adjust_pt_to_satisfy_ineqs(current_point, ineq, strict_ineqs, nonstrict_ineq Bug example in cpl bigcell 16 with test point (12219/26000, 24/1625). Redo with QQ had infinite loop. Bug comes from find_neighbour_point where it calls bsa_section.upstairs()._polyhedron.is_empty(), which is not strong enough. If we could test bsa_section is empty (perhaps by tighten_upstairs_by_mccormick), then this example should not appear:: - sage: P.=QQ[]; - sage: current_point = vector((71582788/143165577, 4673/377000)) # came from vector((RR(70727/150800), RR(4673/377000))), + sage: P.=QQ[]; + sage: current_point = vector((71582788/143165577, 4673/377000)) # came from vector((RR(70727/150800), RR(4673/377000))), sage: ineq=None; strict_ineqs=[2*f - 1, -9*f + 2]; nonstrict_ineqs=[4*f^2 - 4*f + 1]; flip_ineq_step=1/1000 sage: pt = adjust_pt_to_satisfy_ineqs(current_point, None, strict_ineqs, nonstrict_ineqs, flip_ineq_step=1/1000); pt is None #long time True @@ -3092,7 +3090,7 @@ def adjust_pt_to_satisfy_ineqs(current_point, ineq, strict_ineqs, nonstrict_ineq #current_point is a vector if ineq is not None: ineq_gradient = gradient(ineq) - if all(x.parent()==QQ for x in current_point): + if all(x.parent() == QQ for x in current_point): max_walks = min(ceil(2/flip_ineq_step), 20) else: max_walks = min(ceil(2/flip_ineq_step), 200) #1000? # define maximum number of walks. @@ -3154,7 +3152,7 @@ def adjust_pt_to_satisfy_ineqs(current_point, ineq, strict_ineqs, nonstrict_ineq return None if ineq is not None and ineq(*current_point) < 0: return None - if all(x.parent()==QQ for x in current_point): + if all(x.parent() == QQ for x in current_point): return tuple(current_point) else: prec = 30 # We hope to have small denominator for the new point, so we set precision in bits = 30 is about 8 digits. @@ -3215,13 +3213,14 @@ def embed_function_into_family(given_function, parametric_family, check_completi var_name = [] var_value = [] for (name, value) in default_args.items(): - if not isinstance(value, bool) and not value is None: + if not isinstance(value, bool) and value is not None: try: RR(value) var_name.append(name) var_value.append(value) except: pass + def frt(K, h): if h is None: return False @@ -3264,6 +3263,7 @@ def embed_function_into_family(given_function, parametric_family, check_completi # plot_cpl_components(complex.components) return {} + """ EXAMPLES:: diff --git a/cutgeneratingfunctionology/spam/parametric_real_field_element.py b/cutgeneratingfunctionology/spam/parametric_real_field_element.py index 39c6375b0..8c6e4c831 100644 --- a/cutgeneratingfunctionology/spam/parametric_real_field_element.py +++ b/cutgeneratingfunctionology/spam/parametric_real_field_element.py @@ -12,6 +12,7 @@ from cutgeneratingfunctionology.shared.EvaluationExceptions import FactorUndetermined import operator + def richcmp_op_negation(op): if op == op_LT: return op_GE @@ -28,6 +29,7 @@ def richcmp_op_negation(op): else: raise ValueError("{} is not a valid richcmp operator".format(op)) + def format_richcmp_op(op): if op == op_LT: return '<' @@ -44,6 +46,7 @@ def format_richcmp_op(op): else: raise ValueError("{} is not a valid richcmp operator".format(op)) + class ParametricRealFieldElement(FieldElement): r""" A :class:`ParametricRealFieldElement` stores a symbolic expression of the parameters in the problem and a concrete value, which is the evaluation of this expression on the given parameter tuple. @@ -85,7 +88,8 @@ def val(self): if possible_val in possible_val.base_ring(): return possible_val else: - raise FactorUndetermined + raise FactorUndetermined.("{} cannot be evaluated because the test point is not complete".format(self.sym())) + def _richcmp_(left, right, op): r""" Examples for traditional cmp semantics:: @@ -135,7 +139,7 @@ def _richcmp_(left, right, op): if left.parent()._big_cells: try: result = richcmp((left-right).val(), 0, op) - except FactorUndetermined: # Partial evauation is happen, assume the result is True. + except FactorUndetermined: # Partial evaluation has happen, assume the result is True. result = True if result: true_op = op @@ -170,13 +174,8 @@ def _richcmp_(left, right, op): left.parent().assume_comparison(right, operator.lt, left) return richcmp(left.val(), right.val(), op) except FactorUndetermined: - result = True - if result: + # With a partial evaluation, assume the written inequality is true. true_op = op - else: - true_op = richcmp_op_negation(op) - # left.sym() - right.sym() may cancel denominators, but that is - # OK because _div_ makes sure that denominators are nonzero. if true_op == op_LT: left.parent().assume_comparison(left - right, operator.lt) elif true_op == op_GT: @@ -193,8 +192,6 @@ def _richcmp_(left, right, op): raise ValueError("{} is not a valid richcmp operator".format(op)) return True - - def __abs__(self): """ Examples for traditional cmp semantics:: @@ -436,6 +433,7 @@ def __hash__(self): """ return hash(self.val()) + def is_parametric_element(x): # We avoid using isinstance here so that this is robust even if parametric.sage is reloaded. # For example, apparently in the test suite. From b2aef9741527dabc9b1f5506004554cf41709787 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Tue, 30 Sep 2025 10:18:16 -0700 Subject: [PATCH 25/64] spelling fixes --- .../igp/parametric.sage | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/cutgeneratingfunctionology/igp/parametric.sage b/cutgeneratingfunctionology/igp/parametric.sage index 4b0734922..a8b0f9aa3 100644 --- a/cutgeneratingfunctionology/igp/parametric.sage +++ b/cutgeneratingfunctionology/igp/parametric.sage @@ -862,9 +862,9 @@ class ParametricRealField(Field): else: #A numerical evaluation of the expression has failed. Assume the partial evaluation of the expression holds. comparison_val_or_expr = self._partial_eval_factor(comparison) if comparison_val_or_expr in base_ring: - if not op(comparison_val_or_expr, 0): #The partial evaluation is ture, means + if not op(comparison_val_or_expr, 0): raise ParametricRealFieldInconsistencyError("New constant constraint {} {} {} is not satisfied".format(lhs, op, rhs)) - else: # comparision_val_or_expr is algebraic expresion, asume the comparision here is comparionsion_val_or_expr + else: # comparision_val_or_expr is algebraic expression, assume the comparison here is comparionsion_val_or_expr. comparison = comparison_val_or_expr if comparison in base_ring: return @@ -1161,7 +1161,7 @@ class ParametricRealField(Field): ############################### def find_polynomial_map(eqs=[], poly_ring=None): """ - BAD FUCNTION! It is used in 'mathematica' approach for non-linear case. Can we avoid it? + BAD FUNCTION! It is used in 'mathematica' approach for non-linear case. Can we avoid it? Return a polynomial map that eliminates linear variables in eqs, and a dictionary recording which equations were used to eliminate those linear variables. Assume that gaussian elimination has been performed by PPL.minimized_constraints() on the input list of equations eqs. It is only called in SemialgebraicComplex.add_new_component in the case polynomial_map is not provided but bddbsa has equations. @@ -1313,7 +1313,7 @@ class SemialgebraicComplexComponent(SageObject): # FIXME: Rename this to be m sage: sorted(component.bsa.lt_poly()) [-x, 3*x - 4] - In ProofCell region_type should alreay consider the polynomial_map:: + In ProofCell region_type should already consider the polynomial_map:: sage: K. = ParametricRealField([1,1/2]) sage: assert(x == 2*y) @@ -1344,10 +1344,14 @@ class SemialgebraicComplexComponent(SageObject): # FIXME: Rename this to be m # In lower dim proof cell or non-linear equations case, some equations of K._bsa are not presented in polynomial_map. eqs = list(K._bsa.eq_poly()) if not all(l(polynomial_map) == 0 for l in eqs): - polynomial_map = find_polynomial_map(eqs=eqs, poly_ring=poly_ring) + polynomial_map = find_polynomial_map(eqs=eqs, poly_ring=poly_ring)fv #self.bsa = K._bsa.section(polynomial_map, bsa_class='veronese', poly_ring=poly_ring) # this is a bigger_bsa self.bsa = BasicSemialgebraicSet_veronese.from_bsa(BasicSemialgebraicSet_local(K._bsa.section(polynomial_map, poly_ring=poly_ring), self.var_value)) # TODO:, polynomial_map=list(poly_ring.gens())) - # WHY is this input polynomial_map sometimes not compatible with the variable elimination done in bddbsa? Because upstairs ppl bsa eliminates large x_i in the inequalities, and x_i doesn't necessarily correspond to the i-th variable in poly_ring. Since polynomial_map and v_dict were not given at the initialization of veronese, the variable first encounted in the constraints is considered as x0 by upstairs ppl bsa. # In old code, we fixed the order of upstairs variables by adding initial space dimensions. We don't do that in the current code. Instead, we take the section of bddbsa to eliminate the varibles in the equations. # Is the given bddbsa required to be veronese with upstairs being ppl_bsa? Convert it anyway. # It's the same as BasicSemialgebraicSet_veronese.from_bsa(bddbsa.section(self.polynomial_map), poly_ring=poly_ring) + # WHY is this input polynomial_map sometimes not compatible with the variable elimination done in bddbsa? + # Because upstairs ppl bsa eliminates large x_i in the inequalities, and x_i doesn't necessarily correspond to the i-th variable in poly_ring. + # Since polynomial_map and v_dict were not given at the initialization of veronese, the variable first encountered, in the constraints is considered as x0 by upstairs ppl bsa. + # In old code, we fixed the order of upstairs variables by adding initial space dimensions. We don't do that in the current code. Instead, we take the section of bddbsa to eliminate the variables in the equations. + # Is the given bddbsa required to be veronese with upstairs being ppl_bsa? Convert it anyway. # It's the same as BasicSemialgebraicSet_veronese.from_bsa(bddbsa.section(self.polynomial_map), poly_ring=poly_ring) self.bddbsa = BasicSemialgebraicSet_veronese.from_bsa(BasicSemialgebraicSet_local(bddbsa.section(polynomial_map, poly_ring=poly_ring), self.var_value)) # Taking section forgets the equations. Then add back the equations # Finally self.bsa should be the same as K._bsa, but its inequalities don't have variables eliminated by polynomial map, so that heuristic wall crossing can be done later. for i in range(len(self.var_name)): @@ -1427,13 +1431,13 @@ class SemialgebraicComplexComponent(SageObject): # FIXME: Rename this to be m - if goto_lower_dim=False, the cell is considered as its formal closure, so no recursion into test points in lower dimensional cells. - pos_poly is a polynomial. The return test point must satisfy pos_poly(new test point) > 0. - OUTPUT new_points is a dictionary of dictionaries. The new_points[i] is a dictionay whose keys = candidate neighbour testpoints, values = (bddbsa whose eq_poly has i elements, polynomial_map, no_crossing_l) of the candidate neighbour cell that contains the candidate neighbour testpoint. bddbsa is recorded so that (1) complex.bddbsa is always respected; and (2) can recursively go into lower dimensional cells. polynomial_map is recorded and passed to the constructor of the neighbour cell. no_crossing is passed to the neighour cell for its find_neighbour_candidates method. We no longer update self.bsa by removing (obvious) redundant eq, lt, le constraints from its description at the end, even when 'mathematica' is used. + OUTPUT new_points is a dictionary of dictionaries. The new_points[i] is a dictionary whose keys = candidate neighbour testpoints, values = (bddbsa whose eq_poly has i elements, polynomial_map, no_crossing_l) of the candidate neighbour cell that contains the candidate neighbour testpoint. bddbsa is recorded so that (1) complex.bddbsa is always respected; and (2) can recursively go into lower dimensional cells. polynomial_map is recorded and passed to the constructor of the neighbour cell. no_crossing is passed to the neighbour cell for its find_neighbour_candidates method. We no longer update self.bsa by removing (obvious) redundant eq, lt, le constraints from its description at the end, even when 'mathematica' is used. """ bsa_eq_poly = list(self.bsa.eq_poly()) bsa_le_poly = list(self.bsa.le_poly()) bsa_lt_poly = list(self.bsa.lt_poly()) num_eq = len(bsa_eq_poly) #was len(list(self.bddbsa.eq_poly())) - new_points = {} #dictionary with key=num_eq, value=dictionay of pt: (bddbsa, polynomial_map). + new_points = {} #dictionary with key=num_eq, value=dictionary of pt: (bddbsa, polynomial_map). #bddbsa = copy(self.bddbsa) #for l in bsa_eq_poly: # should be already in bddbsa # bddbsa.add_polynomial_constraint(l, operator.eq) @@ -2095,7 +2099,7 @@ class SemialgebraicComplex(SageObject): def find_uncovered_random_point(self, var_bounds=None, max_failings=10000): r""" Return a random point that satisfies the bounds and is uncovered by any cells in the complex. - Return ``None`` if the number of attemps > max_failings. + Return ``None`` if the number of attempts > max_failings. EXAMPLES:: @@ -2311,7 +2315,7 @@ class SemialgebraicComplex(SageObject): self.graph.xmin(kwds['xmin']) xmin = kwds['xmin'] else: - xmin = self.default_var_bound[0] # special treatement in the case goto_lower_dim which uses bsa.plot() instead of component.plot() because zorder is broken in region_plot/ContourPlot. + xmin = self.default_var_bound[0] # special treatment in the case goto_lower_dim which uses bsa.plot() instead of component.plot() because zorder is broken in region_plot/ContourPlot. if 'xmax' in kwds: self.graph.xmax(kwds['xmax']) xmax = kwds['xmax'] @@ -2988,7 +2992,7 @@ def find_point_flip_ineq_heuristic(current_var_value, ineq, ineqs, flip_ineq_ste sage: all(l(pt) < 0 for l in ineqs) and ineq(pt)>0 True - Bug examle from positive definite matrix [a, b; b, 1/4], where ineq is very negative at the test point. Make big moves first, then small moves:: + Bug example from positive definite matrix [a, b; b, 1/4], where ineq is very negative at the test point. Make big moves first, then small moves:: sage: P.=QQ[]; current_var_value = (5, 4); ineq = -4*b^2 + a; ineqs = [-a]; flip_ineq_step=1/100 sage: pt = find_point_flip_ineq_heuristic(current_var_value, ineq, ineqs, flip_ineq_step); # got pt (30943/6018, 17803/15716) From 4f19888a678bf83477a159d61d1bd401fabadc38 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:21:30 -0700 Subject: [PATCH 26/64] minor fixes --- cutgeneratingfunctionology/igp/parametric.sage | 2 +- .../spam/parametric_real_field_element.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cutgeneratingfunctionology/igp/parametric.sage b/cutgeneratingfunctionology/igp/parametric.sage index a8b0f9aa3..5b44a94b2 100644 --- a/cutgeneratingfunctionology/igp/parametric.sage +++ b/cutgeneratingfunctionology/igp/parametric.sage @@ -1344,7 +1344,7 @@ class SemialgebraicComplexComponent(SageObject): # FIXME: Rename this to be m # In lower dim proof cell or non-linear equations case, some equations of K._bsa are not presented in polynomial_map. eqs = list(K._bsa.eq_poly()) if not all(l(polynomial_map) == 0 for l in eqs): - polynomial_map = find_polynomial_map(eqs=eqs, poly_ring=poly_ring)fv + polynomial_map = find_polynomial_map(eqs=eqs, poly_ring=poly_ring) #self.bsa = K._bsa.section(polynomial_map, bsa_class='veronese', poly_ring=poly_ring) # this is a bigger_bsa self.bsa = BasicSemialgebraicSet_veronese.from_bsa(BasicSemialgebraicSet_local(K._bsa.section(polynomial_map, poly_ring=poly_ring), self.var_value)) # TODO:, polynomial_map=list(poly_ring.gens())) # WHY is this input polynomial_map sometimes not compatible with the variable elimination done in bddbsa? diff --git a/cutgeneratingfunctionology/spam/parametric_real_field_element.py b/cutgeneratingfunctionology/spam/parametric_real_field_element.py index 8c6e4c831..a0cd5c55e 100644 --- a/cutgeneratingfunctionology/spam/parametric_real_field_element.py +++ b/cutgeneratingfunctionology/spam/parametric_real_field_element.py @@ -88,7 +88,7 @@ def val(self): if possible_val in possible_val.base_ring(): return possible_val else: - raise FactorUndetermined.("{} cannot be evaluated because the test point is not complete".format(self.sym())) + raise FactorUndetermined("{} cannot be evaluated because the test point is not complete".format(self.sym())) def _richcmp_(left, right, op): r""" From 3c8ba069ad104286fd35eb093d53252808923423 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Tue, 30 Sep 2025 17:12:06 -0700 Subject: [PATCH 27/64] fix --- cutgeneratingfunctionology/igp/parametric.sage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cutgeneratingfunctionology/igp/parametric.sage b/cutgeneratingfunctionology/igp/parametric.sage index 8127e137e..c77cffe9c 100644 --- a/cutgeneratingfunctionology/igp/parametric.sage +++ b/cutgeneratingfunctionology/igp/parametric.sage @@ -1347,7 +1347,7 @@ class SemialgebraicComplexComponent(SageObject): # FIXME: Rename this to be m # In lower dim proof cell or non-linear equations case, some equations of K._bsa are not presented in polynomial_map. eqs = list(K._bsa.eq_poly()) if not all(l(polynomial_map) == 0 for l in eqs): - polynomial_map = find_polynomial_map(eqs=eqs, poly_ring=poly_ring)fv + polynomial_map = find_polynomial_map(eqs=eqs, poly_ring=poly_ring) #self.bsa = K._bsa.section(polynomial_map, bsa_class='veronese', poly_ring=poly_ring) # this is a bigger_bsa self.bsa = BasicSemialgebraicSet_veronese.from_bsa(BasicSemialgebraicSet_local(K._bsa.section(polynomial_map, poly_ring=poly_ring), self.var_value)) # TODO:, polynomial_map=list(poly_ring.gens())) # WHY is this input polynomial_map sometimes not compatible with the variable elimination done in bddbsa? From 663b77ade310a110dfd8f447de9cb94ec4facf86 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Tue, 7 Oct 2025 09:17:17 -0700 Subject: [PATCH 28/64] fixed min_fun_cashe --- cutgeneratingfunctionology/igp/functions.sage | 4 +-- .../igp/minimal_function_cache.sage | 18 ++++++----- .../igp/parametric.sage | 6 ++-- .../spam/basic_semialgebraic.py | 15 ++++++++-- .../spam/parametric_real_field_element.py | 30 +++++++++---------- 5 files changed, 44 insertions(+), 29 deletions(-) diff --git a/cutgeneratingfunctionology/igp/functions.sage b/cutgeneratingfunctionology/igp/functions.sage index 2d5f50e44..0a1098d78 100644 --- a/cutgeneratingfunctionology/igp/functions.sage +++ b/cutgeneratingfunctionology/igp/functions.sage @@ -1513,11 +1513,11 @@ def piecewise_function_from_breakpoints_slopes_and_values(bkpt, slopes, values, if field is None: field = default_field # global symb_values - if slopes is None: + if slopes is None: # make order assumptions in these functions, remove != when possible. symb_values = bkpt + values field_values = nice_field_values(symb_values, field) bkpt, values = field_values[0:len(bkpt)], field_values[-len(values):] - slopes = [(values[i+1]-values[i])/(bkpt[i+1]-bkpt[i]) if bkpt[i+1] != bkpt[i] else 0 for i in range(len(bkpt)-1)] + slopes = [(values[i+1]-values[i])/(bkpt[i+1]-bkpt[i]) if bkpt[i+1] > bkpt[i] else 0 for i in range(len(bkpt)-1)] else: symb_values = bkpt + slopes + values field_values = nice_field_values(symb_values, field) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index 8abf347e4..981fa79f1 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -3,7 +3,7 @@ from itertools import pairwise from cutgeneratingfunctionology.igp import * import csv import os - +from cutgeneratingfunctionology.spam.basic_semialgebraic import EmptyBSA def mod_one(x): if x >= 0: return x - int(x) @@ -77,7 +77,7 @@ def nnc_poly_from_bkpt(bkpt, backend=None): vals = bkpt_vals[0:n] for i in range(0,n): coord_names.append('lambda'+str(i)) - K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, partial_test_point_mode=True, default_backend=backend) + K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True) logging.disable(logging.INFO) K.gens()[0] == 0 for i in range(n-1): @@ -133,7 +133,7 @@ class BreakpointComplexClassContainer: def get_nnc_poly_from_bkpt(self): for bkpt in self._data: - yield nnc_poly_from_bkpt(bkpt, self._backend) + yield nnc_poly_from_bkpt(bkpt) def num_rep_elems(self): return len(self._data) @@ -208,7 +208,7 @@ def assume_minimality(bkpt, f_index, backend=None): for i in range(1,n): coord_names.append('gamma'+str(i)) logging.disable(logging.INFO) - K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, partial_test_point_mode=True, default_backend=backend) + K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, allow_refinement=False) for i in range(n-1): K.gens()[i+n-1] <=1 K.gens()[i+n-1] > 0 @@ -219,15 +219,17 @@ def assume_minimality(bkpt, f_index, backend=None): vert for vert in generate_assumed_symmetric_vertices_continuous(h, K.gens()[f_index-1], [0] + K.gens()[0:n-1] + [1]): vert - for i in range(n): - K.gens()[i] == bkpt[i] - K.find_test_point() + try: + K.find_test_point() + except EmptyBSA: + return h_2 = piecewise_function_from_breakpoints_and_values([0]+list(K._values[0:n-1])+[1], [0] + list(K._values[n-1:2*n-2])+[0]) is_minimal = minimality_test(h_2) if is_minimal: rep_bkpt = [0] + list(K._values[0:n-1]) v = [0] + list(K._values[n-1:2*n-2]) return (rep_bkpt, v) + return @@ -305,7 +307,7 @@ class PiMinContContainer: else: logging.warning("Generating representative elements. This might take a while.") bkpts = make_bkpts_with_len_n(self._n) - self._data = find_minimal_function_reps_from_bkpts(bkpts, self._backend) + self._data = find_minimal_function_reps_from_bkpts(bkpts) def __repr__(self): return "Space of minimal functions with at most {} breakpoints parameterized by breakpoints and values using semialgebraic sets.".format(self._n) diff --git a/cutgeneratingfunctionology/igp/parametric.sage b/cutgeneratingfunctionology/igp/parametric.sage index c77cffe9c..95b242b24 100644 --- a/cutgeneratingfunctionology/igp/parametric.sage +++ b/cutgeneratingfunctionology/igp/parametric.sage @@ -862,13 +862,15 @@ class ParametricRealField(Field): raise ParametricRealFieldInconsistencyError("New constant constraint {} {} {} is not satisfied".format(lhs, op, rhs)) else: raise ParametricRealFieldInconsistencyError("New constraint {} {} {} is not satisfied by the test point".format(lhs, op, rhs)) - else: #A numerical evaluation of the expression has failed. Assume the partial evaluation of the expression holds. + else: # A numerical evaluation of the expression has failed. Assume the partial evaluation of the expression holds. comparison_val_or_expr = self._partial_eval_factor(comparison) if comparison_val_or_expr in base_ring: if not op(comparison_val_or_expr, 0): raise ParametricRealFieldInconsistencyError("New constant constraint {} {} {} is not satisfied".format(lhs, op, rhs)) - else: # comparision_val_or_expr is algebraic expression, assume the comparison here is comparionsion_val_or_expr. + else: # comparision_val_or_expr is symobolic expression, assume the comparison here is comparionsion_val_or_expr. comparison = comparison_val_or_expr + self.record_factor(comparison, op) + return if comparison in base_ring: return if comparison.denominator() == 1 and comparison.numerator().degree() == 1: diff --git a/cutgeneratingfunctionology/spam/basic_semialgebraic.py b/cutgeneratingfunctionology/spam/basic_semialgebraic.py index 796aec7aa..4ca1471a5 100644 --- a/cutgeneratingfunctionology/spam/basic_semialgebraic.py +++ b/cutgeneratingfunctionology/spam/basic_semialgebraic.py @@ -35,6 +35,9 @@ from sage.structure.sage_object import SageObject from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +class EmptyBSA(Exception): + pass + def _bsa_class(bsa_class): r""" Translate a class nickname to a class. @@ -1070,8 +1073,8 @@ def __contains__(self, point): # override the abstract methods def find_point(self): r""" - Find a point in ``self``. - + Find a point in ``self``. + EXAMPLES:: sage: from cutgeneratingfunctionology.spam.basic_semialgebraic import * @@ -1081,6 +1084,12 @@ def find_point(self): sage: P.add_linear_constraint([2,3],-6,operator.lt) sage: P.find_point() (11/10, 11/15) + sage: P = BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(1) + sage: P.add_linear_constraint([1],0,operator.ge) + sage: P.add_linear_constraint([1],-1,operator.lt) + sage: P.find_point() + EmptyBSA + """ def to_point(g): den = g.divisor() @@ -1091,6 +1100,8 @@ def to_vector(g): if g.is_point() or g.is_closure_point() ] rays = [ to_vector(g) for g in self._polyhedron.generators() if g.is_ray() ] + if self.is_empty(): + raise EmptyBSA("The underlying bsa is empty set.") if points: p = sum(points) / len(points) if rays: diff --git a/cutgeneratingfunctionology/spam/parametric_real_field_element.py b/cutgeneratingfunctionology/spam/parametric_real_field_element.py index a0cd5c55e..f9defcbb0 100644 --- a/cutgeneratingfunctionology/spam/parametric_real_field_element.py +++ b/cutgeneratingfunctionology/spam/parametric_real_field_element.py @@ -176,21 +176,21 @@ def _richcmp_(left, right, op): except FactorUndetermined: # With a partial evaluation, assume the written inequality is true. true_op = op - if true_op == op_LT: - left.parent().assume_comparison(left - right, operator.lt) - elif true_op == op_GT: - left.parent().assume_comparison(left - right, operator.gt) - elif true_op == op_EQ: - left.parent().assume_comparison(right - left, operator.eq) - elif true_op == op_LE: - left.parent().assume_comparison(left - right, operator.le) - elif true_op == op_GE: - left.parent().assume_comparison(left - right, operator.ge) - elif true_op == op_NE: - left.parent().assume_comparison(right - left, operator.ne) - else: - raise ValueError("{} is not a valid richcmp operator".format(op)) - return True + if true_op == op_LT: + left.parent().assume_comparison(left - right, operator.lt) + elif true_op == op_GT: + left.parent().assume_comparison(left - right, operator.gt) + elif true_op == op_EQ: + left.parent().assume_comparison(right - left, operator.eq) + elif true_op == op_LE: + left.parent().assume_comparison(left - right, operator.le) + elif true_op == op_GE: + left.parent().assume_comparison(left - right, operator.ge) + elif true_op == op_NE: + left.parent().assume_comparison(right - left, operator.ne) + else: + raise ValueError("{} is not a valid richcmp operator".format(op)) + return True def __abs__(self): """ From bc3a33511c420b0d1c0574925f0de86374f438b6 Mon Sep 17 00:00:00 2001 From: Acadia Larsen <102884863+ComboProblem@users.noreply.github.com> Date: Wed, 8 Oct 2025 13:08:43 -0700 Subject: [PATCH 29/64] fix in writing output for bkpt container --- cutgeneratingfunctionology/igp/minimal_function_cache.sage | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index 981fa79f1..241f18d9b 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -163,8 +163,8 @@ class BreakpointComplexClassContainer: for file_number in range(num_files): out_file = open(output_file, "w") data_writer = csv.writer(out_file, csv.QUOTE_NONE) - for row in range(max_row): - data_writer.writerow(self._data[max_row * file_number + row]) + for row in range(max_rows): + data_writer.writerow(self._data[max_rows * file_number + row]) out_file.close() output_file = file_name_base[:-1]+"{}".format(file_number) From acf440794caa28699a11a96827ee2357a039a497 Mon Sep 17 00:00:00 2001 From: Acadia Larsen <102884863+ComboProblem@users.noreply.github.com> Date: Wed, 8 Oct 2025 13:15:58 -0700 Subject: [PATCH 30/64] outputfile_name_fix --- cutgeneratingfunctionology/igp/minimal_function_cache.sage | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index 241f18d9b..ed3e684f4 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -164,9 +164,12 @@ class BreakpointComplexClassContainer: out_file = open(output_file, "w") data_writer = csv.writer(out_file, csv.QUOTE_NONE) for row in range(max_rows): - data_writer.writerow(self._data[max_rows * file_number + row]) + try: + data_writer.writerow(self._data[max_rows * file_number + row]) + except IndexError: + break out_file.close() - output_file = file_name_base[:-1]+"{}".format(file_number) + output_file = file_name_base[:-1]+"{}".format(file_number)+".csv" def generate_assumed_symmetric_vertices_continuous(fn, f, bkpt): From b96b3fa0eaa3090afd6d02816e5a60255cf80681 Mon Sep 17 00:00:00 2001 From: Acadia Larsen <102884863+ComboProblem@users.noreply.github.com> Date: Wed, 8 Oct 2025 13:20:29 -0700 Subject: [PATCH 31/64] more file writing fixes --- .../igp/minimal_function_cache.sage | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index ed3e684f4..d6e6f5be1 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -169,7 +169,7 @@ class BreakpointComplexClassContainer: except IndexError: break out_file.close() - output_file = file_name_base[:-1]+"{}".format(file_number)+".csv" + output_file = file_name_base[:-1]+"{}".format(file_number+1)+".csv" def generate_assumed_symmetric_vertices_continuous(fn, f, bkpt): @@ -358,6 +358,9 @@ class PiMinContContainer: out_file = open(output_file, "w") data_writer = csv.writer(out_file, csv.QUOTE_NONE) for row in range(max_row): - data_writer.writerow(self._data[max_row * file_number + row]) + try: + data_writer.writerow(self._data[max_row * file_number + row]) + except IndexError: + break out_file.close() - output_file = file_name_base[:-1]+"{}".format(file_number) + output_file = file_name_base[:-1]+"{}".format(file_number+1)+".csv" From a8ddf95ef698e1cda269cb1ca87feb0529e0ed61 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Thu, 16 Oct 2025 08:31:44 -0700 Subject: [PATCH 32/64] wip --- .../igp/minimal_function_cache.sage | 245 ++++++++++-------- 1 file changed, 138 insertions(+), 107 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index d6e6f5be1..98199a914 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -4,11 +4,14 @@ from cutgeneratingfunctionology.igp import * import csv import os from cutgeneratingfunctionology.spam.basic_semialgebraic import EmptyBSA + + def mod_one(x): if x >= 0: return x - int(x) return x - int(x) + 1 + def add_breakpoint(bkpt): """ Given a breakpoint sequence, creates a list of breakpoint sequences of length one more such that @@ -91,6 +94,130 @@ def nnc_poly_from_bkpt(bkpt, backend=None): return K.make_proof_cell().bsa +def generate_assumed_symmetric_vertices_continuous(fn, f, bkpt): + """ + Assumes the symmetry condition holds for all vertices (x,y) in bkpt's breakpoints complex + such that x+y equiv f. + """ + for i in range(len(bkpt)): + x = bkpt[i] + if x == f: + continue + if x < f: + y = f - x + else: + y = 1 + f - x + fn(x) + fn(y) == 1 + yield (x, y, 0, 0) + + +def value_nnc_polyhedron(bkpt, f_index): + """ + For a given ``bkpt`` seqeunce and ``f_index``, find the value polyhedron which assumes pi_(bkpt, v) is minimal. + """ + n = len(bkpt) + assert(n >= 2) + assert(f_index >= 1) + assert(f_index <= n - 1) + coord_names = [] + val = [None]*(n-1) + for i in range(1,n): + coord_names.append('gamma'+str(i)) + logging.disable(logging.INFO) + K = ParametricRealField(names=coord_names, values = val, mutable_values=True, big_cells=True, allow_refinement=False) + for i in range(n-1): + K.gens()[i] <=1 + K.gens()[i] > 0 + h = piecewise_function_from_breakpoints_and_values(bkpt + [1], [0] + K.gens() + [0], merge=False) + # Assumes minimality for the partially defined function. + for vert in generate_type_1_vertices_continuous(h, operator.ge, bkpt + [1]): + vert + for vert in generate_type_2_vertices_continuous(h, operator.ge, bkpt + [1]): + vert + for vert in generate_assumed_symmetric_vertices_continuous(h, bkpt[f_index], bkpt + [1]): + vert + return K._bsa + +def breakpoint_seq_and_value_nnc_polyhedron(bkpt, f_index): + """ + For a given ``bkpt`` seqeunce and ``f_index``, find the breakpoint and value NNC polyhedron which assumes pi_(b, v) is minimal and Delta mathcal P_bkpt is isomorphic to Delta mathcal P_b. + """ + n = len(bkpt) + assert(n >= 2) + assert(f_index >= 1) + assert(f_index <= n - 1) + coord_names = [] + bkpt_vals = bkpt + vals = bkpt_vals[1:n]+ [None]*(n-1) + for i in range(1,n): + coord_names.append('lambda'+str(i)) + for i in range(1,n): + coord_names.append('gamma'+str(i)) + logging.disable(logging.INFO) + K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, allow_refinement=False) + for i in range(n-1): + K.gens()[i+n-1] <=1 + K.gens()[i+n-1] > 0 + h = piecewise_function_from_breakpoints_and_values([0] + K.gens()[0:n-1] + [1], [0] + K.gens()[n-1:2*n-2] + [0], merge=False) + # Assumes minimality for the partially defined function. + for vert in generate_type_1_vertices_continuous(h, operator.ge, [0] + K.gens()[0:n-1] + [1]): + vert + for vert in generate_type_2_vertices_continuous(h, operator.ge, [0] + K.gens()[0:n-1] + [1]): + vert + for vert in generate_assumed_symmetric_vertices_continuous(h, K.gens()[f_index-1], [0] + K.gens()[0:n-1] + [1]): + vert + return K._bsa + + +def bsa_of_rep_element(bkpt, vals): + """ + Given pi_(bkpt, vals) is {minimal, not minimal}, find BSA subset of R^(2n) such that (bkpt, vals) in BSA and for all p + in BSA, pi_p is {minimal, not minimal}. + + INPUT: (bkpt, vals) are lists or vectors of length n and bkpt is a proper breakpoints sequence and vals + is the corresponding value parameters. + + OUTPUT: A basic semialgebraic set. + """ + n = len(bkpt) + assert(n>=2) + coord_names = [] + for i in range(0,n): + coord_names.append('lambda'+str(i)) + for i in range(0,n): + coord_names.append('gamma'+str(i)) + logging.disable(logging.INFO) + K = ParametricRealField(names=coord_names, values = bkpt+vals, big_cells=True) + h = piecewise_function_from_breakpoints_and_values([0] + K.gens()[1:n] + [1], [0] + K.gens()[n+1:2*n] + [0], merge=False) + minimality_test(h) + return K.make_proof_cell().bsa + + +def find_minimal_function_reps_from_bkpts(bkpts, backend=None): + """ + Finds representative elements of minimal functions from a given breakpoint sequence. + """ + rep_elems = [] + for bkpt in bkpts: + n = len(bkpt) + for f_index in range(1, n): + poly_bsa = breakpoint_seq_and_value_nnc_polyhedron(bkpt, f_index) + gammas = poly_bsa.polynomial_map()[0].parent().gens()[n-1:] + lambdas = poly_bsa.polynomial_map()[0].parent().gens()[:n-1] + test_point = poly_bsa.upstairs().find_point() + test_bkpt = [0] + test_val = [0] + for lambda_i in lambdas: + test_bkpt.append(test_point[poly_bsa.v_dict()[lambda_i]]) + for gamma_i in gammas: + test_val.append(test_point[poly_bsa.v_dict()[gamma_i]]) + h = piecewise_function_from_breakpoints_and_values(test_bkpt+[1], test_val+[0]) + if not minimality_test(h): # test bkpt doesn't make a valid minimal function, the statement still holds with original bkpt + test_bkpt = bkpt + rep_elems.append((test_bkpt, test_val)) + return rep_elems + + class BreakpointComplexClassContainer: """ A container for the family of breakpoint complexes for peicewise linear functions @@ -155,6 +282,7 @@ class BreakpointComplexClassContainer: else: file_name_base =output_file_name_style if max_rows is not None: + assert(max_rows >= 1) num_files = len(self._data)//max_rows + 1 file_name_base = file_name_base + "_part_0" if max_rows is None: @@ -172,106 +300,6 @@ class BreakpointComplexClassContainer: output_file = file_name_base[:-1]+"{}".format(file_number+1)+".csv" -def generate_assumed_symmetric_vertices_continuous(fn, f, bkpt): - """ - Assumes the symmetry condition holds for all vertices (x,y) in bkpt's breakpoints complex - such that x+y equiv f. - """ - for i in range(len(bkpt)): - x = bkpt[i] - if x == f: - continue - if x < f: - y = f - x - else: - y = 1 + f - x - fn(x) + fn(y) == 1 - yield (x, y, 0, 0) - - -def assume_minimality(bkpt, f_index, backend=None): - """ - Given a breakpoint sequence, bkpt, and an index for f, f_index, determine if there is a (rep_bkpt, v) - such that pi_(rep_bkpt,v) is minimal, pi_(bkpt,v)(lambda_f_index)=1, and rep_bkpt's breakpoint complex - is isomorphic to bkpt's breakpoint complex. - - INPUT: bkpt a list or vector of length n. bkpt is assumed to be breakpoint sequence, f_index an integer. - - OUTPUT: (rep_bkpt, v), a pair of lists of length n with the described property. - """ - n = len(bkpt) - assert(n >= 2) - assert(f_index >= 1) - assert(f_index <= n - 1) - coord_names = [] - bkpt_vals = bkpt - vals = bkpt_vals[1:n]+ [None]*(n-1) - for i in range(1,n): - coord_names.append('lambda'+str(i)) - for i in range(1,n): - coord_names.append('gamma'+str(i)) - logging.disable(logging.INFO) - K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, allow_refinement=False) - for i in range(n-1): - K.gens()[i+n-1] <=1 - K.gens()[i+n-1] > 0 - h = piecewise_function_from_breakpoints_and_values([0] + K.gens()[0:n-1] + [1], [0] + K.gens()[n-1:2*n-2] + [0], merge=False) - for vert in generate_type_1_vertices_continuous(h, operator.ge, [0] + K.gens()[0:n-1] + [1]): - vert - for vert in generate_type_2_vertices_continuous(h, operator.ge, [0] + K.gens()[0:n-1] + [1]): - vert - for vert in generate_assumed_symmetric_vertices_continuous(h, K.gens()[f_index-1], [0] + K.gens()[0:n-1] + [1]): - vert - try: - K.find_test_point() - except EmptyBSA: - return - h_2 = piecewise_function_from_breakpoints_and_values([0]+list(K._values[0:n-1])+[1], [0] + list(K._values[n-1:2*n-2])+[0]) - is_minimal = minimality_test(h_2) - if is_minimal: - rep_bkpt = [0] + list(K._values[0:n-1]) - v = [0] + list(K._values[n-1:2*n-2]) - return (rep_bkpt, v) - return - - - -def bsa_of_rep_element(bkpt, vals): - """ - Given pi_(bkpt, vals) is {minimal, not minimal}, find BSA subset of R^(2n) such that (bkpt, vals) in BSA and for all p - in BSA, pi_p is {minimal, not minimal}. - - INPUT: (bkpt, vals) are lists or vectors of length n and bkpt is a proper breakpoints sequence and vals - is the corresponding value parameters. - - OUTPUT: A basic semialgebraic set. - """ - n = len(bkpt) - assert(n>=2) - coord_names = [] - for i in range(0,n): - coord_names.append('lambda'+str(i)) - for i in range(0,n): - coord_names.append('gamma'+str(i)) - logging.disable(logging.INFO) - K = ParametricRealField(names=coord_names, values = bkpt+vals, big_cells=True) - h = piecewise_function_from_breakpoints_and_values([0] + K.gens()[1:n] + [1], [0] + K.gens()[n+1:2*n] + [0], merge=False) - minimality_test(h) - return K.make_proof_cell().bsa - - -def find_minimal_function_reps_from_bkpts(bkpts, backend=None): - """ - Finds representative elements of minimal functions if they exist from the given breakpoint sequence. - """ - data = [] - for bkpt in bkpts: - for i in range(1, len(bkpt)): - result = assume_minimality(bkpt, i, backend) - if result is not None: - data.append(result) - return data - class PiMinContContainer: """ A container for the space of continuous piecewise linear minimal functions with at @@ -296,17 +324,19 @@ class PiMinContContainer: file_names = kwrds["load_bkpt_data"].split(",") bkpts = [] for file_name in file_names: - file = open(file_name, "r") - bkpts += [eval(preparse(data)) for data in list(csv.reader(file))] - close(file) + with open(file_name, newline='' ) as csvfile: + file_reader = csv.reader(csvfile) + for row in file_reader: + bkpts.append([eval(preparse(data)) for data in row]) self._data = find_minimal_function_reps_from_bkpts(bkpts) elif "load_bkpt_data" not in kwrds.keys() and "load_rep_elem_data" in kwrds.keys(): - file_names = kwrds["load_rep_elem_data"].split(",") + file_names = kwrds["load_rep_elem_data"].strip(" ").split(",") self._data = [] for file_name in file_names: - file = open(file_name, "r") - self._data += [(eval(preparse(data[0])), eval(preparse(data[1]))) for data in list(csv.reader(file))] - file.close() + with open(file_name, newline='' ) as csvfile: + file_reader = csv.reader(csvfile) + for row in file_reader: + self._data.append([eval(preparse(data)) for data in row]) else: logging.warning("Generating representative elements. This might take a while.") bkpts = make_bkpts_with_len_n(self._n) @@ -349,6 +379,7 @@ class PiMinContContainer: else: file_name_base =output_file_name_style if max_rows is not None: + assert(max_rows >= 1) num_files = len(self._data)//max_rows + 1 file_name_base = file_name_base + "_part_0" if max_rows is None: From c293381702d2a038ebf31586559e3519956e3e66 Mon Sep 17 00:00:00 2001 From: Acadia Larsen <102884863+ComboProblem@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:14:30 -0700 Subject: [PATCH 33/64] wip pt 2 --- cutgeneratingfunctionology/igp/minimal_function_cache.sage | 1 + 1 file changed, 1 insertion(+) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index 98199a914..ddae091bf 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -384,6 +384,7 @@ class PiMinContContainer: file_name_base = file_name_base + "_part_0" if max_rows is None: max_rows = 0 + num_files = 1 output_file = file_name_base +".csv" for file_number in range(num_files): out_file = open(output_file, "w") From 3dc4868f13ca893a1fabc0e5c641b6ed20d14562 Mon Sep 17 00:00:00 2001 From: Acadia Larsen <102884863+ComboProblem@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:23:09 -0700 Subject: [PATCH 34/64] wip pt3 --- cutgeneratingfunctionology/igp/minimal_function_cache.sage | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index ddae091bf..0af1b1971 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -389,9 +389,9 @@ class PiMinContContainer: for file_number in range(num_files): out_file = open(output_file, "w") data_writer = csv.writer(out_file, csv.QUOTE_NONE) - for row in range(max_row): + for row in range(max_rows): try: - data_writer.writerow(self._data[max_row * file_number + row]) + data_writer.writerow(self._data[max_rows * file_number + row]) except IndexError: break out_file.close() From 3f3c514f5da13dba1f2bc2975a7b7dab948d75af Mon Sep 17 00:00:00 2001 From: Acadia Larsen <102884863+ComboProblem@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:32:44 -0700 Subject: [PATCH 35/64] wip pt4 --- cutgeneratingfunctionology/igp/minimal_function_cache.sage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index 0af1b1971..d73a2850c 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -383,7 +383,7 @@ class PiMinContContainer: num_files = len(self._data)//max_rows + 1 file_name_base = file_name_base + "_part_0" if max_rows is None: - max_rows = 0 + max_rows = len(self._data) num_files = 1 output_file = file_name_base +".csv" for file_number in range(num_files): From 4bdea7a1d2aa6390040554bef36a0e97e431a514 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Mon, 24 Nov 2025 17:16:38 -0800 Subject: [PATCH 36/64] fix nnc poly from bkpt ' --- .../igp/minimal_function_cache.sage | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index d73a2850c..b570b8c49 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -25,8 +25,8 @@ def add_breakpoint(bkpt): TESTS:: >>> add_breakpoint([0]) [[0, 3/4], [0, 1/2], [0, 1/4]] - >>> add_breakpoint([0,1/3]) [[0, 1/12, 1/3], [0, 1/6, 1/3], [0, 1/4, 1/3], [0, 1/3, 2/3], [0, 1/3, 5/12], [0, 1/3, 1/2], [0, 1/3, 7/12], [0, 1/3, 5/6]] + >>> add_breakpoint([0,1/3]) """ possible_new_seqs = [] possible_eq_lambda_star = [b/2 for b in bkpt+[1]] + [(1+b)/2 for b in bkpt] @@ -86,12 +86,34 @@ def nnc_poly_from_bkpt(bkpt, backend=None): for i in range(n-1): K.gens()[i] < K.gens()[i+1] K.gens()[n-1] < 1 - h = piecewise_function_from_breakpoints_and_values([0] + K.gens()[0:n-1] + [1], [0]*(n+1), merge=False) - for vert in generate_type_1_vertices_continuous(h, operator.ge, [0] + K.gens()[0:n-1] + [1]): - vert - for vert in generate_type_2_vertices_continuous(h, operator.ge, [0] + K.gens()[0:n-1] + [1]): - vert - return K.make_proof_cell().bsa + # write intervals + bkpt_right_invervals = [ right_open_interval(lambda_k, lambda_k_plus_one) for lambda_k, lambda_k_plus_one in zip(K.gens(), K.gens()[1:]+[1]) ] + + # This next block of writes (in a mathematical way) a description of the complex in terms of the breakpoint parameters for + # type two verticies along the line y=x in [0,1)times [0,1). These inequalities are necessary and sufficient to detemine a bkpt complex. + # There is likely a shorter way of writing this block of code. + print(bkpt_right_invervals) + for lambda_i in K.gens(): + for interval in bkpt_right_invervals: + if 2*lambda_i >= 1: + if is_pt_in_interval(interval, 2*lambda_i -1): + if 2*lambda_i - 1 == interval[0]: # alias for lambda_k + 2*lambda_i - 1 == interval[0] + break + else: + interval[0] < 2*lambda_i - 1 + 2*lambda_i - 1 < interval[1] + break + else: + if is_pt_in_interval(interval, 2*lambda_i): + if 2*lambda_i == interval[0]: # alias for lambda_k + 2*lambda_i == interval[0] + break + else: + interval[0] < 2*lambda_i + 2*lambda_i < interval[1] + break + return K._bsa def generate_assumed_symmetric_vertices_continuous(fn, f, bkpt): @@ -252,7 +274,7 @@ class BreakpointComplexClassContainer: self._data = make_bkpts_with_len_n(self._n) def __repr__(self): - return "Container of a family of breakpoint" + return f"Container for the space breakpoint sequences of length {self._n} under equivlance of polyhedral complexes." def get_rep_elems(self): for bkpt in self._data: @@ -260,7 +282,8 @@ class BreakpointComplexClassContainer: def get_nnc_poly_from_bkpt(self): for bkpt in self._data: - yield nnc_poly_from_bkpt(bkpt) + for f_index in range(1,n): + yield nnc_poly_from_bkpt(bkpt, f_index) def num_rep_elems(self): return len(self._data) From 3daa98cf95960615df2f13fee568cdd08625f7cb Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:37:46 -0800 Subject: [PATCH 37/64] update value polys, bkpt gen, and rep elem gen --- .../igp/minimal_function_cache.sage | 325 ++++++++++++------ 1 file changed, 211 insertions(+), 114 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index b570b8c49..f430d4dbc 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -4,48 +4,110 @@ from cutgeneratingfunctionology.igp import * import csv import os from cutgeneratingfunctionology.spam.basic_semialgebraic import EmptyBSA - -def mod_one(x): - if x >= 0: - return x - int(x) - return x - int(x) + 1 +class RepElemGenFailure(Exception): + pass -def add_breakpoint(bkpt): +def add_breakpoints_and_find_equiv_classes(bkpt_poly): """ - Given a breakpoint sequence, creates a list of breakpoint sequences of length one more such that - each the breakpoint complex of each breakpoint sequence is not guaranteed to be isomorphic to any - other breakpoint sequence in the list. - + Takes dim k-1 breakpoint NNC polyhedron (as a :class:`BasicSemialgebraicSet_base`) and finds rep elements + """ + # BSAs are highly mutable, work only with copies. + B_cap_N_b = copy(bkpt_poly) + B_cap_N_b.add_space_dimensions_and_embed(1) + # get new number of breakpoints + k = B_cap_N_b.ambient_dim() + # if k< 2: + # raise ValueError("bkpt_poly should have space dim at least 1.") + model_bound_bkpts = [0]*k + model_bound_bkpts[k-1] = 1 + # 0 < lambda_k <1 + B_cap_N_b.add_linear_constraint(model_bound_bkpts, -1, operator.lt) # model bounds + B_cap_N_b.add_linear_constraint(model_bound_bkpts, 0, operator.gt) # model bounds + bkpt_order = [0]*k + bkpt_order[k-2] = 1 + bkpt_order[k-1] = -1 + B_cap_N_b.add_linear_constraint(bkpt_order, 0, operator.lt) # order on bkpts + # print(B_cap_N_b) + # rep elem list + rep_elems = [] + for j in range(k-1): + for i in range(k): + for interval_w in [0,1]: + for line_w in [0,1]: + # which interval is (lambda_k,lambda_k) located in? + # modeled lambda_i op 2lambda_k - w < lambda_{i+1} + for interval_op in [operator.lt, operator.eq, operator.gt]: + for line_op in [operator.lt, operator.eq, operator.gt]: + # highly mutable objects, operater on the copy + B_cap_N_b_copy = copy(B_cap_N_b) + lhs_i = [0]*k + lhs_i[k-1] = -2 + lhs_i[i] = 1 + B_cap_N_b_copy.add_linear_constraint(lhs_i, interval_w, interval_op) + lhs_i_plus_1 = [0]*k + lhs_i_plus_1[k-1] = -2 + if i < k-1: + lhs_i_plus_1[i+1] = 1 + B_cap_N_b_copy.add_linear_constraint(lhs_i_plus_1, interval_w, operator.gt) + else: + B_cap_N_b_copy.add_linear_constraint(lhs_i_plus_1, interval_w + 1, operator.gt) + if not B_cap_N_b_copy.is_empty(): + # does the line x+y equiv lambda_k mod 1 lie on/above/below (lambda_j,lambda_j)? + # modeled by 2lambda_j op lambda_k + w + lhs_j = [0]*k + lhs_j[j] = 2 + lhs_j[k-1] = -1 + B_cap_N_b_copy.add_linear_constraint(lhs_j, -line_w, line_op) + try: + rep_elem = B_cap_N_b_copy.find_point() + rep_elems.append(tuple(rep_elem)) + except EmptyBSA: + pass + return unique_list(rep_elems) + +def nnc_poly_from_bkpt_sequence(bkpt, backend=None): + n = len(bkpt) + # assert(n >= 2) + coord_names = [] + bkpt_vals = bkpt + vals = bkpt_vals[0:n] + bkpt_extd = list(bkpt)+[1] + for i in range(0,n): + coord_names.append('lambda'+str(i)) + K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True) + logging.disable(logging.INFO) + K.gens()[0] == 0 + for i in range(n-1): + K.gens()[i] < K.gens()[i+1] + K.gens()[n-1] < 1 + for i in range(n): + for j in range(n): + if bkpt[i]+bkpt[j]>= 1: + w = 1 + else: + w = 0 + for k in range(n): + if bkpt_extd[k] < bkpt[i]+bkpt[j] - w and bkpt[i]+bkpt[j] - w < bkpt_extd[k+1]: + if k != n-1: + K.gens()[k] < K.gens()[i] + K.gens()[j] - w + K.gens()[i] + K.gens()[j] - w < K.gens()[k+1] + else: + K.gens()[k] < K.gens()[i] + K.gens()[j] - w + K.gens()[i] + K.gens()[j] - w < 1 + elif bkpt_extd[k] == bkpt[i]+bkpt[j] - w: + if k != n-1: + K.gens()[k] == K.gens()[i] + K.gens()[j] - w + K.gens()[i] + K.gens()[j] - w < K.gens()[k+1] + else: + K.gens()[k] == K.gens()[i] + K.gens()[j] - w + K.gens()[i] + K.gens()[j] - w < 1 + return K._bsa - INPUT: Assume vector or list of n breakpoints with lambda_0=0 and lambda_i>> add_breakpoint([0]) - [[0, 3/4], [0, 1/2], [0, 1/4]] - [[0, 1/12, 1/3], [0, 1/6, 1/3], [0, 1/4, 1/3], [0, 1/3, 2/3], [0, 1/3, 5/12], [0, 1/3, 1/2], [0, 1/3, 7/12], [0, 1/3, 5/6]] - >>> add_breakpoint([0,1/3]) - """ - possible_new_seqs = [] - possible_eq_lambda_star = [b/2 for b in bkpt+[1]] + [(1+b)/2 for b in bkpt] - intervals_along_y_eq_x = sorted(list(set(tuple(bkpt + [1] + [b/2 for b in bkpt+[1]] + [(1+b)/2 for b in bkpt])))) - possible_ne_lambda_star = [] - for lower_bound, upper_bound in pairwise(intervals_along_y_eq_x): - possible_ne_lambda_star.append(1/2 * (lower_bound + upper_bound)) - possible_lambda_star = possible_eq_lambda_star + possible_ne_lambda_star - for lambda_star in possible_eq_lambda_star + possible_ne_lambda_star: - temp_bkpt = deepcopy(bkpt) - temp_bkpt.append(mod_one(lambda_star)) - possible_new_seqs.append(sorted(temp_bkpt)) - possible_new_seqs = [list(y) for y in set([tuple(x) for x in possible_new_seqs]) if len(set(y)) == len(y)] - return possible_new_seqs - - - -def make_bkpts_with_len_n(n, k=1, bkpts=None): - """ +def make_rep_bkpts_with_len_n(n, k=1, bkpts=None): + r""" Produce representative elements of every isomorphism class of breakpoints complexes for breakpoint sequences of length n. Note, this function does not check that the data input is correct and assume it is being used correctly. @@ -63,57 +125,13 @@ def make_bkpts_with_len_n(n, k=1, bkpts=None): if k == 1 and bkpts is None: bkpts=[[0]] for bkpt in bkpts: - new_bkpts += add_breakpoint(bkpt) - new_bkpts = [list(y) for y in set([tuple(x) for x in new_bkpts])] + new_bkpts += add_breakpoints_and_find_equiv_classes(nnc_poly_from_bkpt_sequence(bkpt).upstairs()) + new_bkpts = unique_list(new_bkpts) k += 1 if k == n: return new_bkpts else: - return make_bkpts_with_len_n(n, k, new_bkpts) - - -def nnc_poly_from_bkpt(bkpt, backend=None): - n = len(bkpt) - assert(n >= 2) - coord_names = [] - bkpt_vals = bkpt - vals = bkpt_vals[0:n] - for i in range(0,n): - coord_names.append('lambda'+str(i)) - K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True) - logging.disable(logging.INFO) - K.gens()[0] == 0 - for i in range(n-1): - K.gens()[i] < K.gens()[i+1] - K.gens()[n-1] < 1 - # write intervals - bkpt_right_invervals = [ right_open_interval(lambda_k, lambda_k_plus_one) for lambda_k, lambda_k_plus_one in zip(K.gens(), K.gens()[1:]+[1]) ] - - # This next block of writes (in a mathematical way) a description of the complex in terms of the breakpoint parameters for - # type two verticies along the line y=x in [0,1)times [0,1). These inequalities are necessary and sufficient to detemine a bkpt complex. - # There is likely a shorter way of writing this block of code. - print(bkpt_right_invervals) - for lambda_i in K.gens(): - for interval in bkpt_right_invervals: - if 2*lambda_i >= 1: - if is_pt_in_interval(interval, 2*lambda_i -1): - if 2*lambda_i - 1 == interval[0]: # alias for lambda_k - 2*lambda_i - 1 == interval[0] - break - else: - interval[0] < 2*lambda_i - 1 - 2*lambda_i - 1 < interval[1] - break - else: - if is_pt_in_interval(interval, 2*lambda_i): - if 2*lambda_i == interval[0]: # alias for lambda_k - 2*lambda_i == interval[0] - break - else: - interval[0] < 2*lambda_i - 2*lambda_i < interval[1] - break - return K._bsa + return make_rep_bkpts_with_len_n(n, k, new_bkpts) def generate_assumed_symmetric_vertices_continuous(fn, f, bkpt): @@ -141,13 +159,45 @@ def value_nnc_polyhedron(bkpt, f_index): assert(n >= 2) assert(f_index >= 1) assert(f_index <= n - 1) + if not isinstance(bkpt, list): + bkpt = list(bkpt) + coord_names = [] + val = [None]*(n) + for i in range(n): + coord_names.append('gamma'+str(i)) + logging.disable(logging.INFO) + K = ParametricRealField(names=coord_names, values = val, mutable_values=True, big_cells=True, allow_refinement=False) + K.gens()[0] == 0 + for i in range(1, n): + K.gens()[i] <=1 + K.gens()[i] > 0 + h = piecewise_function_from_breakpoints_and_values(bkpt + [1], K.gens() + [0], merge=False) + # Assumes minimality for the partially defined function. + for vert in generate_type_1_vertices_continuous(h, operator.ge, bkpt + [1]): + vert + for vert in generate_type_2_vertices_continuous(h, operator.ge, bkpt + [1]): + vert + for vert in generate_assumed_symmetric_vertices_continuous(h, bkpt[f_index], bkpt + [1]): + vert + return K._bsa + +def value_nnc_polyhedron_gamma_0_not_as_param(bkpt, f_index): + """ + For a given ``bkpt`` seqeunce and ``f_index``, find the value polyhedron which assumes pi_(bkpt, v) is minimal. + """ + n = len(bkpt) + assert(n >= 2) + assert(f_index >= 1) + assert(f_index <= n - 1) + if not isinstance(bkpt, list): + bkpt = list(bkpt) coord_names = [] val = [None]*(n-1) - for i in range(1,n): + for i in range(1, n): coord_names.append('gamma'+str(i)) logging.disable(logging.INFO) K = ParametricRealField(names=coord_names, values = val, mutable_values=True, big_cells=True, allow_refinement=False) - for i in range(n-1): + for i in range(1, n): K.gens()[i] <=1 K.gens()[i] > 0 h = piecewise_function_from_breakpoints_and_values(bkpt + [1], [0] + K.gens() + [0], merge=False) @@ -163,30 +213,36 @@ def value_nnc_polyhedron(bkpt, f_index): def breakpoint_seq_and_value_nnc_polyhedron(bkpt, f_index): """ For a given ``bkpt`` seqeunce and ``f_index``, find the breakpoint and value NNC polyhedron which assumes pi_(b, v) is minimal and Delta mathcal P_bkpt is isomorphic to Delta mathcal P_b. + + EXAMPLES:: + """ n = len(bkpt) assert(n >= 2) assert(f_index >= 1) - assert(f_index <= n - 1) + assert(f_index <= n) coord_names = [] - bkpt_vals = bkpt - vals = bkpt_vals[1:n]+ [None]*(n-1) - for i in range(1,n): + bkpt_vals = list(bkpt) + vals = bkpt_vals + [None]*(n) + for i in range(0,n): coord_names.append('lambda'+str(i)) - for i in range(1,n): + for i in range(0,n): coord_names.append('gamma'+str(i)) logging.disable(logging.INFO) K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, allow_refinement=False) - for i in range(n-1): - K.gens()[i+n-1] <=1 - K.gens()[i+n-1] > 0 - h = piecewise_function_from_breakpoints_and_values([0] + K.gens()[0:n-1] + [1], [0] + K.gens()[n-1:2*n-2] + [0], merge=False) + # gamma_0 == 0 + K.gens()[n] == 0 + # 0 < gamma_i <= 1 + for i in range(1, n): + K.gens()[i+n] <=1 + K.gens()[i+n] > 0 + h = piecewise_function_from_breakpoints_and_values(K.gens()[0:n] + [1], K.gens()[n:2*n] + [0], merge=False) # Assumes minimality for the partially defined function. - for vert in generate_type_1_vertices_continuous(h, operator.ge, [0] + K.gens()[0:n-1] + [1]): + for vert in generate_type_1_vertices_continuous(h, operator.ge, K.gens()[0:n] + [1]): vert - for vert in generate_type_2_vertices_continuous(h, operator.ge, [0] + K.gens()[0:n-1] + [1]): + for vert in generate_type_2_vertices_continuous(h, operator.ge, K.gens()[0:n] + [1]): vert - for vert in generate_assumed_symmetric_vertices_continuous(h, K.gens()[f_index-1], [0] + K.gens()[0:n-1] + [1]): + for vert in generate_assumed_symmetric_vertices_continuous(h, K.gens()[f_index-1], [0] + K.gens()[0:n] + [1]): vert return K._bsa @@ -200,6 +256,10 @@ def bsa_of_rep_element(bkpt, vals): is the corresponding value parameters. OUTPUT: A basic semialgebraic set. + + EXAMPLES:: + + """ n = len(bkpt) assert(n>=2) @@ -210,7 +270,39 @@ def bsa_of_rep_element(bkpt, vals): coord_names.append('gamma'+str(i)) logging.disable(logging.INFO) K = ParametricRealField(names=coord_names, values = bkpt+vals, big_cells=True) - h = piecewise_function_from_breakpoints_and_values([0] + K.gens()[1:n] + [1], [0] + K.gens()[n+1:2*n] + [0], merge=False) + h = piecewise_function_from_breakpoints_and_values(K.gens()[0:n] + [1], K.gens()[n:2*n] + [0], merge=False) + minimality_test(h) + return K.make_proof_cell().bsa + + +def bsa_of_rep_element_pi_of_0_not_param(bkpt, vals): + """ + Given pi_(bkpt, vals) is {minimal, not minimal}, find BSA subset of R^(2n) such that (bkpt, vals) in BSA and for all p + in BSA, pi_p is {minimal, not minimal}. + + INPUT: (bkpt, vals) are lists or vectors of length n and bkpt is a proper breakpoints sequence and vals + is the corresponding value parameters. + + OUTPUT: A basic semialgebraic set. + + EXAMPLES:: + + + """ + n = len(bkpt) + if not isinstance(bkpt, list): + bkpt = list(bkpt) + if not isinstance(vals, list): + vals = list(vals) + assert(n>=2) + coord_names = [] + for i in range(1,n): + coord_names.append('lambda'+str(i)) + for i in range(1,n): + coord_names.append('gamma'+str(i)) + logging.disable(logging.INFO) + K = ParametricRealField(names=coord_names, values = bkpt[1:]+vals[1:], big_cells=True) + h = piecewise_function_from_breakpoints_and_values([0]+K.gens()[:n-1] + [1], [0] + K.gens()[n-1:] + [0], merge=False) minimality_test(h) return K.make_proof_cell().bsa @@ -223,20 +315,19 @@ def find_minimal_function_reps_from_bkpts(bkpts, backend=None): for bkpt in bkpts: n = len(bkpt) for f_index in range(1, n): - poly_bsa = breakpoint_seq_and_value_nnc_polyhedron(bkpt, f_index) - gammas = poly_bsa.polynomial_map()[0].parent().gens()[n-1:] - lambdas = poly_bsa.polynomial_map()[0].parent().gens()[:n-1] - test_point = poly_bsa.upstairs().find_point() - test_bkpt = [0] - test_val = [0] - for lambda_i in lambdas: - test_bkpt.append(test_point[poly_bsa.v_dict()[lambda_i]]) + poly_bsa = value_nnc_polyhedron(list(bkpt), f_index) + gammas = poly_bsa.polynomial_map()[0].parent().gens() + try: + test_point = poly_bsa.upstairs().find_point() + except EmptyBSA: + raise RepElemGenFailure("The value polyhedron {} is empty. This should not be empty. Double check inputs".format(poly_bsa)) + test_val = [] for gamma_i in gammas: test_val.append(test_point[poly_bsa.v_dict()[gamma_i]]) - h = piecewise_function_from_breakpoints_and_values(test_bkpt+[1], test_val+[0]) + h = piecewise_function_from_breakpoints_and_values(list(bkpt)+[1], test_val+[0]) if not minimality_test(h): # test bkpt doesn't make a valid minimal function, the statement still holds with original bkpt - test_bkpt = bkpt - rep_elems.append((test_bkpt, test_val)) + raise ValueError("HELP! ({}, {}) paramaterized is not a minimal function but assuming a breakpoint sequence is input, this should be minimal. GL debugging.".format(bkpt, test_val)) + rep_elems.append((bkpt, test_val)) return rep_elems @@ -244,6 +335,9 @@ class BreakpointComplexClassContainer: """ A container for the family of breakpoint complexes for peicewise linear functions with at most n breakpoints. + + EXAMPLES:: + """ def __init__(self, n, **kwrds): self._n = n @@ -256,7 +350,7 @@ class BreakpointComplexClassContainer: if "load_rep_elem_data" in kwrds.keys(): if kwrds[load_rep_elem_data] is None: logging.warning("Generating representative elements. This might take a while.") - self._data = make_bkpts_with_len_n(self._n) + self._data = make_rep_bkpts_with_len_n(self._n) else: file_names = kwrds["load_bkpt_data"].split(",") self._data = [] @@ -268,10 +362,10 @@ class BreakpointComplexClassContainer: if kwrds[gen_elems_from_data] == True: k = len(self._data[0]) if k < n: - self._data = make_bkpts_with_len_n(n, k, self._data) + self._data = make_rep_bkpts_with_len_n(n, k, self._data) else: logging.warning("Generating representative elements. This might take a while.") - self._data = make_bkpts_with_len_n(self._n) + self._data = make_rep_bkpts_with_len_n(self._n) def __repr__(self): return f"Container for the space breakpoint sequences of length {self._n} under equivlance of polyhedral complexes." @@ -362,7 +456,7 @@ class PiMinContContainer: self._data.append([eval(preparse(data)) for data in row]) else: logging.warning("Generating representative elements. This might take a while.") - bkpts = make_bkpts_with_len_n(self._n) + bkpts = make_rep_bkpts_with_len_n(self._n) self._data = find_minimal_function_reps_from_bkpts(bkpts) def __repr__(self): @@ -374,7 +468,7 @@ class PiMinContContainer: def get_rep_elems(self): for b, v in self._data: - yield (b, v) + yield (list(b), list(v)) def get_rep_functions(self): for b, v in self._data: @@ -419,3 +513,6 @@ class PiMinContContainer: break out_file.close() output_file = file_name_base[:-1]+"{}".format(file_number+1)+".csv" + + +### Plotting Utilties ### \ No newline at end of file From eec2deb48d2405e4b10585311b169b786979629d Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Fri, 30 Jan 2026 08:21:50 -0800 Subject: [PATCH 38/64] update value ploy, num params --- .../igp/minimal_function_cache.sage | 86 ++++++++++--------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index f430d4dbc..2b41b4a5b 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -151,10 +151,12 @@ def generate_assumed_symmetric_vertices_continuous(fn, f, bkpt): yield (x, y, 0, 0) -def value_nnc_polyhedron(bkpt, f_index): +def value_nnc_polyhedron_value_cords(bkpt, f_index): """ - For a given ``bkpt`` seqeunce and ``f_index``, find the value polyhedron which assumes pi_(bkpt, v) is minimal. + For a given ``bkpt`` seqeunce and ``f_index``, write the value polyhedron as a BSA in only the value parameters. """ + # this saves a slight amount of overhead when detemrining points for the value polyhedron since the assumed + # minimality test does not have to entierly go though parametric real field. n = len(bkpt) assert(n >= 2) assert(f_index >= 1) @@ -181,38 +183,38 @@ def value_nnc_polyhedron(bkpt, f_index): vert return K._bsa -def value_nnc_polyhedron_gamma_0_not_as_param(bkpt, f_index): - """ - For a given ``bkpt`` seqeunce and ``f_index``, find the value polyhedron which assumes pi_(bkpt, v) is minimal. - """ - n = len(bkpt) - assert(n >= 2) - assert(f_index >= 1) - assert(f_index <= n - 1) - if not isinstance(bkpt, list): - bkpt = list(bkpt) - coord_names = [] - val = [None]*(n-1) - for i in range(1, n): - coord_names.append('gamma'+str(i)) - logging.disable(logging.INFO) - K = ParametricRealField(names=coord_names, values = val, mutable_values=True, big_cells=True, allow_refinement=False) - for i in range(1, n): - K.gens()[i] <=1 - K.gens()[i] > 0 - h = piecewise_function_from_breakpoints_and_values(bkpt + [1], [0] + K.gens() + [0], merge=False) - # Assumes minimality for the partially defined function. - for vert in generate_type_1_vertices_continuous(h, operator.ge, bkpt + [1]): - vert - for vert in generate_type_2_vertices_continuous(h, operator.ge, bkpt + [1]): - vert - for vert in generate_assumed_symmetric_vertices_continuous(h, bkpt[f_index], bkpt + [1]): - vert - return K._bsa +# def value_nnc_polyhedron_gamma_0_not_as_param(bkpt, f_index): + # """ + # For a given ``bkpt`` seqeunce and ``f_index``, find the value polyhedron which assumes pi_(bkpt, v) is minimal. + # """ + # n = len(bkpt) + # assert(n >= 2) + # assert(f_index >= 1) + # assert(f_index <= n - 1) + # if not isinstance(bkpt, list): + # bkpt = list(bkpt) + # coord_names = [] + # val = [None]*(n-1) + # for i in range(1, n): + # coord_names.append('gamma'+str(i)) + # logging.disable(logging.INFO) + # K = ParametricRealField(names=coord_names, values = val, mutable_values=True, big_cells=True, allow_refinement=False) + # for i in range(1, n): + # K.gens()[i] <=1 + # K.gens()[i] > 0 + # h = piecewise_function_from_breakpoints_and_values(bkpt + [1], [0] + K.gens() + [0], merge=False) + # # Assumes minimality for the partially defined function. + # for vert in generate_type_1_vertices_continuous(h, operator.ge, bkpt + [1]): + # vert + # for vert in generate_type_2_vertices_continuous(h, operator.ge, bkpt + [1]): + # vert + # for vert in generate_assumed_symmetric_vertices_continuous(h, bkpt[f_index], bkpt + [1]): + # vert + # return K._bsa -def breakpoint_seq_and_value_nnc_polyhedron(bkpt, f_index): +def value_nnc_polyhedron(bkpt, f_index): """ - For a given ``bkpt`` seqeunce and ``f_index``, find the breakpoint and value NNC polyhedron which assumes pi_(b, v) is minimal and Delta mathcal P_bkpt is isomorphic to Delta mathcal P_b. + For a given ``bkpt`` seqeunce and ``f_index``, write a base which is the value polyhedron corrospoding in the full space of parameters. EXAMPLES:: @@ -224,19 +226,21 @@ def breakpoint_seq_and_value_nnc_polyhedron(bkpt, f_index): coord_names = [] bkpt_vals = list(bkpt) vals = bkpt_vals + [None]*(n) - for i in range(0,n): + for i in range(n): coord_names.append('lambda'+str(i)) - for i in range(0,n): + for i in range(n): coord_names.append('gamma'+str(i)) logging.disable(logging.INFO) K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, allow_refinement=False) - # gamma_0 == 0 + # breakpoint parameters are the mesured breakpoint values. + for i in range(n): + K.gens()[i] == bkpt[i] + # necessary conditions on value parameters K.gens()[n] == 0 - # 0 < gamma_i <= 1 for i in range(1, n): K.gens()[i+n] <=1 K.gens()[i+n] > 0 - h = piecewise_function_from_breakpoints_and_values(K.gens()[0:n] + [1], K.gens()[n:2*n] + [0], merge=False) + h = piecewise_function_from_breakpoints_and_values(K.gens()[0:n] + [1], K.gens()[n:2*n] + [0], merge=False) # Assumes minimality for the partially defined function. for vert in generate_type_1_vertices_continuous(h, operator.ge, K.gens()[0:n] + [1]): vert @@ -264,9 +268,9 @@ def bsa_of_rep_element(bkpt, vals): n = len(bkpt) assert(n>=2) coord_names = [] - for i in range(0,n): + for i in range(n): coord_names.append('lambda'+str(i)) - for i in range(0,n): + for i in range(n): coord_names.append('gamma'+str(i)) logging.disable(logging.INFO) K = ParametricRealField(names=coord_names, values = bkpt+vals, big_cells=True) @@ -315,7 +319,7 @@ def find_minimal_function_reps_from_bkpts(bkpts, backend=None): for bkpt in bkpts: n = len(bkpt) for f_index in range(1, n): - poly_bsa = value_nnc_polyhedron(list(bkpt), f_index) + poly_bsa = value_nnc_polyhedron_value_cords(list(bkpt), f_index) gammas = poly_bsa.polynomial_map()[0].parent().gens() try: test_point = poly_bsa.upstairs().find_point() @@ -464,7 +468,7 @@ class PiMinContContainer: def get_semialgebraic_sets(self): for b, v in self._data: - yield bsa_of_rep_element(b, v) + yield bsa_of_rep_element(list(b), list(v)) def get_rep_elems(self): for b, v in self._data: From 86b91dd5de2707a28d6a3bd7ccd05f498d39686b Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Thu, 5 Feb 2026 13:03:04 -0800 Subject: [PATCH 39/64] add some more documenation, sketch method of refining breakpoint space --- .../igp/minimal_function_cache.sage | 314 ++++++++++++------ 1 file changed, 212 insertions(+), 102 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index 2b41b4a5b..aedf2429f 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -5,21 +5,93 @@ import csv import os from cutgeneratingfunctionology.spam.basic_semialgebraic import EmptyBSA +### Note to future reader, from yours truely. ### +### bkpt is assumed to be a breakpoint sequence of length n>= 2. +### Breakpoint sequence are sorted lists of real numbers in [0,1). +### A breakpoint sequences should always have 0 as an element. + +### + + class RepElemGenFailure(Exception): pass +def nnc_poly_from_bkpt_sequence(bkpt, backend=None): + r""" + Defines an NNC polyhedron P such that for all b in P the delta complex of b is isomoprhic to the delta complex of bkpt. + + INPUT: + - ``bkpt`` - sorted list of length 2 or more of sage type in the interval [0,1). + - ```backend`` - ``None``, ``str(pplite)`` + + OUTPUT: class::``BasicSemialgebraicSet_veronese`` + + EXAMPLES:: + >>> from cutgeneratingfunctionology.igp import * + >>> nnc_poly_from_bkpt_sequence([0, 4/5]) + BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(Constraint_System {x0==0, -x1+1>0, 2*x1-1>0}, names=[x0, x1]), polynomial_map=[lambda0, lambda1]) + """ + n = len(bkpt) + # assert(n >= 2) + coord_names = [] + bkpt_vals = bkpt + vals = bkpt_vals[0:n] + bkpt_extd = list(bkpt)+[1] + for i in range(0,n): + coord_names.append('lambda'+str(i)) + K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True) + logging.disable(logging.INFO) + K.gens()[0] == 0 + for i in range(n-1): + K.gens()[i] < K.gens()[i+1] + K.gens()[n-1] < 1 + for i in range(n): + for j in range(n): + if bkpt[i]+bkpt[j]>= 1: + w = 1 + else: + w = 0 + for k in range(n): + if bkpt_extd[k] < bkpt[i]+bkpt[j] - w and bkpt[i]+bkpt[j] - w < bkpt_extd[k+1]: + if k != n-1: + K.gens()[k] < K.gens()[i] + K.gens()[j] - w + K.gens()[i] + K.gens()[j] - w < K.gens()[k+1] + else: + K.gens()[k] < K.gens()[i] + K.gens()[j] - w + K.gens()[i] + K.gens()[j] - w < 1 + elif bkpt_extd[k] == bkpt[i]+bkpt[j] - w: + if k != n-1: + K.gens()[k] == K.gens()[i] + K.gens()[j] - w + K.gens()[i] + K.gens()[j] - w < K.gens()[k+1] + else: + K.gens()[k] == K.gens()[i] + K.gens()[j] - w + K.gens()[i] + K.gens()[j] - w < 1 + return K._bsa + + + def add_breakpoints_and_find_equiv_classes(bkpt_poly): """ - Takes dim k-1 breakpoint NNC polyhedron (as a :class:`BasicSemialgebraicSet_base`) and finds rep elements + Takes dim k-1 breakpoint NNC polyhedron (as a :class:`BasicSemialgebraicSet_base`), adds a dimension, + and finds all possible representive elements for equivlance classes of polyhedral complexes. + + INPUT: :class:`BasicSemialgebraicSet_Polyhedral` + + OUTPUT: unique list of breakpoint sequnes of lenght k (as tuples). + + EXAMPLES:: + >>> add_breakpoints_and_find_equiv_classes(nnc_poly_from_bkpt_sequence([0,4/5]).upstairs()) + [(0, 9/14, 83/112), (0, 13/20, 33/40), (0, 9/14, 101/112), (0, 7/10, 17/20)] + """ # BSAs are highly mutable, work only with copies. B_cap_N_b = copy(bkpt_poly) B_cap_N_b.add_space_dimensions_and_embed(1) - # get new number of breakpoints + # get new number of breakpoints. k = B_cap_N_b.ambient_dim() - # if k< 2: - # raise ValueError("bkpt_poly should have space dim at least 1.") + if k< 1: + raise ValueError("bkpt_poly should have space dim at least 1.") model_bound_bkpts = [0]*k model_bound_bkpts[k-1] = 1 # 0 < lambda_k <1 @@ -29,8 +101,6 @@ def add_breakpoints_and_find_equiv_classes(bkpt_poly): bkpt_order[k-2] = 1 bkpt_order[k-1] = -1 B_cap_N_b.add_linear_constraint(bkpt_order, 0, operator.lt) # order on bkpts - # print(B_cap_N_b) - # rep elem list rep_elems = [] for j in range(k-1): for i in range(k): @@ -40,7 +110,7 @@ def add_breakpoints_and_find_equiv_classes(bkpt_poly): # modeled lambda_i op 2lambda_k - w < lambda_{i+1} for interval_op in [operator.lt, operator.eq, operator.gt]: for line_op in [operator.lt, operator.eq, operator.gt]: - # highly mutable objects, operater on the copy + # highly mutable objects, operate on the copy. B_cap_N_b_copy = copy(B_cap_N_b) lhs_i = [0]*k lhs_i[k-1] = -2 @@ -67,54 +137,31 @@ def add_breakpoints_and_find_equiv_classes(bkpt_poly): pass return unique_list(rep_elems) -def nnc_poly_from_bkpt_sequence(bkpt, backend=None): - n = len(bkpt) - # assert(n >= 2) - coord_names = [] - bkpt_vals = bkpt - vals = bkpt_vals[0:n] - bkpt_extd = list(bkpt)+[1] - for i in range(0,n): - coord_names.append('lambda'+str(i)) - K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True) - logging.disable(logging.INFO) - K.gens()[0] == 0 - for i in range(n-1): - K.gens()[i] < K.gens()[i+1] - K.gens()[n-1] < 1 - for i in range(n): - for j in range(n): - if bkpt[i]+bkpt[j]>= 1: - w = 1 - else: - w = 0 - for k in range(n): - if bkpt_extd[k] < bkpt[i]+bkpt[j] - w and bkpt[i]+bkpt[j] - w < bkpt_extd[k+1]: - if k != n-1: - K.gens()[k] < K.gens()[i] + K.gens()[j] - w - K.gens()[i] + K.gens()[j] - w < K.gens()[k+1] - else: - K.gens()[k] < K.gens()[i] + K.gens()[j] - w - K.gens()[i] + K.gens()[j] - w < 1 - elif bkpt_extd[k] == bkpt[i]+bkpt[j] - w: - if k != n-1: - K.gens()[k] == K.gens()[i] + K.gens()[j] - w - K.gens()[i] + K.gens()[j] - w < K.gens()[k+1] - else: - K.gens()[k] == K.gens()[i] + K.gens()[j] - w - K.gens()[i] + K.gens()[j] - w < 1 - return K._bsa - - -def make_rep_bkpts_with_len_n(n, k=1, bkpts=None): +def make_rep_bkpts_with_len_n(n, k=1, bkpts=None, backend=None): r""" Produce representative elements of every isomorphism class of breakpoints complexes for breakpoint sequences of length n. - Note, this function does not check that the data input is correct and assume it is being used correctly. - - INPUT: n, length of breakpoint sequence, k, length of every element, bkpts, an iterable of breakpoints all of length k. + INPUT: + - n, integer, maximum length of breakpoint sequence. + - k, assumed length of breakpoint sequences in ``bkpts`` + - bkpts, list of breakpoint sequenes (sorted , length of every element, bkpts, an iterable of breakpoints all of length k. OUTPUT: A list of representative elements of every isomorphism class of breakpoints complexes for breakpoint sequences of length n extrapolated from bkpts. + + EXAMPLES:: + >>> make_rep_bkpts_with_len_n(2) + [(0, 1/2), (0, 13/18), (0, 5/18)] + + The number of representative elements grows quickly:: + + >>> bkpts_rep_with_len_3 = make_rep_bkpts_with_len_n(3) + >>> len(bkpts_rep_with_len_3) + 34 + + Previous computations can be reused:: + + >>> bkpts_rep_with_len_4 = make_bkpts_with_len_n(4, 3, bkpts_rep_with_len_3) + >>> len(bkpts_rep_with_len_4) """ # Look into using a directed tree as an underlying data structure for generating elements. new_bkpts = [] @@ -136,8 +183,10 @@ def make_rep_bkpts_with_len_n(n, k=1, bkpts=None): def generate_assumed_symmetric_vertices_continuous(fn, f, bkpt): """ - Assumes the symmetry condition holds for all vertices (x,y) in bkpt's breakpoints complex + Silently assumes the symmetry condition holds for all vertices (x,y) in bkpt's breakpoints complex such that x+y equiv f. + + See fun:``generate_symmetric_vertices_continuous``. """ for i in range(len(bkpt)): x = bkpt[i] @@ -151,9 +200,21 @@ def generate_assumed_symmetric_vertices_continuous(fn, f, bkpt): yield (x, y, 0, 0) -def value_nnc_polyhedron_value_cords(bkpt, f_index): +def value_nnc_polyhedron_value_cords(bkpt, f_index, backend =None): """ - For a given ``bkpt`` seqeunce and ``f_index``, write the value polyhedron as a BSA in only the value parameters. + For a given breakpoints seqeunce and f index, write the value polyhedron as a basic semialgebraic set in only the value parameters. + + INPUT: + - ``bkpt`` - sorted list of length 2 or more of sage type in the interval [0,1). + - ``f_index`` - integer between 1 and length of ``len(bkpt) -1``. + - ```backend`` - ``None``, ``str(pplite)`` + + OUTPUT: + - class::``BasicSemialgebraicSet_veronese`` + + EXAMPLES:: + >>> value_nnc_polyhedron_value_cords([0,4/5], 1) # gmic with f=4/5 + BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(Constraint_System {x1-1==0, x0==0}, names=[x0, x1]), polynomial_map=[gamma0, gamma1]) """ # this saves a slight amount of overhead when detemrining points for the value polyhedron since the assumed # minimality test does not have to entierly go though parametric real field. @@ -182,41 +243,21 @@ def value_nnc_polyhedron_value_cords(bkpt, f_index): for vert in generate_assumed_symmetric_vertices_continuous(h, bkpt[f_index], bkpt + [1]): vert return K._bsa - -# def value_nnc_polyhedron_gamma_0_not_as_param(bkpt, f_index): - # """ - # For a given ``bkpt`` seqeunce and ``f_index``, find the value polyhedron which assumes pi_(bkpt, v) is minimal. - # """ - # n = len(bkpt) - # assert(n >= 2) - # assert(f_index >= 1) - # assert(f_index <= n - 1) - # if not isinstance(bkpt, list): - # bkpt = list(bkpt) - # coord_names = [] - # val = [None]*(n-1) - # for i in range(1, n): - # coord_names.append('gamma'+str(i)) - # logging.disable(logging.INFO) - # K = ParametricRealField(names=coord_names, values = val, mutable_values=True, big_cells=True, allow_refinement=False) - # for i in range(1, n): - # K.gens()[i] <=1 - # K.gens()[i] > 0 - # h = piecewise_function_from_breakpoints_and_values(bkpt + [1], [0] + K.gens() + [0], merge=False) - # # Assumes minimality for the partially defined function. - # for vert in generate_type_1_vertices_continuous(h, operator.ge, bkpt + [1]): - # vert - # for vert in generate_type_2_vertices_continuous(h, operator.ge, bkpt + [1]): - # vert - # for vert in generate_assumed_symmetric_vertices_continuous(h, bkpt[f_index], bkpt + [1]): - # vert - # return K._bsa def value_nnc_polyhedron(bkpt, f_index): """ - For a given ``bkpt`` seqeunce and ``f_index``, write a base which is the value polyhedron corrospoding in the full space of parameters. + For a given breakpoints seqeunce and f index, write the value polyhedron as a basic semialgebraic set in the full space of parameters. + + INPUTS: + - ``bkpt`` - sorted list of length 2 or more of sage type in the interval [0,1). + - ``f_index`` - integer between 1 and length of ``len(bkpt) -1``. + - ``backend`` - ``None``, ``str(pplite)`` + + OUTPUT: + - class::``BasicSemialgebraicSet_veronese`` EXAMPLES:: + >>> value_nnc_polyhedron([0,4/5], 1) # gmic with f=4/5 """ n = len(bkpt) @@ -256,8 +297,10 @@ def bsa_of_rep_element(bkpt, vals): Given pi_(bkpt, vals) is {minimal, not minimal}, find BSA subset of R^(2n) such that (bkpt, vals) in BSA and for all p in BSA, pi_p is {minimal, not minimal}. - INPUT: (bkpt, vals) are lists or vectors of length n and bkpt is a proper breakpoints sequence and vals - is the corresponding value parameters. + INPUT: + - ``bkpt`` - a breakpoint seqeunce + - ``vals`` - list like of sage numerical types corrosponding values for the breakpoint sequence. + - ``backend`` - None, ``str(pplite)`` OUTPUT: A basic semialgebraic set. @@ -279,13 +322,15 @@ def bsa_of_rep_element(bkpt, vals): return K.make_proof_cell().bsa -def bsa_of_rep_element_pi_of_0_not_param(bkpt, vals): +def bsa_of_rep_element_pi_of_0_not_param(bkpt, vals, backend=None): """ Given pi_(bkpt, vals) is {minimal, not minimal}, find BSA subset of R^(2n) such that (bkpt, vals) in BSA and for all p - in BSA, pi_p is {minimal, not minimal}. + in BSA, pi_p is {minimal, not minimal}. The first entry of ``bkpt`` and ``vals`` is assuemd to be 0. - INPUT: (bkpt, vals) are lists or vectors of length n and bkpt is a proper breakpoints sequence and vals - is the corresponding value parameters. + INPUT: + - ``bkpt`` - a breakpoint seqeunce + - ``vals`` - list like of sage numerical types corrosponding values for the breakpoint sequence. + - ``backend`` - None, ``str(pplite)`` OUTPUT: A basic semialgebraic set. @@ -311,9 +356,21 @@ def bsa_of_rep_element_pi_of_0_not_param(bkpt, vals): return K.make_proof_cell().bsa -def find_minimal_function_reps_from_bkpts(bkpts, backend=None): +def find_minimal_function_reps_from_bkpts(bkpts, prove_minimality=True, backend=None): """ - Finds representative elements of minimal functions from a given breakpoint sequence. + Finds representative elements of minimal functions from a given breakpoint sequence. + + INPUT: + - ``bkpts`` - an interable of breakpoint seqeunces. + - ``prove_minimality`` - bool, proves minimality of paramaterized function + - ``backend`` - None, ``str(pplite)`` + + EXAMPLES:: + + >>> bkpts = make_rep_bkpts_with_len_n(2) + >>> rep_elems = find_minimal_function_reps_from_bkpts(bkpts) + >>> rep_elems + """ rep_elems = [] for bkpt in bkpts: @@ -328,9 +385,10 @@ def find_minimal_function_reps_from_bkpts(bkpts, backend=None): test_val = [] for gamma_i in gammas: test_val.append(test_point[poly_bsa.v_dict()[gamma_i]]) - h = piecewise_function_from_breakpoints_and_values(list(bkpt)+[1], test_val+[0]) - if not minimality_test(h): # test bkpt doesn't make a valid minimal function, the statement still holds with original bkpt - raise ValueError("HELP! ({}, {}) paramaterized is not a minimal function but assuming a breakpoint sequence is input, this should be minimal. GL debugging.".format(bkpt, test_val)) + if prove_minimality: + h = piecewise_function_from_breakpoints_and_values(list(bkpt)+[1], test_val+[0]) + if not minimality_test(h): # The following error should never be raised when this function is used as intended. + raise ValueError(f"({bkpt}, {test_val}) paramaterized by breakpoints and values is not a minimal function but assuming a breakpoint sequence is input, this should be minimal.") rep_elems.append((bkpt, test_val)) return rep_elems @@ -340,6 +398,9 @@ class BreakpointComplexClassContainer: A container for the family of breakpoint complexes for peicewise linear functions with at most n breakpoints. + The container assumes that loaded data is correct and performs no checking + that the loaded data represnts the full space. + EXAMPLES:: """ @@ -380,8 +441,7 @@ class BreakpointComplexClassContainer: def get_nnc_poly_from_bkpt(self): for bkpt in self._data: - for f_index in range(1,n): - yield nnc_poly_from_bkpt(bkpt, f_index) + yield nnc_poly_from_bkpt_sequence(bkpt) def num_rep_elems(self): return len(self._data) @@ -389,6 +449,32 @@ class BreakpointComplexClassContainer: def add_one_bkpt_to_all(self): logging.warning("Generating representative elements. This might take a while.") self._data = make_bkpts_with_len_n(self._n+1, self._n, self._data) + + def covers_space(self): + ### TODO: This method should prove that container is correct. + raise NotImplementedError + + def refine_space(self): + """Ensures that repersentative elements are unique and contained in a single cell. """ + raise NotImplementedError + ### TODO: Test this. I think I need to write a containment method for the BSA + ### or pick the up the underlying polyhedron in the BSA. + self._data = unique_list(self._data) + cells_found = [] + for bkpt in self._data: + bkpt_contained_in_cell = False + for found_cell in cells_found: + if found_cell.contains(bkpt): + bkpt_contained_in_cell = True: + break + if not contained_in_cell: + cells_found.append(nnc_poly_from_bkpt_sequence(bkpt)) + new_data = [] + for cell in cells_found: + new_data.append(cell.find_point()) + self._data = new_data + + def write_data(self, output_file_name_style=None, max_rows=None): """ @@ -425,13 +511,31 @@ class PiMinContContainer: """ A container for the space of continuous piecewise linear minimal functions with at most n breakpoints paramaterized by breakpoints and values using semialgebraic sets. + + The container assumes that loaded data is correct and performs no checking. - TESTS:: + EXAMPLES:: - >>> PiMin_at_most_4_breakpoints = PiMinContContainer(4) - >>> all([minimality_test(pi) for PiMin_at_most_4_breakpoints.get_rep_functions()]) + >>> PiMin_4 = PiMinContContainer(4) + >>> all([minimality_test(pi) for pi in PiMin_4.get_rep_functions()]) True - >>> + >>> PiMin_4 + + A cell descrption is can be accessed:: + + >>> all([isinstance(cell, ) for cell in PiMin_4.get_semialgebraic_sets()]) + + Data is stored as repersenative elements. The number of repersentative elements grows quickly. :: + + >>> len([rep_elem for rep_elem in PiMin_4.get_rep_elems()]) + + The container provides methods of writing data.:: + + >>> PiMin_4.write_data() + + One can load representative element data. :: + + >>> PiMin_4_loaded_data = PiMinContContainer(4, """ def __init__(self, n, **kwrds): self._n = n @@ -467,24 +571,30 @@ class PiMinContContainer: return "Space of minimal functions with at most {} breakpoints parameterized by breakpoints and values using semialgebraic sets.".format(self._n) def get_semialgebraic_sets(self): + """Iterator for semialgebraic set description""" for b, v in self._data: yield bsa_of_rep_element(list(b), list(v)) def get_rep_elems(self): + """Iterator for representative elements.""" for b, v in self._data: yield (list(b), list(v)) def get_rep_functions(self): + """Iterator for representative functinos.""" for b, v in self._data: yield piecewise_function_from_breakpoints_and_values(list(b)+[1], list(v)+[0]) def n(self): + """The maximum number of proper breakpoints of paramaterized functions in the space.""" return self._n - + def covers_space(self): + ### TODO: This method should prove that container is correct. raise NotImplementedError def refine_space(self): + ### TODO: This method should be called when loading multiple file to ensure cells have not been duplicated. raise NotImplementedError def write_data(self, output_file_name_style=None, max_rows=None): @@ -492,7 +602,7 @@ class PiMinContContainer: Writes representative element data to a `.csv` file with one column and rows of representative elements. Optionally, write many `.csv` files with at `most max_rows` rows per file. Files are named output_file_file_name_style_filenumber.csv. - The default output_file_name_style="Pi_Min_n". + The default output_file_name_style=f"Pi_Min_{n}". """ # TODO: Future, support writing different types of data such as polyhedra data. if output_file_name_style is None: From aa1970aa7d903eb71c269cf8559f21e03b218cd5 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Fri, 6 Feb 2026 10:32:21 -0800 Subject: [PATCH 40/64] finish minimal documantion, fix value poly bug --- .../igp/minimal_function_cache.sage | 70 ++++++------------- 1 file changed, 23 insertions(+), 47 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index aedf2429f..d75809e15 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -9,8 +9,8 @@ from cutgeneratingfunctionology.spam.basic_semialgebraic import EmptyBSA ### bkpt is assumed to be a breakpoint sequence of length n>= 2. ### Breakpoint sequence are sorted lists of real numbers in [0,1). ### A breakpoint sequences should always have 0 as an element. - -### +### This is never strictly enforced in this file and it is assumed that +### the user is always provided a breakpoint sequence. class RepElemGenFailure(Exception): @@ -137,6 +137,7 @@ def add_breakpoints_and_find_equiv_classes(bkpt_poly): pass return unique_list(rep_elems) + def make_rep_bkpts_with_len_n(n, k=1, bkpts=None, backend=None): r""" Produce representative elements of every isomorphism class of breakpoints complexes for breakpoint sequences of length n. @@ -258,7 +259,7 @@ def value_nnc_polyhedron(bkpt, f_index): EXAMPLES:: >>> value_nnc_polyhedron([0,4/5], 1) # gmic with f=4/5 - + BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(Constraint_System {x3-1==0, x2==0, 5*x1-4==0, x0==0}, names=[x0, x1, x2, x3]), polynomial_map=[lambda0, lambda1, gamma0, gamma1]) """ n = len(bkpt) assert(n >= 2) @@ -287,7 +288,7 @@ def value_nnc_polyhedron(bkpt, f_index): vert for vert in generate_type_2_vertices_continuous(h, operator.ge, K.gens()[0:n] + [1]): vert - for vert in generate_assumed_symmetric_vertices_continuous(h, K.gens()[f_index-1], [0] + K.gens()[0:n] + [1]): + for vert in generate_assumed_symmetric_vertices_continuous(h, K.gens()[f_index], [0] + K.gens()[0:n] + [1]): vert return K._bsa @@ -322,40 +323,6 @@ def bsa_of_rep_element(bkpt, vals): return K.make_proof_cell().bsa -def bsa_of_rep_element_pi_of_0_not_param(bkpt, vals, backend=None): - """ - Given pi_(bkpt, vals) is {minimal, not minimal}, find BSA subset of R^(2n) such that (bkpt, vals) in BSA and for all p - in BSA, pi_p is {minimal, not minimal}. The first entry of ``bkpt`` and ``vals`` is assuemd to be 0. - - INPUT: - - ``bkpt`` - a breakpoint seqeunce - - ``vals`` - list like of sage numerical types corrosponding values for the breakpoint sequence. - - ``backend`` - None, ``str(pplite)`` - - OUTPUT: A basic semialgebraic set. - - EXAMPLES:: - - - """ - n = len(bkpt) - if not isinstance(bkpt, list): - bkpt = list(bkpt) - if not isinstance(vals, list): - vals = list(vals) - assert(n>=2) - coord_names = [] - for i in range(1,n): - coord_names.append('lambda'+str(i)) - for i in range(1,n): - coord_names.append('gamma'+str(i)) - logging.disable(logging.INFO) - K = ParametricRealField(names=coord_names, values = bkpt[1:]+vals[1:], big_cells=True) - h = piecewise_function_from_breakpoints_and_values([0]+K.gens()[:n-1] + [1], [0] + K.gens()[n-1:] + [0], merge=False) - minimality_test(h) - return K.make_proof_cell().bsa - - def find_minimal_function_reps_from_bkpts(bkpts, prove_minimality=True, backend=None): """ Finds representative elements of minimal functions from a given breakpoint sequence. @@ -365,11 +332,14 @@ def find_minimal_function_reps_from_bkpts(bkpts, prove_minimality=True, backend= - ``prove_minimality`` - bool, proves minimality of paramaterized function - ``backend`` - None, ``str(pplite)`` + OUTPUT: + - List of tuples of lists + EXAMPLES:: >>> bkpts = make_rep_bkpts_with_len_n(2) - >>> rep_elems = find_minimal_function_reps_from_bkpts(bkpts) - >>> rep_elems + >>> find_minimal_function_reps_from_bkpts(bkpts) + [([0, 1/2], [0, 1]), ((0, 13/18), [0, 1]), ((0, 5/18), [0, 1])][((0, 1/2), [0, 1]), ((0, 13/18), [0, 1]), ((0, 5/18), [0, 1])] """ rep_elems = [] @@ -389,7 +359,7 @@ def find_minimal_function_reps_from_bkpts(bkpts, prove_minimality=True, backend= h = piecewise_function_from_breakpoints_and_values(list(bkpt)+[1], test_val+[0]) if not minimality_test(h): # The following error should never be raised when this function is used as intended. raise ValueError(f"({bkpt}, {test_val}) paramaterized by breakpoints and values is not a minimal function but assuming a breakpoint sequence is input, this should be minimal.") - rep_elems.append((bkpt, test_val)) + rep_elems.append((list(bkpt), test_val)) return rep_elems @@ -465,7 +435,7 @@ class BreakpointComplexClassContainer: bkpt_contained_in_cell = False for found_cell in cells_found: if found_cell.contains(bkpt): - bkpt_contained_in_cell = True: + bkpt_contained_in_cell = True break if not contained_in_cell: cells_found.append(nnc_poly_from_bkpt_sequence(bkpt)) @@ -520,22 +490,28 @@ class PiMinContContainer: >>> all([minimality_test(pi) for pi in PiMin_4.get_rep_functions()]) True >>> PiMin_4 + Space of minimal functions with at most 4 breakpoints parameterized by breakpoints and values using semialgebraic sets. - A cell descrption is can be accessed:: + A cell descrption of semialgebraic sets can be accessed:: - >>> all([isinstance(cell, ) for cell in PiMin_4.get_semialgebraic_sets()]) + >>> all([isinstance(cell, BasicSemialgebraicSet_base) for cell in PiMin_4.get_semialgebraic_sets()]) + True Data is stored as repersenative elements. The number of repersentative elements grows quickly. :: >>> len([rep_elem for rep_elem in PiMin_4.get_rep_elems()]) + 987 The container provides methods of writing data.:: >>> PiMin_4.write_data() - One can load representative element data. :: - >>> PiMin_4_loaded_data = PiMinContContainer(4, + Written data can be reused :: + + >>> PiMin_4_loaded_data = PiMinContContainer(4, load_rep_elem_data="Pi_Min_4.csv") + >>> len([rep_elem for rep_elem in PiMin_4_loaded_data.get_rep_elems()]) + 987 """ def __init__(self, n, **kwrds): self._n = n @@ -627,6 +603,6 @@ class PiMinContContainer: break out_file.close() output_file = file_name_base[:-1]+"{}".format(file_number+1)+".csv" - + ### TODO: Add method to make loaded objects the same as generated objects if they repersent the same space. ### Plotting Utilties ### \ No newline at end of file From 9051140fa0bbc73cb51c7102b8fd052373c374c0 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Sun, 8 Feb 2026 10:06:13 -0800 Subject: [PATCH 41/64] start logging changes --- .../igp/minimal_function_cache.sage | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index d75809e15..7dd52f25a 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -4,6 +4,8 @@ from cutgeneratingfunctionology.igp import * import csv import os from cutgeneratingfunctionology.spam.basic_semialgebraic import EmptyBSA +import logging +logger = logging.getLogger(__name__) ### Note to future reader, from yours truely. ### ### bkpt is assumed to be a breakpoint sequence of length n>= 2. @@ -12,6 +14,10 @@ from cutgeneratingfunctionology.spam.basic_semialgebraic import EmptyBSA ### This is never strictly enforced in this file and it is assumed that ### the user is always provided a breakpoint sequence. +# global defaults for logging from portions of CGF +log_paramateric_real_field = False +log_pw_functions = False + class RepElemGenFailure(Exception): pass @@ -41,7 +47,6 @@ def nnc_poly_from_bkpt_sequence(bkpt, backend=None): for i in range(0,n): coord_names.append('lambda'+str(i)) K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True) - logging.disable(logging.INFO) K.gens()[0] == 0 for i in range(n-1): K.gens()[i] < K.gens()[i+1] @@ -176,9 +181,12 @@ def make_rep_bkpts_with_len_n(n, k=1, bkpts=None, backend=None): new_bkpts += add_breakpoints_and_find_equiv_classes(nnc_poly_from_bkpt_sequence(bkpt).upstairs()) new_bkpts = unique_list(new_bkpts) k += 1 + if k == n: + logger.info(f"Breakpoints of length {n} have been generated. ") return new_bkpts else: + logger.info(f"Breakpoitns of lenght {k} have been generated. Now generating breakpoints of length{k+1}") return make_rep_bkpts_with_len_n(n, k, new_bkpts) @@ -229,7 +237,6 @@ def value_nnc_polyhedron_value_cords(bkpt, f_index, backend =None): val = [None]*(n) for i in range(n): coord_names.append('gamma'+str(i)) - logging.disable(logging.INFO) K = ParametricRealField(names=coord_names, values = val, mutable_values=True, big_cells=True, allow_refinement=False) K.gens()[0] == 0 for i in range(1, n): @@ -272,7 +279,6 @@ def value_nnc_polyhedron(bkpt, f_index): coord_names.append('lambda'+str(i)) for i in range(n): coord_names.append('gamma'+str(i)) - logging.disable(logging.INFO) K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, allow_refinement=False) # breakpoint parameters are the mesured breakpoint values. for i in range(n): @@ -316,7 +322,6 @@ def bsa_of_rep_element(bkpt, vals): coord_names.append('lambda'+str(i)) for i in range(n): coord_names.append('gamma'+str(i)) - logging.disable(logging.INFO) K = ParametricRealField(names=coord_names, values = bkpt+vals, big_cells=True) h = piecewise_function_from_breakpoints_and_values(K.gens()[0:n] + [1], K.gens()[n:2*n] + [0], merge=False) minimality_test(h) @@ -384,7 +389,7 @@ class BreakpointComplexClassContainer: self._backend = None if "load_rep_elem_data" in kwrds.keys(): if kwrds[load_rep_elem_data] is None: - logging.warning("Generating representative elements. This might take a while.") + logger.info("Generating representative elements. This might take a while.") self._data = make_rep_bkpts_with_len_n(self._n) else: file_names = kwrds["load_bkpt_data"].split(",") @@ -399,7 +404,7 @@ class BreakpointComplexClassContainer: if k < n: self._data = make_rep_bkpts_with_len_n(n, k, self._data) else: - logging.warning("Generating representative elements. This might take a while.") + logger.info("Generating representative elements. This might take a while.") self._data = make_rep_bkpts_with_len_n(self._n) def __repr__(self): @@ -417,7 +422,7 @@ class BreakpointComplexClassContainer: return len(self._data) def add_one_bkpt_to_all(self): - logging.warning("Generating representative elements. This might take a while.") + logger.info("Generating representative elements. This might take a while.") self._data = make_bkpts_with_len_n(self._n+1, self._n, self._data) def covers_space(self): @@ -539,7 +544,7 @@ class PiMinContContainer: for row in file_reader: self._data.append([eval(preparse(data)) for data in row]) else: - logging.warning("Generating representative elements. This might take a while.") + logger.info("Generating representative elements. This might take a while.") bkpts = make_rep_bkpts_with_len_n(self._n) self._data = find_minimal_function_reps_from_bkpts(bkpts) From fb472990ad0dc4ed9c9160a348e32d5d04bd7009 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Tue, 17 Feb 2026 12:14:49 -0800 Subject: [PATCH 42/64] minimal necessary doc tests completed. --- .../igp/minimal_function_cache.sage | 165 +++++++++++++----- 1 file changed, 125 insertions(+), 40 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index 7dd52f25a..e753e0460 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -5,7 +5,9 @@ import csv import os from cutgeneratingfunctionology.spam.basic_semialgebraic import EmptyBSA import logging -logger = logging.getLogger(__name__) + +minimal_function_cache_logger = logging.getLogger("cutgeneratingfunctionology.igp.minimal_function_cache") +minimal_function_cache_logger.setLevel(logging.INFO) ### Note to future reader, from yours truely. ### ### bkpt is assumed to be a breakpoint sequence of length n>= 2. @@ -14,11 +16,10 @@ logger = logging.getLogger(__name__) ### This is never strictly enforced in this file and it is assumed that ### the user is always provided a breakpoint sequence. -# global defaults for logging from portions of CGF +# global defaults for logging from portions of igp; change to log different parts of igp. log_paramateric_real_field = False log_pw_functions = False - class RepElemGenFailure(Exception): pass @@ -34,8 +35,9 @@ def nnc_poly_from_bkpt_sequence(bkpt, backend=None): OUTPUT: class::``BasicSemialgebraicSet_veronese`` EXAMPLES:: - >>> from cutgeneratingfunctionology.igp import * - >>> nnc_poly_from_bkpt_sequence([0, 4/5]) + sage: from cutgeneratingfunctionology.igp import * + sage: logging.disable(logging.INFO) # suppress logging for tests + sage: nnc_poly_from_bkpt_sequence([0, 4/5]) BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(Constraint_System {x0==0, -x1+1>0, 2*x1-1>0}, names=[x0, x1]), polynomial_map=[lambda0, lambda1]) """ n = len(bkpt) @@ -44,6 +46,9 @@ def nnc_poly_from_bkpt_sequence(bkpt, backend=None): bkpt_vals = bkpt vals = bkpt_vals[0:n] bkpt_extd = list(bkpt)+[1] + if not log_paramateric_real_field: + parametric_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.parametric").getEffectiveLevel() + logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(logging.ERROR) for i in range(0,n): coord_names.append('lambda'+str(i)) K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True) @@ -71,11 +76,12 @@ def nnc_poly_from_bkpt_sequence(bkpt, backend=None): K.gens()[i] + K.gens()[j] - w < K.gens()[k+1] else: K.gens()[k] == K.gens()[i] + K.gens()[j] - w - K.gens()[i] + K.gens()[j] - w < 1 + K.gens()[i] + K.gens()[j] - w < 1 + if not log_paramateric_real_field: + logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(parametric_logging_level) return K._bsa - def add_breakpoints_and_find_equiv_classes(bkpt_poly): """ Takes dim k-1 breakpoint NNC polyhedron (as a :class:`BasicSemialgebraicSet_base`), adds a dimension, @@ -86,7 +92,10 @@ def add_breakpoints_and_find_equiv_classes(bkpt_poly): OUTPUT: unique list of breakpoint sequnes of lenght k (as tuples). EXAMPLES:: - >>> add_breakpoints_and_find_equiv_classes(nnc_poly_from_bkpt_sequence([0,4/5]).upstairs()) + + sage: from cutgeneratingfunctionology.igp import * + sage: logging.disable(logging.INFO) # suppress logging for tests + sage: add_breakpoints_and_find_equiv_classes(nnc_poly_from_bkpt_sequence([0,4/5]).upstairs()) [(0, 9/14, 83/112), (0, 13/20, 33/40), (0, 9/14, 101/112), (0, 7/10, 17/20)] """ @@ -155,19 +164,23 @@ def make_rep_bkpts_with_len_n(n, k=1, bkpts=None, backend=None): OUTPUT: A list of representative elements of every isomorphism class of breakpoints complexes for breakpoint sequences of length n extrapolated from bkpts. EXAMPLES:: - >>> make_rep_bkpts_with_len_n(2) + + sage: from cutgeneratingfunctionology.igp import * + sage: logging.disable(logging.INFO) # suppress logging for tests + sage: make_rep_bkpts_with_len_n(2) [(0, 1/2), (0, 13/18), (0, 5/18)] The number of representative elements grows quickly:: - >>> bkpts_rep_with_len_3 = make_rep_bkpts_with_len_n(3) - >>> len(bkpts_rep_with_len_3) + sage: bkpts_rep_with_len_3 = make_rep_bkpts_with_len_n(3) + sage: len(bkpts_rep_with_len_3) 34 Previous computations can be reused:: - >>> bkpts_rep_with_len_4 = make_bkpts_with_len_n(4, 3, bkpts_rep_with_len_3) - >>> len(bkpts_rep_with_len_4) + sage: bkpts_rep_with_len_4 = make_rep_bkpts_with_len_n(4, 3, bkpts_rep_with_len_3) + sage: len(bkpts_rep_with_len_4) + 329 """ # Look into using a directed tree as an underlying data structure for generating elements. new_bkpts = [] @@ -183,10 +196,10 @@ def make_rep_bkpts_with_len_n(n, k=1, bkpts=None, backend=None): k += 1 if k == n: - logger.info(f"Breakpoints of length {n} have been generated. ") + minimal_function_cache_logger.info(f"Breakpoints of length {n} have been generated. ") return new_bkpts else: - logger.info(f"Breakpoitns of lenght {k} have been generated. Now generating breakpoints of length{k+1}") + minimal_function_cache_logger.info(f"Breakpoitns of lenght {k} have been generated. Now generating breakpoints of length{k+1}") return make_rep_bkpts_with_len_n(n, k, new_bkpts) @@ -222,12 +235,21 @@ def value_nnc_polyhedron_value_cords(bkpt, f_index, backend =None): - class::``BasicSemialgebraicSet_veronese`` EXAMPLES:: - >>> value_nnc_polyhedron_value_cords([0,4/5], 1) # gmic with f=4/5 + + sage: from cutgeneratingfunctionology.igp import * + sage: logging.disable(logging.INFO) # suppress logging for tests + sage: value_nnc_polyhedron_value_cords([0,4/5], 1) # gmic with f=4/5 BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(Constraint_System {x1-1==0, x0==0}, names=[x0, x1]), polynomial_map=[gamma0, gamma1]) """ # this saves a slight amount of overhead when detemrining points for the value polyhedron since the assumed # minimality test does not have to entierly go though parametric real field. n = len(bkpt) + if not log_paramateric_real_field: + parametric_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.parametric").getEffectiveLevel() + logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(logging.ERROR) + if not log_pw_functions: + pw_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.functions").getEffectiveLevel() + logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(logging.ERROR) assert(n >= 2) assert(f_index >= 1) assert(f_index <= n - 1) @@ -250,6 +272,10 @@ def value_nnc_polyhedron_value_cords(bkpt, f_index, backend =None): vert for vert in generate_assumed_symmetric_vertices_continuous(h, bkpt[f_index], bkpt + [1]): vert + if not log_paramateric_real_field: + logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(parametric_logging_level) + if not log_pw_functions: + logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) return K._bsa def value_nnc_polyhedron(bkpt, f_index): @@ -265,10 +291,19 @@ def value_nnc_polyhedron(bkpt, f_index): - class::``BasicSemialgebraicSet_veronese`` EXAMPLES:: - >>> value_nnc_polyhedron([0,4/5], 1) # gmic with f=4/5 + + sage: from cutgeneratingfunctionology.igp import * + sage: logging.disable(logging.INFO) # suppress logging for tests + sage: value_nnc_polyhedron([0,4/5], 1) # gmic with f=4/5 BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(Constraint_System {x3-1==0, x2==0, 5*x1-4==0, x0==0}, names=[x0, x1, x2, x3]), polynomial_map=[lambda0, lambda1, gamma0, gamma1]) """ n = len(bkpt) + if not log_paramateric_real_field: + parametric_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.parametric").getEffectiveLevel() + logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(logging.ERROR) + if not log_pw_functions: + pw_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.functions").getEffectiveLevel() + logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(logging.ERROR) assert(n >= 2) assert(f_index >= 1) assert(f_index <= n) @@ -296,6 +331,10 @@ def value_nnc_polyhedron(bkpt, f_index): vert for vert in generate_assumed_symmetric_vertices_continuous(h, K.gens()[f_index], [0] + K.gens()[0:n] + [1]): vert + if not log_paramateric_real_field: + logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(parametric_logging_level) + if not log_pw_functions: + logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) return K._bsa @@ -316,6 +355,12 @@ def bsa_of_rep_element(bkpt, vals): """ n = len(bkpt) + if not log_paramateric_real_field: + parametric_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.parametric").getEffectiveLevel() + logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(logging.ERROR) + if not log_pw_functions: + pw_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.functions").getEffectiveLevel() + logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(logging.ERROR) assert(n>=2) coord_names = [] for i in range(n): @@ -325,6 +370,10 @@ def bsa_of_rep_element(bkpt, vals): K = ParametricRealField(names=coord_names, values = bkpt+vals, big_cells=True) h = piecewise_function_from_breakpoints_and_values(K.gens()[0:n] + [1], K.gens()[n:2*n] + [0], merge=False) minimality_test(h) + if not log_paramateric_real_field: + logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(parametric_logging_level) + if not log_pw_functions: + logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) return K.make_proof_cell().bsa @@ -341,13 +390,18 @@ def find_minimal_function_reps_from_bkpts(bkpts, prove_minimality=True, backend= - List of tuples of lists EXAMPLES:: - - >>> bkpts = make_rep_bkpts_with_len_n(2) - >>> find_minimal_function_reps_from_bkpts(bkpts) - [([0, 1/2], [0, 1]), ((0, 13/18), [0, 1]), ((0, 5/18), [0, 1])][((0, 1/2), [0, 1]), ((0, 13/18), [0, 1]), ((0, 5/18), [0, 1])] + + sage: from cutgeneratingfunctionology.igp import * + sage: logging.disable(logging.INFO) # suppress logging for tests + sage: bkpts = make_rep_bkpts_with_len_n(2) + sage: find_minimal_function_reps_from_bkpts(bkpts) + [([0, 1/2], [0, 1]), ([0, 13/18], [0, 1]), ([0, 5/18], [0, 1])] """ rep_elems = [] + if not log_pw_functions: + pw_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.functions").getEffectiveLevel() + logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(logging.ERROR) for bkpt in bkpts: n = len(bkpt) for f_index in range(1, n): @@ -365,6 +419,8 @@ def find_minimal_function_reps_from_bkpts(bkpts, prove_minimality=True, backend= if not minimality_test(h): # The following error should never be raised when this function is used as intended. raise ValueError(f"({bkpt}, {test_val}) paramaterized by breakpoints and values is not a minimal function but assuming a breakpoint sequence is input, this should be minimal.") rep_elems.append((list(bkpt), test_val)) + if not log_pw_functions: + logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) return rep_elems @@ -376,7 +432,25 @@ class BreakpointComplexClassContainer: The container assumes that loaded data is correct and performs no checking that the loaded data represnts the full space. + This class contains ways to read/write data for use with minimal function generation. + EXAMPLES:: + + sage: from cutgeneratingfunctionology.igp import * + sage: logging.disable(logging.INFO) # suppress logging for tests + sage: bkpt_of_len_2 = BreakpointComplexClassContainer(2) + sage: bkpt_of_len_2.num_rep_elems() + 3 + sage: [elem for elem in bkpt_of_len_2.get_rep_elems()] + [(0, 1/2), (0, 13/18), (0, 5/18)] + sage: make_rep_bkpts_with_len_n(2) + [(0, 1/2), (0, 13/18), (0, 5/18)] + + A cell descrption of semialgebraic sets can be accessed:: + + sage: all([isinstance(cell, BasicSemialgebraicSet_base) for cell in bkpt_of_len_2.get_nnc_poly_from_bkpt()]) + True + """ def __init__(self, n, **kwrds): @@ -389,7 +463,7 @@ class BreakpointComplexClassContainer: self._backend = None if "load_rep_elem_data" in kwrds.keys(): if kwrds[load_rep_elem_data] is None: - logger.info("Generating representative elements. This might take a while.") + minimal_function_cache_logger.info("Generating representative elements. This might take a while.") self._data = make_rep_bkpts_with_len_n(self._n) else: file_names = kwrds["load_bkpt_data"].split(",") @@ -404,7 +478,7 @@ class BreakpointComplexClassContainer: if k < n: self._data = make_rep_bkpts_with_len_n(n, k, self._data) else: - logger.info("Generating representative elements. This might take a while.") + minimal_function_cache_logger.info("Generating representative elements. This might take a while.") self._data = make_rep_bkpts_with_len_n(self._n) def __repr__(self): @@ -421,9 +495,10 @@ class BreakpointComplexClassContainer: def num_rep_elems(self): return len(self._data) - def add_one_bkpt_to_all(self): - logger.info("Generating representative elements. This might take a while.") - self._data = make_bkpts_with_len_n(self._n+1, self._n, self._data) + # def add_one_bkpt_to_all(self): + # minimal_function_cache_logger.info("Generating representative elements. This might take a while.") + # self._n = n+1 + # self._data = make_bkpts_with_len_n(self._n, self._n-1, self._data) def covers_space(self): ### TODO: This method should prove that container is correct. @@ -448,8 +523,6 @@ class BreakpointComplexClassContainer: for cell in cells_found: new_data.append(cell.find_point()) self._data = new_data - - def write_data(self, output_file_name_style=None, max_rows=None): """ @@ -488,35 +561,46 @@ class PiMinContContainer: most n breakpoints paramaterized by breakpoints and values using semialgebraic sets. The container assumes that loaded data is correct and performs no checking. + + INPUTS: + - n, an integer + keywords: + - backend, None or str(pplite) + - load_bkpt_data, .csv file(s) of list of tuples of breakpoitns + - load_rep_elem_data, .csv file(s) of list of tuples of representative elements of the space of minimal functions EXAMPLES:: - >>> PiMin_4 = PiMinContContainer(4) - >>> all([minimality_test(pi) for pi in PiMin_4.get_rep_functions()]) + sage: from cutgeneratingfunctionology.igp import * + sage: logging.disable(logging.INFO) # suppress logging for tests + sage: PiMin_2 = PiMinContContainer(2) + sage: all([minimality_test(pi) for pi in PiMin_2.get_rep_functions()]) True - >>> PiMin_4 - Space of minimal functions with at most 4 breakpoints parameterized by breakpoints and values using semialgebraic sets. + sage: PiMin_2 + Space of minimal functions with at most 2 breakpoints parameterized by breakpoints and values using semialgebraic sets. A cell descrption of semialgebraic sets can be accessed:: - >>> all([isinstance(cell, BasicSemialgebraicSet_base) for cell in PiMin_4.get_semialgebraic_sets()]) + sage: all([isinstance(cell, BasicSemialgebraicSet_base) for cell in PiMin_2.get_semialgebraic_sets()]) True Data is stored as repersenative elements. The number of repersentative elements grows quickly. :: - >>> len([rep_elem for rep_elem in PiMin_4.get_rep_elems()]) + sage: PiMin_4 = PiMinContContainer(4) # not tested + sage: len([rep_elem for rep_elem in PiMin_4.get_rep_elems()]) # not tested 987 + sage: len([rep_elem for rep_elem in PiMin_2.get_rep_elems()]) + 3 The container provides methods of writing data.:: - >>> PiMin_4.write_data() - + sage: PiMin_2.write_data() # Written data can be reused :: - >>> PiMin_4_loaded_data = PiMinContContainer(4, load_rep_elem_data="Pi_Min_4.csv") - >>> len([rep_elem for rep_elem in PiMin_4_loaded_data.get_rep_elems()]) - 987 + sage: PiMin_2_loaded_data = PiMinContContainer(2, load_rep_elem_data="Pi_Min_2.csv") + sage: len([rep_elem for rep_elem in PiMin_2_loaded_data.get_rep_elems()]) + 3 """ def __init__(self, n, **kwrds): self._n = n @@ -544,8 +628,9 @@ class PiMinContContainer: for row in file_reader: self._data.append([eval(preparse(data)) for data in row]) else: - logger.info("Generating representative elements. This might take a while.") + minimal_function_cache_logger.info("Generating representative elements. This might take a while.") bkpts = make_rep_bkpts_with_len_n(self._n) + minimal_function_cache_logger.info("Finished generating elements.") self._data = find_minimal_function_reps_from_bkpts(bkpts) def __repr__(self): From cf11adfad0739fd788f04608be158c7b4aacdf2b Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Tue, 3 Mar 2026 13:27:57 -0800 Subject: [PATCH 43/64] add additional doc test, add in pplite backend support --- .../igp/minimal_function_cache.sage | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index e753e0460..0d6c0c2b3 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -9,7 +9,7 @@ import logging minimal_function_cache_logger = logging.getLogger("cutgeneratingfunctionology.igp.minimal_function_cache") minimal_function_cache_logger.setLevel(logging.INFO) -### Note to future reader, from yours truely. ### +### Note to future reader: ### ### bkpt is assumed to be a breakpoint sequence of length n>= 2. ### Breakpoint sequence are sorted lists of real numbers in [0,1). ### A breakpoint sequences should always have 0 as an element. @@ -20,6 +20,7 @@ minimal_function_cache_logger.setLevel(logging.INFO) log_paramateric_real_field = False log_pw_functions = False + class RepElemGenFailure(Exception): pass @@ -51,7 +52,7 @@ def nnc_poly_from_bkpt_sequence(bkpt, backend=None): logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(logging.ERROR) for i in range(0,n): coord_names.append('lambda'+str(i)) - K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True) + K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, default_backend=backend) K.gens()[0] == 0 for i in range(n-1): K.gens()[i] < K.gens()[i+1] @@ -109,6 +110,8 @@ def add_breakpoints_and_find_equiv_classes(bkpt_poly): model_bound_bkpts = [0]*k model_bound_bkpts[k-1] = 1 # 0 < lambda_k <1 + # Using ppl and pplite type bsas have the same signature, so this should work regardless of backend. + # this will fail if the BSA attached does not have these methods. B_cap_N_b.add_linear_constraint(model_bound_bkpts, -1, operator.lt) # model bounds B_cap_N_b.add_linear_constraint(model_bound_bkpts, 0, operator.gt) # model bounds bkpt_order = [0]*k @@ -259,7 +262,7 @@ def value_nnc_polyhedron_value_cords(bkpt, f_index, backend =None): val = [None]*(n) for i in range(n): coord_names.append('gamma'+str(i)) - K = ParametricRealField(names=coord_names, values = val, mutable_values=True, big_cells=True, allow_refinement=False) + K = ParametricRealField(names=coord_names, values = val, mutable_values=True, big_cells=True, allow_refinement=False, default_backend=backend) K.gens()[0] == 0 for i in range(1, n): K.gens()[i] <=1 @@ -278,7 +281,7 @@ def value_nnc_polyhedron_value_cords(bkpt, f_index, backend =None): logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) return K._bsa -def value_nnc_polyhedron(bkpt, f_index): +def value_nnc_polyhedron(bkpt, f_index, backend=None): """ For a given breakpoints seqeunce and f index, write the value polyhedron as a basic semialgebraic set in the full space of parameters. @@ -294,8 +297,11 @@ def value_nnc_polyhedron(bkpt, f_index): sage: from cutgeneratingfunctionology.igp import * sage: logging.disable(logging.INFO) # suppress logging for tests - sage: value_nnc_polyhedron([0,4/5], 1) # gmic with f=4/5 + sage: value_nnc_polyhedron([0,4/5], 1) # gmic with f=4/5, a trivial value polyhedron BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(Constraint_System {x3-1==0, x2==0, 5*x1-4==0, x0==0}, names=[x0, x1, x2, x3]), polynomial_map=[lambda0, lambda1, gamma0, gamma1]) + sage: P = value_nnc_polyhedron([0,2/15,2/3, 4/5], 3) # A non trivial value polyhedron. + sage: P.upstairs()._polyhedron + A 1-dimensional polyhedron in QQ^8 defined as the convex hull of 2 points """ n = len(bkpt) if not log_paramateric_real_field: @@ -314,7 +320,7 @@ def value_nnc_polyhedron(bkpt, f_index): coord_names.append('lambda'+str(i)) for i in range(n): coord_names.append('gamma'+str(i)) - K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, allow_refinement=False) + K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, allow_refinement=False, default_backend=backend) # breakpoint parameters are the mesured breakpoint values. for i in range(n): K.gens()[i] == bkpt[i] @@ -338,7 +344,7 @@ def value_nnc_polyhedron(bkpt, f_index): return K._bsa -def bsa_of_rep_element(bkpt, vals): +def bsa_of_rep_element(bkpt, vals, backend=None): """ Given pi_(bkpt, vals) is {minimal, not minimal}, find BSA subset of R^(2n) such that (bkpt, vals) in BSA and for all p in BSA, pi_p is {minimal, not minimal}. @@ -367,7 +373,7 @@ def bsa_of_rep_element(bkpt, vals): coord_names.append('lambda'+str(i)) for i in range(n): coord_names.append('gamma'+str(i)) - K = ParametricRealField(names=coord_names, values = bkpt+vals, big_cells=True) + K = ParametricRealField(names=coord_names, values = bkpt+vals, big_cells=True, default_backend=backend) h = piecewise_function_from_breakpoints_and_values(K.gens()[0:n] + [1], K.gens()[n:2*n] + [0], merge=False) minimality_test(h) if not log_paramateric_real_field: @@ -405,7 +411,7 @@ def find_minimal_function_reps_from_bkpts(bkpts, prove_minimality=True, backend= for bkpt in bkpts: n = len(bkpt) for f_index in range(1, n): - poly_bsa = value_nnc_polyhedron_value_cords(list(bkpt), f_index) + poly_bsa = value_nnc_polyhedron_value_cords(list(bkpt), f_index, backend) gammas = poly_bsa.polynomial_map()[0].parent().gens() try: test_point = poly_bsa.upstairs().find_point() @@ -464,7 +470,7 @@ class BreakpointComplexClassContainer: if "load_rep_elem_data" in kwrds.keys(): if kwrds[load_rep_elem_data] is None: minimal_function_cache_logger.info("Generating representative elements. This might take a while.") - self._data = make_rep_bkpts_with_len_n(self._n) + self._data = make_rep_bkpts_with_len_n(self._n, self._backend) else: file_names = kwrds["load_bkpt_data"].split(",") self._data = [] @@ -479,7 +485,7 @@ class BreakpointComplexClassContainer: self._data = make_rep_bkpts_with_len_n(n, k, self._data) else: minimal_function_cache_logger.info("Generating representative elements. This might take a while.") - self._data = make_rep_bkpts_with_len_n(self._n) + self._data = make_rep_bkpts_with_len_n(self._n, self._backend) def __repr__(self): return f"Container for the space breakpoint sequences of length {self._n} under equivlance of polyhedral complexes." From 2ccd6d3c83250c2fa0e71a9e2c061dc7ec19fb58 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Tue, 3 Mar 2026 13:40:12 -0800 Subject: [PATCH 44/64] fix imports --- cutgeneratingfunctionology/spam/basic_semialgebraic_pplite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cutgeneratingfunctionology/spam/basic_semialgebraic_pplite.py b/cutgeneratingfunctionology/spam/basic_semialgebraic_pplite.py index 36e910e5e..62c25531a 100644 --- a/cutgeneratingfunctionology/spam/basic_semialgebraic_pplite.py +++ b/cutgeneratingfunctionology/spam/basic_semialgebraic_pplite.py @@ -1,4 +1,4 @@ -from cutgeneratingfunctionology.spam.basic_semialgebraic import BasicSemialgebraicSet_polyhedral +from cutgeneratingfunctionology.spam.basic_semialgebraic import * from pplite import Variable as pplite_Var, Constraint as pplite_Con, Linear_Expression as pplite_Lin_Expr, Affine_Expression as pplite_Aff_expr, NNC_Polyhedron as pplite_NNC_Polyhedron, PPliteGenerator, Polyhedron_Constraint_Rel, Polyhedron_Generator_Rel From ac8daf7d65d2a2585b1d0821bc34f2b7b4fe9dd6 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Wed, 4 Mar 2026 09:56:17 -0800 Subject: [PATCH 45/64] finish adding support to pplite backend; some spelling fixes. --- .../igp/minimal_function_cache.sage | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index 0d6c0c2b3..3f9e2a667 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -4,6 +4,7 @@ from cutgeneratingfunctionology.igp import * import csv import os from cutgeneratingfunctionology.spam.basic_semialgebraic import EmptyBSA +from cutgeneratingfunctionology.spam.basic_semialgebraic_pplite import BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron import logging minimal_function_cache_logger = logging.getLogger("cutgeneratingfunctionology.igp.minimal_function_cache") @@ -202,7 +203,7 @@ def make_rep_bkpts_with_len_n(n, k=1, bkpts=None, backend=None): minimal_function_cache_logger.info(f"Breakpoints of length {n} have been generated. ") return new_bkpts else: - minimal_function_cache_logger.info(f"Breakpoitns of lenght {k} have been generated. Now generating breakpoints of length{k+1}") + minimal_function_cache_logger.info(f"Breakpoints of lenght {k} have been generated. Now generating breakpoints of length {k+1}.") return make_rep_bkpts_with_len_n(n, k, new_bkpts) @@ -463,14 +464,13 @@ class BreakpointComplexClassContainer: self._n = n assert(self._n >= 2) if "backend" in kwrds.keys(): - if kwrds[backend] == "pplite": - self._backend = "pplite" - else: - self._backend = None + self._backend = kwrds["backend"] + else: + self._backend = None if "load_rep_elem_data" in kwrds.keys(): if kwrds[load_rep_elem_data] is None: minimal_function_cache_logger.info("Generating representative elements. This might take a while.") - self._data = make_rep_bkpts_with_len_n(self._n, self._backend) + self._data = make_rep_bkpts_with_len_n(self._n, backend = self._backend) else: file_names = kwrds["load_bkpt_data"].split(",") self._data = [] @@ -485,7 +485,7 @@ class BreakpointComplexClassContainer: self._data = make_rep_bkpts_with_len_n(n, k, self._data) else: minimal_function_cache_logger.info("Generating representative elements. This might take a while.") - self._data = make_rep_bkpts_with_len_n(self._n, self._backend) + self._data = make_rep_bkpts_with_len_n(self._n, backend = self._backend) def __repr__(self): return f"Container for the space breakpoint sequences of length {self._n} under equivlance of polyhedral complexes." @@ -612,10 +612,9 @@ class PiMinContContainer: self._n = n assert(self._n >= 2) if "backend" in kwrds.keys(): - if kwrds[backend] == "pplite": - self._backend = "pplite" - else: - self._backend = None + self._backend = kwrds["backend"] + else: + self._backend = None if "load_bkpt_data" in kwrds.keys() and "load_rep_elem_data" not in kwrds.keys(): file_names = kwrds["load_bkpt_data"].split(",") bkpts = [] @@ -624,7 +623,7 @@ class PiMinContContainer: file_reader = csv.reader(csvfile) for row in file_reader: bkpts.append([eval(preparse(data)) for data in row]) - self._data = find_minimal_function_reps_from_bkpts(bkpts) + self._data = find_minimal_function_reps_from_bkpts(bkpts, backend = self._backend) elif "load_bkpt_data" not in kwrds.keys() and "load_rep_elem_data" in kwrds.keys(): file_names = kwrds["load_rep_elem_data"].strip(" ").split(",") self._data = [] @@ -637,7 +636,7 @@ class PiMinContContainer: minimal_function_cache_logger.info("Generating representative elements. This might take a while.") bkpts = make_rep_bkpts_with_len_n(self._n) minimal_function_cache_logger.info("Finished generating elements.") - self._data = find_minimal_function_reps_from_bkpts(bkpts) + self._data = find_minimal_function_reps_from_bkpts(bkpts, backend = self._backend) def __repr__(self): return "Space of minimal functions with at most {} breakpoints parameterized by breakpoints and values using semialgebraic sets.".format(self._n) From 56bd618a624768f94a7245c7d7cc7dbb91cb2f0f Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:22:02 -0800 Subject: [PATCH 46/64] finished with pplite addition --- .../igp/minimal_function_cache.sage | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index 3f9e2a667..3a42699ce 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -204,7 +204,7 @@ def make_rep_bkpts_with_len_n(n, k=1, bkpts=None, backend=None): return new_bkpts else: minimal_function_cache_logger.info(f"Breakpoints of lenght {k} have been generated. Now generating breakpoints of length {k+1}.") - return make_rep_bkpts_with_len_n(n, k, new_bkpts) + return make_rep_bkpts_with_len_n(n, k, new_bkpts, backend) def generate_assumed_symmetric_vertices_continuous(fn, f, bkpt): @@ -485,7 +485,7 @@ class BreakpointComplexClassContainer: self._data = make_rep_bkpts_with_len_n(n, k, self._data) else: minimal_function_cache_logger.info("Generating representative elements. This might take a while.") - self._data = make_rep_bkpts_with_len_n(self._n, backend = self._backend) + self._data = make_rep_bkpts_with_len_n(self._n, backend=self._backend) def __repr__(self): return f"Container for the space breakpoint sequences of length {self._n} under equivlance of polyhedral complexes." @@ -496,7 +496,7 @@ class BreakpointComplexClassContainer: def get_nnc_poly_from_bkpt(self): for bkpt in self._data: - yield nnc_poly_from_bkpt_sequence(bkpt) + yield nnc_poly_from_bkpt_sequence(bkpt, backend=self._backend) def num_rep_elems(self): return len(self._data) @@ -524,7 +524,7 @@ class BreakpointComplexClassContainer: bkpt_contained_in_cell = True break if not contained_in_cell: - cells_found.append(nnc_poly_from_bkpt_sequence(bkpt)) + cells_found.append(nnc_poly_from_bkpt_sequence(bkpt, backend=self._backend)) new_data = [] for cell in cells_found: new_data.append(cell.find_point()) @@ -634,9 +634,9 @@ class PiMinContContainer: self._data.append([eval(preparse(data)) for data in row]) else: minimal_function_cache_logger.info("Generating representative elements. This might take a while.") - bkpts = make_rep_bkpts_with_len_n(self._n) + bkpts = make_rep_bkpts_with_len_n(self._n, backend=self._backend) minimal_function_cache_logger.info("Finished generating elements.") - self._data = find_minimal_function_reps_from_bkpts(bkpts, backend = self._backend) + self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) def __repr__(self): return "Space of minimal functions with at most {} breakpoints parameterized by breakpoints and values using semialgebraic sets.".format(self._n) @@ -644,7 +644,7 @@ class PiMinContContainer: def get_semialgebraic_sets(self): """Iterator for semialgebraic set description""" for b, v in self._data: - yield bsa_of_rep_element(list(b), list(v)) + yield bsa_of_rep_element(list(b), list(v), backend=self._backend) def get_rep_elems(self): """Iterator for representative elements.""" @@ -698,6 +698,5 @@ class PiMinContContainer: break out_file.close() output_file = file_name_base[:-1]+"{}".format(file_number+1)+".csv" - ### TODO: Add method to make loaded objects the same as generated objects if they repersent the same space. -### Plotting Utilties ### \ No newline at end of file + \ No newline at end of file From b2b612d515d07c9b7908a3a0624fea97fd1aabe7 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:46:39 -0800 Subject: [PATCH 47/64] add note for future self --- cutgeneratingfunctionology/igp/minimal_function_cache.sage | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index 3a42699ce..0008c3359 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -186,7 +186,11 @@ def make_rep_bkpts_with_len_n(n, k=1, bkpts=None, backend=None): sage: len(bkpts_rep_with_len_4) 329 """ - # Look into using a directed tree as an underlying data structure for generating elements. + # Matthias suggested looking at a directed tree. + # An alternative approach would be to look into using a (graded) lattice as a data strcture. + # We have bkpt \leq bkpt' if and only if dim(NNC(bkpt)) \leq dim(NNC(bkpt')) + # and embed(NNC(bkpt), dim(NNC(bkpt')) \leq NNC(bkpt') in the poset of NNC polyhedra. + # This might speed up/less the load of verifying unquiness of cells which is the time bounding task here. new_bkpts = [] if n < 2: raise ValueError("n>=2") From f1f333fe6e19699970c7c319890ef12ea3c8f349 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Tue, 24 Mar 2026 13:32:40 -0700 Subject: [PATCH 48/64] update loading methods for future existance of function cache, rename file --- .../igp/minimal_function_cache.sage | 81 +- .../igp/minimal_function_cell_description.py | 750 ++++++++++++++++++ 2 files changed, 805 insertions(+), 26 deletions(-) create mode 100644 cutgeneratingfunctionology/igp/minimal_function_cell_description.py diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage index 0008c3359..93893f1b5 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cache.sage @@ -604,43 +604,72 @@ class PiMinContContainer: The container provides methods of writing data.:: - sage: PiMin_2.write_data() # + sage: PiMin_2.write_data() # not tested Written data can be reused :: - sage: PiMin_2_loaded_data = PiMinContContainer(2, load_rep_elem_data="Pi_Min_2.csv") - sage: len([rep_elem for rep_elem in PiMin_2_loaded_data.get_rep_elems()]) + sage: PiMin_2_loaded_data = PiMinContContainer(2, manually_load_function_cache=True, load_files="Pi_Min_2.csv", data_type="rep elems") # not tested + sage: len([rep_elem for rep_elem in PiMin_2_loaded_data.get_rep_elems()]) # not tested 3 """ - def __init__(self, n, **kwrds): + def __init__(self, n, backend=None, manually_load_function_cache=False, **loading_kwrds): self._n = n assert(self._n >= 2) if "backend" in kwrds.keys(): self._backend = kwrds["backend"] else: self._backend = None - if "load_bkpt_data" in kwrds.keys() and "load_rep_elem_data" not in kwrds.keys(): - file_names = kwrds["load_bkpt_data"].split(",") - bkpts = [] - for file_name in file_names: - with open(file_name, newline='' ) as csvfile: - file_reader = csv.reader(csvfile) - for row in file_reader: - bkpts.append([eval(preparse(data)) for data in row]) - self._data = find_minimal_function_reps_from_bkpts(bkpts, backend = self._backend) - elif "load_bkpt_data" not in kwrds.keys() and "load_rep_elem_data" in kwrds.keys(): - file_names = kwrds["load_rep_elem_data"].strip(" ").split(",") - self._data = [] - for file_name in file_names: - with open(file_name, newline='' ) as csvfile: - file_reader = csv.reader(csvfile) - for row in file_reader: - self._data.append([eval(preparse(data)) for data in row]) + if not manually_load_function_cache: + try: + # Load the minimal function cache. + from cacheUtils.utils import cache_loader + except ImportError: + if self._n > 4: + minimal_function_cache_logger.warning(f"This may take a while. Try installing the minimalfunctioncache.") + bkpts = make_rep_bkpts_with_len_n(self._n, backend=self._backend) + minimal_function_cache_logger.info("Finished generating breakpoints.") + minimal_function_cache_logger.info("Computing repersentative elements.") + self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) + try: + self._data = cache_loader(self._n, "rep elems") + except ValueError: + try: + bkpts = cache_loader(n, "breakpoints") + except ValueError: + minimal_function_cache_logger.info(f"The cache for {n} breakpoints has not been generated or could not be found.") + minimal_function_cache_logger.info("Generating representative breakpoints.") + if n> 4: + + + minimal_function_cache_logger.info("PiMin container, Reportin' for duty.") + elif manually_load_function_cache: + # this is for generating + try: + if loading_kwrds.keys("breakpoints_or_rep_elems").strip(" ").lower() == "breakpoints": + bkpts = [] + with open(loading_kwrds.keys("path_to_file_or_file_name_in_cwd")) as csvfile: + file_reader = csv.reader(csvfile) + for row in file_reader: + bkpts.append([QQ(data) for data in row]) + self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) + except OSError: + with open(loading_kwrds.keys("path_to_file_or_file_name_in_cwd")) as csvfile: + file_reader = csv.reader(csvfile) + for row in file_reader: + bkpts.append([QQ(data) for data in row]) + self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) + if loading_kwrds.keys("breakpoints_or_rep_elems").strip(" ").lower() == "rep_elems": + self._data = [] + with open(loading_kwrds.keys("path_to_file_or_file_name_in_cwd")) as csvfile: + file_reader = csv.reader(csvfile) + for row in file_reader: + self._data.append([QQ(data) for data in row]) + except KeyError: + raise ValueError("No valid inputs provded. Maybe check your spelling?") + else: - minimal_function_cache_logger.info("Generating representative elements. This might take a while.") - bkpts = make_rep_bkpts_with_len_n(self._n, backend=self._backend) - minimal_function_cache_logger.info("Finished generating elements.") - self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) + raise ValueError("manually_load_function_cache is a python bool") + def __repr__(self): return "Space of minimal functions with at most {} breakpoints parameterized by breakpoints and values using semialgebraic sets.".format(self._n) @@ -703,4 +732,4 @@ class PiMinContContainer: out_file.close() output_file = file_name_base[:-1]+"{}".format(file_number+1)+".csv" - \ No newline at end of file + def _load_file_or_dir() \ No newline at end of file diff --git a/cutgeneratingfunctionology/igp/minimal_function_cell_description.py b/cutgeneratingfunctionology/igp/minimal_function_cell_description.py new file mode 100644 index 000000000..050035929 --- /dev/null +++ b/cutgeneratingfunctionology/igp/minimal_function_cell_description.py @@ -0,0 +1,750 @@ +from copy import deepcopy +from itertools import pairwise +from cutgeneratingfunctionology.igp import * +import csv +import os +from cutgeneratingfunctionology.spam.basic_semialgebraic import EmptyBSA +from cutgeneratingfunctionology.spam.basic_semialgebraic_pplite import BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron +import logging + +minimal_funciton_cell_description_logger = logging.getLogger("cutgeneratingfunctionology.igp.minimal_funciton_cell_description") +minimal_funciton_cell_description_logger.setLevel(logging.INFO) + +### Note to future reader: ### +### bkpt is assumed to be a breakpoint sequence of length n>= 2. +### Breakpoint sequence are sorted lists of real numbers in [0,1). +### A breakpoint sequences should always have 0 as an element. +### This is never strictly enforced in this file and it is assumed that +### the user is always provided a breakpoint sequence. + +# global defaults for logging from portions of igp; change to log different parts of igp. +log_paramateric_real_field = False +log_pw_functions = False + + +class RepElemGenFailure(Exception): + pass + + +def nnc_poly_from_bkpt_sequence(bkpt, backend=None): + r""" + Defines an NNC polyhedron P such that for all b in P the delta complex of b is isomoprhic to the delta complex of bkpt. + + INPUT: + - ``bkpt`` - sorted list of length 2 or more of sage type in the interval [0,1). + - ```backend`` - ``None``, ``str(pplite)`` + + OUTPUT: class::``BasicSemialgebraicSet_veronese`` + + EXAMPLES:: + sage: from cutgeneratingfunctionology.igp import * + sage: logging.disable(logging.INFO) # suppress logging for tests + sage: nnc_poly_from_bkpt_sequence([0, 4/5]) + BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(Constraint_System {x0==0, -x1+1>0, 2*x1-1>0}, names=[x0, x1]), polynomial_map=[lambda0, lambda1]) + """ + n = len(bkpt) + # assert(n >= 2) + coord_names = [] + bkpt_vals = bkpt + vals = bkpt_vals[0:n] + bkpt_extd = list(bkpt)+[1] + if not log_paramateric_real_field: + parametric_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.parametric").getEffectiveLevel() + logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(logging.ERROR) + for i in range(0,n): + coord_names.append('lambda'+str(i)) + K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, default_backend=backend) + K.gens()[0] == 0 + for i in range(n-1): + K.gens()[i] < K.gens()[i+1] + K.gens()[n-1] < 1 + for i in range(n): + for j in range(n): + if bkpt[i]+bkpt[j]>= 1: + w = 1 + else: + w = 0 + for k in range(n): + if bkpt_extd[k] < bkpt[i]+bkpt[j] - w and bkpt[i]+bkpt[j] - w < bkpt_extd[k+1]: + if k != n-1: + K.gens()[k] < K.gens()[i] + K.gens()[j] - w + K.gens()[i] + K.gens()[j] - w < K.gens()[k+1] + else: + K.gens()[k] < K.gens()[i] + K.gens()[j] - w + K.gens()[i] + K.gens()[j] - w < 1 + elif bkpt_extd[k] == bkpt[i]+bkpt[j] - w: + if k != n-1: + K.gens()[k] == K.gens()[i] + K.gens()[j] - w + K.gens()[i] + K.gens()[j] - w < K.gens()[k+1] + else: + K.gens()[k] == K.gens()[i] + K.gens()[j] - w + K.gens()[i] + K.gens()[j] - w < 1 + if not log_paramateric_real_field: + logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(parametric_logging_level) + return K._bsa + + +def add_breakpoints_and_find_equiv_classes(bkpt_poly): + """ + Takes dim k-1 breakpoint NNC polyhedron (as a :class:`BasicSemialgebraicSet_base`), adds a dimension, + and finds all possible representive elements for equivlance classes of polyhedral complexes. + + INPUT: :class:`BasicSemialgebraicSet_Polyhedral` + + OUTPUT: unique list of breakpoint sequnes of lenght k (as tuples). + + EXAMPLES:: + + sage: from cutgeneratingfunctionology.igp import * + sage: logging.disable(logging.INFO) # suppress logging for tests + sage: add_breakpoints_and_find_equiv_classes(nnc_poly_from_bkpt_sequence([0,4/5]).upstairs()) + [(0, 9/14, 83/112), (0, 13/20, 33/40), (0, 9/14, 101/112), (0, 7/10, 17/20)] + + """ + # BSAs are highly mutable, work only with copies. + B_cap_N_b = copy(bkpt_poly) + B_cap_N_b.add_space_dimensions_and_embed(1) + # get new number of breakpoints. + k = B_cap_N_b.ambient_dim() + if k< 1: + raise ValueError("bkpt_poly should have space dim at least 1.") + model_bound_bkpts = [0]*k + model_bound_bkpts[k-1] = 1 + # 0 < lambda_k <1 + # Using ppl and pplite type bsas have the same signature, so this should work regardless of backend. + # this will fail if the BSA attached does not have these methods. + B_cap_N_b.add_linear_constraint(model_bound_bkpts, -1, operator.lt) # model bounds + B_cap_N_b.add_linear_constraint(model_bound_bkpts, 0, operator.gt) # model bounds + bkpt_order = [0]*k + bkpt_order[k-2] = 1 + bkpt_order[k-1] = -1 + B_cap_N_b.add_linear_constraint(bkpt_order, 0, operator.lt) # order on bkpts + rep_elems = [] + for j in range(k-1): + for i in range(k): + for interval_w in [0,1]: + for line_w in [0,1]: + # which interval is (lambda_k,lambda_k) located in? + # modeled lambda_i op 2lambda_k - w < lambda_{i+1} + for interval_op in [operator.lt, operator.eq, operator.gt]: + for line_op in [operator.lt, operator.eq, operator.gt]: + # highly mutable objects, operate on the copy. + B_cap_N_b_copy = copy(B_cap_N_b) + lhs_i = [0]*k + lhs_i[k-1] = -2 + lhs_i[i] = 1 + B_cap_N_b_copy.add_linear_constraint(lhs_i, interval_w, interval_op) + lhs_i_plus_1 = [0]*k + lhs_i_plus_1[k-1] = -2 + if i < k-1: + lhs_i_plus_1[i+1] = 1 + B_cap_N_b_copy.add_linear_constraint(lhs_i_plus_1, interval_w, operator.gt) + else: + B_cap_N_b_copy.add_linear_constraint(lhs_i_plus_1, interval_w + 1, operator.gt) + if not B_cap_N_b_copy.is_empty(): + # does the line x+y equiv lambda_k mod 1 lie on/above/below (lambda_j,lambda_j)? + # modeled by 2lambda_j op lambda_k + w + lhs_j = [0]*k + lhs_j[j] = 2 + lhs_j[k-1] = -1 + B_cap_N_b_copy.add_linear_constraint(lhs_j, -line_w, line_op) + try: + rep_elem = B_cap_N_b_copy.find_point() + rep_elems.append(tuple(rep_elem)) + except EmptyBSA: + pass + return unique_list(rep_elems) + + +def make_rep_bkpts_with_len_n(n, k=1, bkpts=None, backend=None): + r""" + Produce representative elements of every isomorphism class of breakpoints complexes for breakpoint sequences of length n. + + INPUT: + - n, integer, maximum length of breakpoint sequence. + - k, assumed length of breakpoint sequences in ``bkpts`` + - bkpts, list of breakpoint sequenes (sorted , length of every element, bkpts, an iterable of breakpoints all of length k. + + OUTPUT: A list of representative elements of every isomorphism class of breakpoints complexes for breakpoint sequences of length n extrapolated from bkpts. + + EXAMPLES:: + + sage: from cutgeneratingfunctionology.igp import * + sage: logging.disable(logging.INFO) # suppress logging for tests + sage: make_rep_bkpts_with_len_n(2) + [(0, 1/2), (0, 13/18), (0, 5/18)] + + The number of representative elements grows quickly:: + + sage: bkpts_rep_with_len_3 = make_rep_bkpts_with_len_n(3) + sage: len(bkpts_rep_with_len_3) + 34 + + Previous computations can be reused:: + + sage: bkpts_rep_with_len_4 = make_rep_bkpts_with_len_n(4, 3, bkpts_rep_with_len_3) + sage: len(bkpts_rep_with_len_4) + 329 + """ + # Matthias suggested looking at a directed tree. + # An alternative approach would be to look into using a (graded) lattice as a data strcture. + # We have bkpt \leq bkpt' if and only if dim(NNC(bkpt)) \leq dim(NNC(bkpt')) + # and embed(NNC(bkpt), dim(NNC(bkpt')) \leq NNC(bkpt') in the poset of NNC polyhedra. + # This might speed up/less the load of verifying unquiness of cells which is the time bounding task here. + new_bkpts = [] + if n < 2: + raise ValueError("n>=2") + if k == n: + raise ValueError("k= 2) + assert(f_index >= 1) + assert(f_index <= n - 1) + if not isinstance(bkpt, list): + bkpt = list(bkpt) + coord_names = [] + val = [None]*(n) + for i in range(n): + coord_names.append('gamma'+str(i)) + K = ParametricRealField(names=coord_names, values = val, mutable_values=True, big_cells=True, allow_refinement=False, default_backend=backend) + K.gens()[0] == 0 + for i in range(1, n): + K.gens()[i] <=1 + K.gens()[i] > 0 + h = piecewise_function_from_breakpoints_and_values(bkpt + [1], K.gens() + [0], merge=False) + # Assumes minimality for the partially defined function. + for vert in generate_type_1_vertices_continuous(h, operator.ge, bkpt + [1]): + vert + for vert in generate_type_2_vertices_continuous(h, operator.ge, bkpt + [1]): + vert + for vert in generate_assumed_symmetric_vertices_continuous(h, bkpt[f_index], bkpt + [1]): + vert + if not log_paramateric_real_field: + logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(parametric_logging_level) + if not log_pw_functions: + logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) + return K._bsa + +def value_nnc_polyhedron(bkpt, f_index, backend=None): + """ + For a given breakpoints seqeunce and f index, write the value polyhedron as a basic semialgebraic set in the full space of parameters. + + INPUTS: + - ``bkpt`` - sorted list of length 2 or more of sage type in the interval [0,1). + - ``f_index`` - integer between 1 and length of ``len(bkpt) -1``. + - ``backend`` - ``None``, ``str(pplite)`` + + OUTPUT: + - class::``BasicSemialgebraicSet_veronese`` + + EXAMPLES:: + + sage: from cutgeneratingfunctionology.igp import * + sage: logging.disable(logging.INFO) # suppress logging for tests + sage: value_nnc_polyhedron([0,4/5], 1) # gmic with f=4/5, a trivial value polyhedron + BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(Constraint_System {x3-1==0, x2==0, 5*x1-4==0, x0==0}, names=[x0, x1, x2, x3]), polynomial_map=[lambda0, lambda1, gamma0, gamma1]) + sage: P = value_nnc_polyhedron([0,2/15,2/3, 4/5], 3) # A non trivial value polyhedron. + sage: P.upstairs()._polyhedron + A 1-dimensional polyhedron in QQ^8 defined as the convex hull of 2 points + """ + n = len(bkpt) + if not log_paramateric_real_field: + parametric_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.parametric").getEffectiveLevel() + logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(logging.ERROR) + if not log_pw_functions: + pw_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.functions").getEffectiveLevel() + logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(logging.ERROR) + assert(n >= 2) + assert(f_index >= 1) + assert(f_index <= n) + coord_names = [] + bkpt_vals = list(bkpt) + vals = bkpt_vals + [None]*(n) + for i in range(n): + coord_names.append('lambda'+str(i)) + for i in range(n): + coord_names.append('gamma'+str(i)) + K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, allow_refinement=False, default_backend=backend) + # breakpoint parameters are the mesured breakpoint values. + for i in range(n): + K.gens()[i] == bkpt[i] + # necessary conditions on value parameters + K.gens()[n] == 0 + for i in range(1, n): + K.gens()[i+n] <=1 + K.gens()[i+n] > 0 + h = piecewise_function_from_breakpoints_and_values(K.gens()[0:n] + [1], K.gens()[n:2*n] + [0], merge=False) + # Assumes minimality for the partially defined function. + for vert in generate_type_1_vertices_continuous(h, operator.ge, K.gens()[0:n] + [1]): + vert + for vert in generate_type_2_vertices_continuous(h, operator.ge, K.gens()[0:n] + [1]): + vert + for vert in generate_assumed_symmetric_vertices_continuous(h, K.gens()[f_index], [0] + K.gens()[0:n] + [1]): + vert + if not log_paramateric_real_field: + logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(parametric_logging_level) + if not log_pw_functions: + logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) + return K._bsa + + +def bsa_of_rep_element(bkpt, vals, backend=None): + """ + Given pi_(bkpt, vals) is {minimal, not minimal}, find BSA subset of R^(2n) such that (bkpt, vals) in BSA and for all p + in BSA, pi_p is {minimal, not minimal}. + + INPUT: + - ``bkpt`` - a breakpoint seqeunce + - ``vals`` - list like of sage numerical types corrosponding values for the breakpoint sequence. + - ``backend`` - None, ``str(pplite)`` + + OUTPUT: A basic semialgebraic set. + + EXAMPLES:: + + + """ + n = len(bkpt) + if not log_paramateric_real_field: + parametric_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.parametric").getEffectiveLevel() + logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(logging.ERROR) + if not log_pw_functions: + pw_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.functions").getEffectiveLevel() + logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(logging.ERROR) + assert(n>=2) + coord_names = [] + for i in range(n): + coord_names.append('lambda'+str(i)) + for i in range(n): + coord_names.append('gamma'+str(i)) + K = ParametricRealField(names=coord_names, values = bkpt+vals, big_cells=True, default_backend=backend) + h = piecewise_function_from_breakpoints_and_values(K.gens()[0:n] + [1], K.gens()[n:2*n] + [0], merge=False) + minimality_test(h) + if not log_paramateric_real_field: + logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(parametric_logging_level) + if not log_pw_functions: + logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) + return K.make_proof_cell().bsa + + +def find_minimal_function_reps_from_bkpts(bkpts, prove_minimality=True, backend=None): + """ + Finds representative elements of minimal functions from a given breakpoint sequence. + + INPUT: + - ``bkpts`` - an interable of breakpoint seqeunces. + - ``prove_minimality`` - bool, proves minimality of paramaterized function + - ``backend`` - None, ``str(pplite)`` + + OUTPUT: + - List of tuples of lists + + EXAMPLES:: + + sage: from cutgeneratingfunctionology.igp import * + sage: logging.disable(logging.INFO) # suppress logging for tests + sage: bkpts = make_rep_bkpts_with_len_n(2) + sage: find_minimal_function_reps_from_bkpts(bkpts) + [([0, 1/2], [0, 1]), ([0, 13/18], [0, 1]), ([0, 5/18], [0, 1])] + + """ + rep_elems = [] + if not log_pw_functions: + pw_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.functions").getEffectiveLevel() + logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(logging.ERROR) + for bkpt in bkpts: + n = len(bkpt) + for f_index in range(1, n): + poly_bsa = value_nnc_polyhedron_value_cords(list(bkpt), f_index, backend) + gammas = poly_bsa.polynomial_map()[0].parent().gens() + try: + test_point = poly_bsa.upstairs().find_point() + except EmptyBSA: + raise RepElemGenFailure("The value polyhedron {} is empty. This should not be empty. Double check inputs".format(poly_bsa)) + test_val = [] + for gamma_i in gammas: + test_val.append(test_point[poly_bsa.v_dict()[gamma_i]]) + if prove_minimality: + h = piecewise_function_from_breakpoints_and_values(list(bkpt)+[1], test_val+[0]) + if not minimality_test(h): # The following error should never be raised when this function is used as intended. + raise ValueError(f"({bkpt}, {test_val}) paramaterized by breakpoints and values is not a minimal function but assuming a breakpoint sequence is input, this should be minimal.") + rep_elems.append((list(bkpt), test_val)) + if not log_pw_functions: + logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) + return rep_elems + + +class BreakpointComplexClassContainer: + """ + A container for the family of breakpoint complexes for peicewise linear functions + with at most n breakpoints. + + The container assumes that loaded data is correct and performs no checking + that the loaded data represnts the full space. + + This class contains ways to read/write data for use with minimal function generation. + + EXAMPLES:: + + sage: from cutgeneratingfunctionology.igp import * + sage: logging.disable(logging.INFO) # suppress logging for tests + sage: bkpt_of_len_2 = BreakpointComplexClassContainer(2) + sage: bkpt_of_len_2.num_rep_elems() + 3 + sage: [elem for elem in bkpt_of_len_2.get_rep_elems()] + [(0, 1/2), (0, 13/18), (0, 5/18)] + sage: make_rep_bkpts_with_len_n(2) + [(0, 1/2), (0, 13/18), (0, 5/18)] + + A cell descrption of semialgebraic sets can be accessed:: + + sage: all([isinstance(cell, BasicSemialgebraicSet_base) for cell in bkpt_of_len_2.get_nnc_poly_from_bkpt()]) + True + + + """ + def __init__(self, n, backend=None, manually_load_breakpoint_cache=False, **loading_kwrds): + self._n = n + assert(self._n >= 2) + self._backend = backend + if not manually_load_breakpoint_cache: + try: + # Load the minimal function cache. + from cacheUtils.utils import cache_loader, cache_info + except ImportError: + if self._n > 4: + minimal_funciton_cell_description_logger.warning(f"This may take a while. Try installing the minimalfunctioncache.") + self._data = make_rep_bkpts_with_len_n(self._n, backend=self._backend) + try: + self._data = cache_loader(self._n, "breakpoints") # cache loader throws a value error if a cache for n is not found. + except ValueError: + minimal_funciton_cell_description_logger.info(f"The cache for {n} breakpoints has not been generated or could not be found.") + minimal_funciton_cell_description_logger.info("Generating representative breakpoints.") + k = max([ i for i in cache_info["avail_bkpts"] if i < self._n]) + if k is None: + raise ValueError + prev_gen_bkpts = self._data = cache_loader(self._n, "breakpoints") + if self._n > 4: + minimal_funciton_cell_description_logger.warning(f"This may take a while.") + self._data = make_rep_bkpts_with_len_n(self._n, k, prev_gen_bkpts, backend=self._backend) + minimal_funciton_cell_description_logger.info("Finished generating breakpoints.") + elif manually_load_breakpoint_cache: + # this is for generating the cache and advanced use. + try: + if loading_kwrds.keys("breakpoints_or_rep_elems").strip(" ").lower() == "breakpoints": + bkpts = [] + with open(loading_kwrds.keys("path_to_file_or_file_name_in_cwd")) as csvfile: + file_reader = csv.reader(csvfile) + for row in file_reader: + bkpts.append([QQ(data) for data in row]) + self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) + except KeyError: + if loading_kwrds.keys("breakpoints_or_rep_elems").strip(" ").lower() == "rep elems": + self._data = [] + with open(loading_kwrds.keys("path_to_file_or_file_name_in_cwd")) as csvfile: + file_reader = csv.reader(csvfile) + for row in file_reader: + self._data.append([QQ(data) for data in row]) + else: + raise ValueError("No valid inputs provded. Maybe check your spelling?") + except KeyError: + raise ValueError("No valid inputs provded. Maybe check your spelling?") + else: + raise ValueError("No elements have been loaded. Check inputs") + + def __repr__(self): + return f"Container for the space breakpoint sequences of length {self._n} under equivlance of polyhedral complexes." + + def get_rep_elems(self): + for bkpt in self._data: + yield bkpt + + def get_nnc_poly_from_bkpt(self): + for bkpt in self._data: + yield nnc_poly_from_bkpt_sequence(bkpt, backend=self._backend) + + def num_rep_elems(self): + return len(self._data) + + # def add_one_bkpt_to_all(self): + # minimal_funciton_cell_description_logger.info("Generating representative elements. This might take a while.") + # self._n = n+1 + # self._data = make_bkpts_with_len_n(self._n, self._n-1, self._data) + + def covers_space(self): + ### TODO: This method should prove that container is correct. + raise NotImplementedError + + def refine_space(self): + """Ensures that repersentative elements are unique and contained in a single cell. """ + raise NotImplementedError + ### TODO: Test this. I think I need to write a containment method for the BSA + ### or pick the up the underlying polyhedron in the BSA. + self._data = unique_list(self._data) + cells_found = [] + for bkpt in self._data: + bkpt_contained_in_cell = False + for found_cell in cells_found: + if found_cell.contains(bkpt): + bkpt_contained_in_cell = True + break + if not contained_in_cell: + cells_found.append(nnc_poly_from_bkpt_sequence(bkpt, backend=self._backend)) + new_data = [] + for cell in cells_found: + new_data.append(cell.find_point()) + self._data = new_data + + def write_data(self, output_file_name_style=None, max_rows=None): + """ + Writes representative element data to a `.csv` file with one column and rows of representative elements. + Optionally, write many `.csv` files with at `most max_rows` rows per file. + Files are named output_file_file_name_style_filenumber.csv. + The default output_file_name_style="bkpts_of_len_n". + """ + # TODO: Future, support writing different types of data such as polyhedra data. + if output_file_name_style is None: + file_name_base = "bkpts_of_len_{}".format(self._n) + else: + file_name_base =output_file_name_style + if max_rows is not None: + assert(max_rows >= 1) + num_files = len(self._data)//max_rows + 1 + file_name_base = file_name_base + "_part_0" + if max_rows is None: + max_rows = 0 + output_file = file_name_base +".csv" + for file_number in range(num_files): + out_file = open(output_file, "w") + data_writer = csv.writer(out_file, csv.QUOTE_NONE) + for row in range(max_rows): + try: + data_writer.writerow(self._data[max_rows * file_number + row]) + except IndexError: + break + out_file.close() + output_file = file_name_base[:-1]+"{}".format(file_number+1)+".csv" + + +class PiMinContContainer: + """ + A container for the space of continuous piecewise linear minimal functions with at + most n breakpoints paramaterized by breakpoints and values using semialgebraic sets. + + The container assumes that loaded data is correct and performs no checking. + + INPUTS: + - n, an integer + keywords: + - backend, None or str(pplite) + - load_bkpt_data, .csv file(s) of list of tuples of breakpoitns + - load_rep_elem_data, .csv file(s) of list of tuples of representative elements of the space of minimal functions + + EXAMPLES:: + + sage: from cutgeneratingfunctionology.igp import * + sage: logging.disable(logging.INFO) # suppress logging for tests + sage: PiMin_2 = PiMinContContainer(2) + sage: all([minimality_test(pi) for pi in PiMin_2.get_rep_functions()]) + True + sage: PiMin_2 + Space of minimal functions with at most 2 breakpoints parameterized by breakpoints and values using semialgebraic sets. + + A cell descrption of semialgebraic sets can be accessed:: + + sage: all([isinstance(cell, BasicSemialgebraicSet_base) for cell in PiMin_2.get_semialgebraic_sets()]) + True + + Data is stored as repersenative elements. The number of repersentative elements grows quickly. :: + + sage: PiMin_4 = PiMinContContainer(4) # not tested + sage: len([rep_elem for rep_elem in PiMin_4.get_rep_elems()]) # not tested + 987 + sage: len([rep_elem for rep_elem in PiMin_2.get_rep_elems()]) + 3 + + The container provides methods of writing data.:: + + sage: PiMin_2.write_data() # not tested + + Written data can be reused :: + + sage: PiMin_2_loaded_data = PiMinContContainer(2, manually_load_function_cache=True, path_to_file_or_file_name_in_cwd="Pi_Min_2.csv", breakpoints_or_rep_elems="rep elems") # not tested + sage: len([rep_elem for rep_elem in PiMin_2_loaded_data.get_rep_elems()]) # not tested + 3 + """ + def __init__(self, n, backend=None, manually_load_function_cache=False, **loading_kwrds): + self._n = n + assert(self._n >= 2) + self._backend = backend + if not manually_load_function_cache: + try: + # Load the minimal function cache. + from cacheUtils.utils import cache_loader + except ImportError: + if self._n > 4: + minimal_funciton_cell_description_logger.warning(f"This may take a while. Try installing the minimalfunctioncache.") + bkpts = make_rep_bkpts_with_len_n(self._n, backend=self._backend) + minimal_funciton_cell_description_logger.info("Finished generating breakpoints.") + minimal_funciton_cell_description_logger.info("Computing repersentative elements.") + self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) + try: + self._data = cache_loader(self._n, "rep elems") # cache loader throws a value error if a cache for n is not found. + except ValueError: + try: + bkpts = (n, "breakpoints") + except ValueError: + minimal_funciton_cell_description_logger.info(f"The cache for {n} breakpoints has not been generated or could not be found.") + minimal_funciton_cell_description_logger.info("Generating representative breakpoints.") + if self._n > 4: + minimal_funciton_cell_description_logger.warning(f"This may take a while.") + bkpts = BreakpointComplexClassContainer(self._n, backend=self._backend) + minimal_funciton_cell_description_logger.info("Finished generating breakpoints.") + minimal_funciton_cell_description_logger.info("Computing repersentative elements.") + self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) + minimal_funciton_cell_description_logger.info("PiMin container, Reportin' for duty.") + elif manually_load_function_cache: + # this is for generating the cache and advanced use. + try: + if loading_kwrds.keys("breakpoints_or_rep_elems").strip(" ").lower() == "breakpoints": + bkpts = [] + with open(loading_kwrds.keys("path_to_file_or_file_name_in_cwd")) as csvfile: + file_reader = csv.reader(csvfile) + for row in file_reader: + bkpts.append([QQ(data) for data in row]) + self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) + except KeyError: + if loading_kwrds.keys("breakpoints_or_rep_elems").strip(" ").lower() == "rep elems": + self._data = [] + with open(loading_kwrds.keys("path_to_file_or_file_name_in_cwd")) as csvfile: + file_reader = csv.reader(csvfile) + for row in file_reader: + self._data.append([QQ(data) for data in row]) + else: + raise ValueError("No valid inputs provded. Maybe check your spelling?") + except KeyError: + raise ValueError("No valid inputs provded. Maybe check your spelling?") + else: + raise ValueError("No elements have been loaded. Check inputs") + + def __repr__(self): + return "Space of minimal functions with at most {} breakpoints parameterized by breakpoints and values using semialgebraic sets.".format(self._n) + + def get_semialgebraic_sets(self): + """Iterator for semialgebraic set description.""" + for b, v in self._data: + yield bsa_of_rep_element(list(b), list(v), backend=self._backend) + + def get_rep_elems(self): + """Iterator for representative elements.""" + for b, v in self._data: + yield (list(b), list(v)) + + def get_rep_functions(self): + """Iterator for representative functinos.""" + for b, v in self._data: + yield piecewise_function_from_breakpoints_and_values(list(b)+[1], list(v)+[0]) + + def n(self): + """The maximum number of proper breakpoints of paramaterized functions in the space.""" + return self._n + + def covers_space(self): + ### TODO: This method should prove that container is correct. + raise NotImplementedError + + def refine_space(self): + ### TODO: This method should be called when loading multiple file to ensure cells have not been duplicated. + raise NotImplementedError + + def write_data(self, output_file_name_style=None, max_rows=None): + """ + Writes representative element data to a `.csv` file with one column and rows of representative elements. + Optionally, write many `.csv` files with at `most max_rows` rows per file. + Files are named output_file_file_name_style_filenumber.csv. + The default output_file_name_style=f"Pi_Min_{n}". + """ + # TODO: Future, support writing different types of data such as polyhedra data. + if output_file_name_style is None: + file_name_base = "Pi_Min_{}".format(self._n) + else: + file_name_base =output_file_name_style + if max_rows is not None: + assert(max_rows >= 1) + num_files = len(self._data)//max_rows + 1 + file_name_base = file_name_base + "_part_0" + if max_rows is None: + max_rows = len(self._data) + num_files = 1 + output_file = file_name_base +".csv" + for file_number in range(num_files): + out_file = open(output_file, "w") + data_writer = csv.writer(out_file, csv.QUOTE_NONE) + for row in range(max_rows): + try: + data_writer.writerow(self._data[max_rows * file_number + row]) + except IndexError: + break + out_file.close() + output_file = file_name_base[:-1]+"{}".format(file_number+1)+".csv" \ No newline at end of file From 897de15eeb309f9cfd2bdc4f05743cf7e42120c2 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Tue, 24 Mar 2026 13:34:47 -0700 Subject: [PATCH 49/64] remove minimal_function_cache.sage --- .../igp/minimal_function_cache.sage | 735 ------------------ 1 file changed, 735 deletions(-) delete mode 100644 cutgeneratingfunctionology/igp/minimal_function_cache.sage diff --git a/cutgeneratingfunctionology/igp/minimal_function_cache.sage b/cutgeneratingfunctionology/igp/minimal_function_cache.sage deleted file mode 100644 index 93893f1b5..000000000 --- a/cutgeneratingfunctionology/igp/minimal_function_cache.sage +++ /dev/null @@ -1,735 +0,0 @@ -from copy import deepcopy -from itertools import pairwise -from cutgeneratingfunctionology.igp import * -import csv -import os -from cutgeneratingfunctionology.spam.basic_semialgebraic import EmptyBSA -from cutgeneratingfunctionology.spam.basic_semialgebraic_pplite import BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron -import logging - -minimal_function_cache_logger = logging.getLogger("cutgeneratingfunctionology.igp.minimal_function_cache") -minimal_function_cache_logger.setLevel(logging.INFO) - -### Note to future reader: ### -### bkpt is assumed to be a breakpoint sequence of length n>= 2. -### Breakpoint sequence are sorted lists of real numbers in [0,1). -### A breakpoint sequences should always have 0 as an element. -### This is never strictly enforced in this file and it is assumed that -### the user is always provided a breakpoint sequence. - -# global defaults for logging from portions of igp; change to log different parts of igp. -log_paramateric_real_field = False -log_pw_functions = False - - -class RepElemGenFailure(Exception): - pass - - -def nnc_poly_from_bkpt_sequence(bkpt, backend=None): - r""" - Defines an NNC polyhedron P such that for all b in P the delta complex of b is isomoprhic to the delta complex of bkpt. - - INPUT: - - ``bkpt`` - sorted list of length 2 or more of sage type in the interval [0,1). - - ```backend`` - ``None``, ``str(pplite)`` - - OUTPUT: class::``BasicSemialgebraicSet_veronese`` - - EXAMPLES:: - sage: from cutgeneratingfunctionology.igp import * - sage: logging.disable(logging.INFO) # suppress logging for tests - sage: nnc_poly_from_bkpt_sequence([0, 4/5]) - BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(Constraint_System {x0==0, -x1+1>0, 2*x1-1>0}, names=[x0, x1]), polynomial_map=[lambda0, lambda1]) - """ - n = len(bkpt) - # assert(n >= 2) - coord_names = [] - bkpt_vals = bkpt - vals = bkpt_vals[0:n] - bkpt_extd = list(bkpt)+[1] - if not log_paramateric_real_field: - parametric_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.parametric").getEffectiveLevel() - logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(logging.ERROR) - for i in range(0,n): - coord_names.append('lambda'+str(i)) - K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, default_backend=backend) - K.gens()[0] == 0 - for i in range(n-1): - K.gens()[i] < K.gens()[i+1] - K.gens()[n-1] < 1 - for i in range(n): - for j in range(n): - if bkpt[i]+bkpt[j]>= 1: - w = 1 - else: - w = 0 - for k in range(n): - if bkpt_extd[k] < bkpt[i]+bkpt[j] - w and bkpt[i]+bkpt[j] - w < bkpt_extd[k+1]: - if k != n-1: - K.gens()[k] < K.gens()[i] + K.gens()[j] - w - K.gens()[i] + K.gens()[j] - w < K.gens()[k+1] - else: - K.gens()[k] < K.gens()[i] + K.gens()[j] - w - K.gens()[i] + K.gens()[j] - w < 1 - elif bkpt_extd[k] == bkpt[i]+bkpt[j] - w: - if k != n-1: - K.gens()[k] == K.gens()[i] + K.gens()[j] - w - K.gens()[i] + K.gens()[j] - w < K.gens()[k+1] - else: - K.gens()[k] == K.gens()[i] + K.gens()[j] - w - K.gens()[i] + K.gens()[j] - w < 1 - if not log_paramateric_real_field: - logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(parametric_logging_level) - return K._bsa - - -def add_breakpoints_and_find_equiv_classes(bkpt_poly): - """ - Takes dim k-1 breakpoint NNC polyhedron (as a :class:`BasicSemialgebraicSet_base`), adds a dimension, - and finds all possible representive elements for equivlance classes of polyhedral complexes. - - INPUT: :class:`BasicSemialgebraicSet_Polyhedral` - - OUTPUT: unique list of breakpoint sequnes of lenght k (as tuples). - - EXAMPLES:: - - sage: from cutgeneratingfunctionology.igp import * - sage: logging.disable(logging.INFO) # suppress logging for tests - sage: add_breakpoints_and_find_equiv_classes(nnc_poly_from_bkpt_sequence([0,4/5]).upstairs()) - [(0, 9/14, 83/112), (0, 13/20, 33/40), (0, 9/14, 101/112), (0, 7/10, 17/20)] - - """ - # BSAs are highly mutable, work only with copies. - B_cap_N_b = copy(bkpt_poly) - B_cap_N_b.add_space_dimensions_and_embed(1) - # get new number of breakpoints. - k = B_cap_N_b.ambient_dim() - if k< 1: - raise ValueError("bkpt_poly should have space dim at least 1.") - model_bound_bkpts = [0]*k - model_bound_bkpts[k-1] = 1 - # 0 < lambda_k <1 - # Using ppl and pplite type bsas have the same signature, so this should work regardless of backend. - # this will fail if the BSA attached does not have these methods. - B_cap_N_b.add_linear_constraint(model_bound_bkpts, -1, operator.lt) # model bounds - B_cap_N_b.add_linear_constraint(model_bound_bkpts, 0, operator.gt) # model bounds - bkpt_order = [0]*k - bkpt_order[k-2] = 1 - bkpt_order[k-1] = -1 - B_cap_N_b.add_linear_constraint(bkpt_order, 0, operator.lt) # order on bkpts - rep_elems = [] - for j in range(k-1): - for i in range(k): - for interval_w in [0,1]: - for line_w in [0,1]: - # which interval is (lambda_k,lambda_k) located in? - # modeled lambda_i op 2lambda_k - w < lambda_{i+1} - for interval_op in [operator.lt, operator.eq, operator.gt]: - for line_op in [operator.lt, operator.eq, operator.gt]: - # highly mutable objects, operate on the copy. - B_cap_N_b_copy = copy(B_cap_N_b) - lhs_i = [0]*k - lhs_i[k-1] = -2 - lhs_i[i] = 1 - B_cap_N_b_copy.add_linear_constraint(lhs_i, interval_w, interval_op) - lhs_i_plus_1 = [0]*k - lhs_i_plus_1[k-1] = -2 - if i < k-1: - lhs_i_plus_1[i+1] = 1 - B_cap_N_b_copy.add_linear_constraint(lhs_i_plus_1, interval_w, operator.gt) - else: - B_cap_N_b_copy.add_linear_constraint(lhs_i_plus_1, interval_w + 1, operator.gt) - if not B_cap_N_b_copy.is_empty(): - # does the line x+y equiv lambda_k mod 1 lie on/above/below (lambda_j,lambda_j)? - # modeled by 2lambda_j op lambda_k + w - lhs_j = [0]*k - lhs_j[j] = 2 - lhs_j[k-1] = -1 - B_cap_N_b_copy.add_linear_constraint(lhs_j, -line_w, line_op) - try: - rep_elem = B_cap_N_b_copy.find_point() - rep_elems.append(tuple(rep_elem)) - except EmptyBSA: - pass - return unique_list(rep_elems) - - -def make_rep_bkpts_with_len_n(n, k=1, bkpts=None, backend=None): - r""" - Produce representative elements of every isomorphism class of breakpoints complexes for breakpoint sequences of length n. - - INPUT: - - n, integer, maximum length of breakpoint sequence. - - k, assumed length of breakpoint sequences in ``bkpts`` - - bkpts, list of breakpoint sequenes (sorted , length of every element, bkpts, an iterable of breakpoints all of length k. - - OUTPUT: A list of representative elements of every isomorphism class of breakpoints complexes for breakpoint sequences of length n extrapolated from bkpts. - - EXAMPLES:: - - sage: from cutgeneratingfunctionology.igp import * - sage: logging.disable(logging.INFO) # suppress logging for tests - sage: make_rep_bkpts_with_len_n(2) - [(0, 1/2), (0, 13/18), (0, 5/18)] - - The number of representative elements grows quickly:: - - sage: bkpts_rep_with_len_3 = make_rep_bkpts_with_len_n(3) - sage: len(bkpts_rep_with_len_3) - 34 - - Previous computations can be reused:: - - sage: bkpts_rep_with_len_4 = make_rep_bkpts_with_len_n(4, 3, bkpts_rep_with_len_3) - sage: len(bkpts_rep_with_len_4) - 329 - """ - # Matthias suggested looking at a directed tree. - # An alternative approach would be to look into using a (graded) lattice as a data strcture. - # We have bkpt \leq bkpt' if and only if dim(NNC(bkpt)) \leq dim(NNC(bkpt')) - # and embed(NNC(bkpt), dim(NNC(bkpt')) \leq NNC(bkpt') in the poset of NNC polyhedra. - # This might speed up/less the load of verifying unquiness of cells which is the time bounding task here. - new_bkpts = [] - if n < 2: - raise ValueError("n>=2") - if k == n: - raise ValueError("k= 2) - assert(f_index >= 1) - assert(f_index <= n - 1) - if not isinstance(bkpt, list): - bkpt = list(bkpt) - coord_names = [] - val = [None]*(n) - for i in range(n): - coord_names.append('gamma'+str(i)) - K = ParametricRealField(names=coord_names, values = val, mutable_values=True, big_cells=True, allow_refinement=False, default_backend=backend) - K.gens()[0] == 0 - for i in range(1, n): - K.gens()[i] <=1 - K.gens()[i] > 0 - h = piecewise_function_from_breakpoints_and_values(bkpt + [1], K.gens() + [0], merge=False) - # Assumes minimality for the partially defined function. - for vert in generate_type_1_vertices_continuous(h, operator.ge, bkpt + [1]): - vert - for vert in generate_type_2_vertices_continuous(h, operator.ge, bkpt + [1]): - vert - for vert in generate_assumed_symmetric_vertices_continuous(h, bkpt[f_index], bkpt + [1]): - vert - if not log_paramateric_real_field: - logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(parametric_logging_level) - if not log_pw_functions: - logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) - return K._bsa - -def value_nnc_polyhedron(bkpt, f_index, backend=None): - """ - For a given breakpoints seqeunce and f index, write the value polyhedron as a basic semialgebraic set in the full space of parameters. - - INPUTS: - - ``bkpt`` - sorted list of length 2 or more of sage type in the interval [0,1). - - ``f_index`` - integer between 1 and length of ``len(bkpt) -1``. - - ``backend`` - ``None``, ``str(pplite)`` - - OUTPUT: - - class::``BasicSemialgebraicSet_veronese`` - - EXAMPLES:: - - sage: from cutgeneratingfunctionology.igp import * - sage: logging.disable(logging.INFO) # suppress logging for tests - sage: value_nnc_polyhedron([0,4/5], 1) # gmic with f=4/5, a trivial value polyhedron - BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(Constraint_System {x3-1==0, x2==0, 5*x1-4==0, x0==0}, names=[x0, x1, x2, x3]), polynomial_map=[lambda0, lambda1, gamma0, gamma1]) - sage: P = value_nnc_polyhedron([0,2/15,2/3, 4/5], 3) # A non trivial value polyhedron. - sage: P.upstairs()._polyhedron - A 1-dimensional polyhedron in QQ^8 defined as the convex hull of 2 points - """ - n = len(bkpt) - if not log_paramateric_real_field: - parametric_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.parametric").getEffectiveLevel() - logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(logging.ERROR) - if not log_pw_functions: - pw_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.functions").getEffectiveLevel() - logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(logging.ERROR) - assert(n >= 2) - assert(f_index >= 1) - assert(f_index <= n) - coord_names = [] - bkpt_vals = list(bkpt) - vals = bkpt_vals + [None]*(n) - for i in range(n): - coord_names.append('lambda'+str(i)) - for i in range(n): - coord_names.append('gamma'+str(i)) - K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, allow_refinement=False, default_backend=backend) - # breakpoint parameters are the mesured breakpoint values. - for i in range(n): - K.gens()[i] == bkpt[i] - # necessary conditions on value parameters - K.gens()[n] == 0 - for i in range(1, n): - K.gens()[i+n] <=1 - K.gens()[i+n] > 0 - h = piecewise_function_from_breakpoints_and_values(K.gens()[0:n] + [1], K.gens()[n:2*n] + [0], merge=False) - # Assumes minimality for the partially defined function. - for vert in generate_type_1_vertices_continuous(h, operator.ge, K.gens()[0:n] + [1]): - vert - for vert in generate_type_2_vertices_continuous(h, operator.ge, K.gens()[0:n] + [1]): - vert - for vert in generate_assumed_symmetric_vertices_continuous(h, K.gens()[f_index], [0] + K.gens()[0:n] + [1]): - vert - if not log_paramateric_real_field: - logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(parametric_logging_level) - if not log_pw_functions: - logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) - return K._bsa - - -def bsa_of_rep_element(bkpt, vals, backend=None): - """ - Given pi_(bkpt, vals) is {minimal, not minimal}, find BSA subset of R^(2n) such that (bkpt, vals) in BSA and for all p - in BSA, pi_p is {minimal, not minimal}. - - INPUT: - - ``bkpt`` - a breakpoint seqeunce - - ``vals`` - list like of sage numerical types corrosponding values for the breakpoint sequence. - - ``backend`` - None, ``str(pplite)`` - - OUTPUT: A basic semialgebraic set. - - EXAMPLES:: - - - """ - n = len(bkpt) - if not log_paramateric_real_field: - parametric_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.parametric").getEffectiveLevel() - logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(logging.ERROR) - if not log_pw_functions: - pw_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.functions").getEffectiveLevel() - logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(logging.ERROR) - assert(n>=2) - coord_names = [] - for i in range(n): - coord_names.append('lambda'+str(i)) - for i in range(n): - coord_names.append('gamma'+str(i)) - K = ParametricRealField(names=coord_names, values = bkpt+vals, big_cells=True, default_backend=backend) - h = piecewise_function_from_breakpoints_and_values(K.gens()[0:n] + [1], K.gens()[n:2*n] + [0], merge=False) - minimality_test(h) - if not log_paramateric_real_field: - logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(parametric_logging_level) - if not log_pw_functions: - logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) - return K.make_proof_cell().bsa - - -def find_minimal_function_reps_from_bkpts(bkpts, prove_minimality=True, backend=None): - """ - Finds representative elements of minimal functions from a given breakpoint sequence. - - INPUT: - - ``bkpts`` - an interable of breakpoint seqeunces. - - ``prove_minimality`` - bool, proves minimality of paramaterized function - - ``backend`` - None, ``str(pplite)`` - - OUTPUT: - - List of tuples of lists - - EXAMPLES:: - - sage: from cutgeneratingfunctionology.igp import * - sage: logging.disable(logging.INFO) # suppress logging for tests - sage: bkpts = make_rep_bkpts_with_len_n(2) - sage: find_minimal_function_reps_from_bkpts(bkpts) - [([0, 1/2], [0, 1]), ([0, 13/18], [0, 1]), ([0, 5/18], [0, 1])] - - """ - rep_elems = [] - if not log_pw_functions: - pw_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.functions").getEffectiveLevel() - logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(logging.ERROR) - for bkpt in bkpts: - n = len(bkpt) - for f_index in range(1, n): - poly_bsa = value_nnc_polyhedron_value_cords(list(bkpt), f_index, backend) - gammas = poly_bsa.polynomial_map()[0].parent().gens() - try: - test_point = poly_bsa.upstairs().find_point() - except EmptyBSA: - raise RepElemGenFailure("The value polyhedron {} is empty. This should not be empty. Double check inputs".format(poly_bsa)) - test_val = [] - for gamma_i in gammas: - test_val.append(test_point[poly_bsa.v_dict()[gamma_i]]) - if prove_minimality: - h = piecewise_function_from_breakpoints_and_values(list(bkpt)+[1], test_val+[0]) - if not minimality_test(h): # The following error should never be raised when this function is used as intended. - raise ValueError(f"({bkpt}, {test_val}) paramaterized by breakpoints and values is not a minimal function but assuming a breakpoint sequence is input, this should be minimal.") - rep_elems.append((list(bkpt), test_val)) - if not log_pw_functions: - logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) - return rep_elems - - -class BreakpointComplexClassContainer: - """ - A container for the family of breakpoint complexes for peicewise linear functions - with at most n breakpoints. - - The container assumes that loaded data is correct and performs no checking - that the loaded data represnts the full space. - - This class contains ways to read/write data for use with minimal function generation. - - EXAMPLES:: - - sage: from cutgeneratingfunctionology.igp import * - sage: logging.disable(logging.INFO) # suppress logging for tests - sage: bkpt_of_len_2 = BreakpointComplexClassContainer(2) - sage: bkpt_of_len_2.num_rep_elems() - 3 - sage: [elem for elem in bkpt_of_len_2.get_rep_elems()] - [(0, 1/2), (0, 13/18), (0, 5/18)] - sage: make_rep_bkpts_with_len_n(2) - [(0, 1/2), (0, 13/18), (0, 5/18)] - - A cell descrption of semialgebraic sets can be accessed:: - - sage: all([isinstance(cell, BasicSemialgebraicSet_base) for cell in bkpt_of_len_2.get_nnc_poly_from_bkpt()]) - True - - - """ - def __init__(self, n, **kwrds): - self._n = n - assert(self._n >= 2) - if "backend" in kwrds.keys(): - self._backend = kwrds["backend"] - else: - self._backend = None - if "load_rep_elem_data" in kwrds.keys(): - if kwrds[load_rep_elem_data] is None: - minimal_function_cache_logger.info("Generating representative elements. This might take a while.") - self._data = make_rep_bkpts_with_len_n(self._n, backend = self._backend) - else: - file_names = kwrds["load_bkpt_data"].split(",") - self._data = [] - for file_name in file_names: - file = open(file_name, "r") - self._data += [eval(preparse(data)) for data in list(csv.reader(file))] - file.close() - if "gen_elems_from_data" in kwrds.keys(): - if kwrds[gen_elems_from_data] == True: - k = len(self._data[0]) - if k < n: - self._data = make_rep_bkpts_with_len_n(n, k, self._data) - else: - minimal_function_cache_logger.info("Generating representative elements. This might take a while.") - self._data = make_rep_bkpts_with_len_n(self._n, backend=self._backend) - - def __repr__(self): - return f"Container for the space breakpoint sequences of length {self._n} under equivlance of polyhedral complexes." - - def get_rep_elems(self): - for bkpt in self._data: - yield bkpt - - def get_nnc_poly_from_bkpt(self): - for bkpt in self._data: - yield nnc_poly_from_bkpt_sequence(bkpt, backend=self._backend) - - def num_rep_elems(self): - return len(self._data) - - # def add_one_bkpt_to_all(self): - # minimal_function_cache_logger.info("Generating representative elements. This might take a while.") - # self._n = n+1 - # self._data = make_bkpts_with_len_n(self._n, self._n-1, self._data) - - def covers_space(self): - ### TODO: This method should prove that container is correct. - raise NotImplementedError - - def refine_space(self): - """Ensures that repersentative elements are unique and contained in a single cell. """ - raise NotImplementedError - ### TODO: Test this. I think I need to write a containment method for the BSA - ### or pick the up the underlying polyhedron in the BSA. - self._data = unique_list(self._data) - cells_found = [] - for bkpt in self._data: - bkpt_contained_in_cell = False - for found_cell in cells_found: - if found_cell.contains(bkpt): - bkpt_contained_in_cell = True - break - if not contained_in_cell: - cells_found.append(nnc_poly_from_bkpt_sequence(bkpt, backend=self._backend)) - new_data = [] - for cell in cells_found: - new_data.append(cell.find_point()) - self._data = new_data - - def write_data(self, output_file_name_style=None, max_rows=None): - """ - Writes representative element data to a `.csv` file with one column and rows of representative elements. - Optionally, write many `.csv` files with at `most max_rows` rows per file. - Files are named output_file_file_name_style_filenumber.csv. - The default output_file_name_style="bkpts_of_len_n". - """ - # TODO: Future, support writing different types of data such as polyhedra data. - if output_file_name_style is None: - file_name_base = "bkpts_of_len_{}".format(self._n) - else: - file_name_base =output_file_name_style - if max_rows is not None: - assert(max_rows >= 1) - num_files = len(self._data)//max_rows + 1 - file_name_base = file_name_base + "_part_0" - if max_rows is None: - max_rows = 0 - output_file = file_name_base +".csv" - for file_number in range(num_files): - out_file = open(output_file, "w") - data_writer = csv.writer(out_file, csv.QUOTE_NONE) - for row in range(max_rows): - try: - data_writer.writerow(self._data[max_rows * file_number + row]) - except IndexError: - break - out_file.close() - output_file = file_name_base[:-1]+"{}".format(file_number+1)+".csv" - - -class PiMinContContainer: - """ - A container for the space of continuous piecewise linear minimal functions with at - most n breakpoints paramaterized by breakpoints and values using semialgebraic sets. - - The container assumes that loaded data is correct and performs no checking. - - INPUTS: - - n, an integer - keywords: - - backend, None or str(pplite) - - load_bkpt_data, .csv file(s) of list of tuples of breakpoitns - - load_rep_elem_data, .csv file(s) of list of tuples of representative elements of the space of minimal functions - - EXAMPLES:: - - sage: from cutgeneratingfunctionology.igp import * - sage: logging.disable(logging.INFO) # suppress logging for tests - sage: PiMin_2 = PiMinContContainer(2) - sage: all([minimality_test(pi) for pi in PiMin_2.get_rep_functions()]) - True - sage: PiMin_2 - Space of minimal functions with at most 2 breakpoints parameterized by breakpoints and values using semialgebraic sets. - - A cell descrption of semialgebraic sets can be accessed:: - - sage: all([isinstance(cell, BasicSemialgebraicSet_base) for cell in PiMin_2.get_semialgebraic_sets()]) - True - - Data is stored as repersenative elements. The number of repersentative elements grows quickly. :: - - sage: PiMin_4 = PiMinContContainer(4) # not tested - sage: len([rep_elem for rep_elem in PiMin_4.get_rep_elems()]) # not tested - 987 - sage: len([rep_elem for rep_elem in PiMin_2.get_rep_elems()]) - 3 - - The container provides methods of writing data.:: - - sage: PiMin_2.write_data() # not tested - - Written data can be reused :: - - sage: PiMin_2_loaded_data = PiMinContContainer(2, manually_load_function_cache=True, load_files="Pi_Min_2.csv", data_type="rep elems") # not tested - sage: len([rep_elem for rep_elem in PiMin_2_loaded_data.get_rep_elems()]) # not tested - 3 - """ - def __init__(self, n, backend=None, manually_load_function_cache=False, **loading_kwrds): - self._n = n - assert(self._n >= 2) - if "backend" in kwrds.keys(): - self._backend = kwrds["backend"] - else: - self._backend = None - if not manually_load_function_cache: - try: - # Load the minimal function cache. - from cacheUtils.utils import cache_loader - except ImportError: - if self._n > 4: - minimal_function_cache_logger.warning(f"This may take a while. Try installing the minimalfunctioncache.") - bkpts = make_rep_bkpts_with_len_n(self._n, backend=self._backend) - minimal_function_cache_logger.info("Finished generating breakpoints.") - minimal_function_cache_logger.info("Computing repersentative elements.") - self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) - try: - self._data = cache_loader(self._n, "rep elems") - except ValueError: - try: - bkpts = cache_loader(n, "breakpoints") - except ValueError: - minimal_function_cache_logger.info(f"The cache for {n} breakpoints has not been generated or could not be found.") - minimal_function_cache_logger.info("Generating representative breakpoints.") - if n> 4: - - - minimal_function_cache_logger.info("PiMin container, Reportin' for duty.") - elif manually_load_function_cache: - # this is for generating - try: - if loading_kwrds.keys("breakpoints_or_rep_elems").strip(" ").lower() == "breakpoints": - bkpts = [] - with open(loading_kwrds.keys("path_to_file_or_file_name_in_cwd")) as csvfile: - file_reader = csv.reader(csvfile) - for row in file_reader: - bkpts.append([QQ(data) for data in row]) - self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) - except OSError: - with open(loading_kwrds.keys("path_to_file_or_file_name_in_cwd")) as csvfile: - file_reader = csv.reader(csvfile) - for row in file_reader: - bkpts.append([QQ(data) for data in row]) - self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) - if loading_kwrds.keys("breakpoints_or_rep_elems").strip(" ").lower() == "rep_elems": - self._data = [] - with open(loading_kwrds.keys("path_to_file_or_file_name_in_cwd")) as csvfile: - file_reader = csv.reader(csvfile) - for row in file_reader: - self._data.append([QQ(data) for data in row]) - except KeyError: - raise ValueError("No valid inputs provded. Maybe check your spelling?") - - else: - raise ValueError("manually_load_function_cache is a python bool") - - - def __repr__(self): - return "Space of minimal functions with at most {} breakpoints parameterized by breakpoints and values using semialgebraic sets.".format(self._n) - - def get_semialgebraic_sets(self): - """Iterator for semialgebraic set description""" - for b, v in self._data: - yield bsa_of_rep_element(list(b), list(v), backend=self._backend) - - def get_rep_elems(self): - """Iterator for representative elements.""" - for b, v in self._data: - yield (list(b), list(v)) - - def get_rep_functions(self): - """Iterator for representative functinos.""" - for b, v in self._data: - yield piecewise_function_from_breakpoints_and_values(list(b)+[1], list(v)+[0]) - - def n(self): - """The maximum number of proper breakpoints of paramaterized functions in the space.""" - return self._n - - def covers_space(self): - ### TODO: This method should prove that container is correct. - raise NotImplementedError - - def refine_space(self): - ### TODO: This method should be called when loading multiple file to ensure cells have not been duplicated. - raise NotImplementedError - - def write_data(self, output_file_name_style=None, max_rows=None): - """ - Writes representative element data to a `.csv` file with one column and rows of representative elements. - Optionally, write many `.csv` files with at `most max_rows` rows per file. - Files are named output_file_file_name_style_filenumber.csv. - The default output_file_name_style=f"Pi_Min_{n}". - """ - # TODO: Future, support writing different types of data such as polyhedra data. - if output_file_name_style is None: - file_name_base = "Pi_Min_{}".format(self._n) - else: - file_name_base =output_file_name_style - if max_rows is not None: - assert(max_rows >= 1) - num_files = len(self._data)//max_rows + 1 - file_name_base = file_name_base + "_part_0" - if max_rows is None: - max_rows = len(self._data) - num_files = 1 - output_file = file_name_base +".csv" - for file_number in range(num_files): - out_file = open(output_file, "w") - data_writer = csv.writer(out_file, csv.QUOTE_NONE) - for row in range(max_rows): - try: - data_writer.writerow(self._data[max_rows * file_number + row]) - except IndexError: - break - out_file.close() - output_file = file_name_base[:-1]+"{}".format(file_number+1)+".csv" - - def _load_file_or_dir() \ No newline at end of file From 5cc8513680864abdae569832dd933089ef7488b1 Mon Sep 17 00:00:00 2001 From: ComboProblem <102884863+ComboProblem@users.noreply.github.com> Date: Tue, 24 Mar 2026 13:36:34 -0700 Subject: [PATCH 50/64] remove depericated imports, order imports --- .../igp/minimal_function_cell_description.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cell_description.py b/cutgeneratingfunctionology/igp/minimal_function_cell_description.py index 050035929..ec386211f 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cell_description.py +++ b/cutgeneratingfunctionology/igp/minimal_function_cell_description.py @@ -1,11 +1,9 @@ -from copy import deepcopy -from itertools import pairwise from cutgeneratingfunctionology.igp import * -import csv -import os from cutgeneratingfunctionology.spam.basic_semialgebraic import EmptyBSA from cutgeneratingfunctionology.spam.basic_semialgebraic_pplite import BasicSemialgebraicSet_polyhedral_pplite_NNC_Polyhedron +import csv import logging +import os minimal_funciton_cell_description_logger = logging.getLogger("cutgeneratingfunctionology.igp.minimal_funciton_cell_description") minimal_funciton_cell_description_logger.setLevel(logging.INFO) From e1c5f3df7747d680a5c6ebe9e988cb3164a1f873 Mon Sep 17 00:00:00 2001 From: Acadia Larsen Date: Tue, 31 Mar 2026 09:44:24 -0700 Subject: [PATCH 51/64] cache loading tested --- cutgeneratingfunctionology/igp/__init__.py | 3 +- ...=> minimal_function_cell_description.sage} | 89 ++++++++++--------- 2 files changed, 50 insertions(+), 42 deletions(-) rename cutgeneratingfunctionology/igp/{minimal_function_cell_description.py => minimal_function_cell_description.sage} (90%) diff --git a/cutgeneratingfunctionology/igp/__init__.py b/cutgeneratingfunctionology/igp/__init__.py index a158eeca1..1f2db68c8 100644 --- a/cutgeneratingfunctionology/igp/__init__.py +++ b/cutgeneratingfunctionology/igp/__init__.py @@ -76,10 +76,9 @@ def igp_load(fpath): igp_load(igp_dir + "plot_options.sage") igp_load(igp_dir + "faster_subadditivity_test.sage") igp_load(igp_dir + "faster_subadditivity_test_discontinuous.sage") -igp_load(igp_dir + "minimal_function_cache.sage") +igp_load(igp_dir + "minimal_function_cell_description.sage") from . import extreme_functions, procedures - try: igp_load(igp_dir + "config.sage") except IOError: diff --git a/cutgeneratingfunctionology/igp/minimal_function_cell_description.py b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage similarity index 90% rename from cutgeneratingfunctionology/igp/minimal_function_cell_description.py rename to cutgeneratingfunctionology/igp/minimal_function_cell_description.sage index ec386211f..0ddb83555 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cell_description.py +++ b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage @@ -111,12 +111,18 @@ def add_breakpoints_and_find_equiv_classes(bkpt_poly): # 0 < lambda_k <1 # Using ppl and pplite type bsas have the same signature, so this should work regardless of backend. # this will fail if the BSA attached does not have these methods. - B_cap_N_b.add_linear_constraint(model_bound_bkpts, -1, operator.lt) # model bounds - B_cap_N_b.add_linear_constraint(model_bound_bkpts, 0, operator.gt) # model bounds + # Ask Matthias about this; in some sense; I don't want to have to write QQ for field everytime I need to have a mathemeatically meaningful number; so a .sage makes sense + # but in terms of integeration/repository goals it makes more sense to write this as a .py file as to not create tech debt. + # Also, we should note that since we are writing this pythontically; the QQ for add linear constraint is + # necessary since we mean the field elements as input. + # If this were a .sage file; coheasion would happen automatically. But it isn't; so we should be explict, when elements should be + # converted to sage types. (Sage types == "mathemamatically accurate") + B_cap_N_b.add_linear_constraint(model_bound_bkpts, QQ(-1), operator.lt) # model bounds + B_cap_N_b.add_linear_constraint(model_bound_bkpts, QQ(0), operator.gt) # model bounds bkpt_order = [0]*k bkpt_order[k-2] = 1 bkpt_order[k-1] = -1 - B_cap_N_b.add_linear_constraint(bkpt_order, 0, operator.lt) # order on bkpts + B_cap_N_b.add_linear_constraint(bkpt_order, QQ(0), operator.lt) # order on bkpts rep_elems = [] for j in range(k-1): for i in range(k): @@ -131,21 +137,21 @@ def add_breakpoints_and_find_equiv_classes(bkpt_poly): lhs_i = [0]*k lhs_i[k-1] = -2 lhs_i[i] = 1 - B_cap_N_b_copy.add_linear_constraint(lhs_i, interval_w, interval_op) + B_cap_N_b_copy.add_linear_constraint(lhs_i, QQ(interval_w), interval_op) lhs_i_plus_1 = [0]*k lhs_i_plus_1[k-1] = -2 if i < k-1: lhs_i_plus_1[i+1] = 1 - B_cap_N_b_copy.add_linear_constraint(lhs_i_plus_1, interval_w, operator.gt) + B_cap_N_b_copy.add_linear_constraint(lhs_i_plus_1, QQ(interval_w), operator.gt) else: - B_cap_N_b_copy.add_linear_constraint(lhs_i_plus_1, interval_w + 1, operator.gt) + B_cap_N_b_copy.add_linear_constraint(lhs_i_plus_1, QQ(interval_w + 1), operator.gt) if not B_cap_N_b_copy.is_empty(): # does the line x+y equiv lambda_k mod 1 lie on/above/below (lambda_j,lambda_j)? # modeled by 2lambda_j op lambda_k + w lhs_j = [0]*k lhs_j[j] = 2 lhs_j[k-1] = -1 - B_cap_N_b_copy.add_linear_constraint(lhs_j, -line_w, line_op) + B_cap_N_b_copy.add_linear_constraint(lhs_j, QQ(-line_w), line_op) try: rep_elem = B_cap_N_b_copy.find_point() rep_elems.append(tuple(rep_elem)) @@ -197,7 +203,7 @@ def make_rep_bkpts_with_len_n(n, k=1, bkpts=None, backend=None): if k == 1 and bkpts is None: bkpts=[[0]] for bkpt in bkpts: - new_bkpts += add_breakpoints_and_find_equiv_classes(nnc_poly_from_bkpt_sequence(bkpt).upstairs()) + new_bkpts += add_breakpoints_and_find_equiv_classes(nnc_poly_from_bkpt_sequence(bkpt, backend=backend).upstairs()) new_bkpts = unique_list(new_bkpts) k += 1 @@ -469,23 +475,28 @@ def __init__(self, n, backend=None, manually_load_breakpoint_cache=False, **load if not manually_load_breakpoint_cache: try: # Load the minimal function cache. - from cacheUtils.utils import cache_loader, cache_info - except ImportError: - if self._n > 4: - minimal_funciton_cell_description_logger.warning(f"This may take a while. Try installing the minimalfunctioncache.") - self._data = make_rep_bkpts_with_len_n(self._n, backend=self._backend) - try: - self._data = cache_loader(self._n, "breakpoints") # cache loader throws a value error if a cache for n is not found. + from minimalFunctionCache.utils import minimal_function_cache_info, minimal_function_cache_loader + cache_info = minimal_function_cache_info() + try: + self._data = minimal_function_cache_loader(self._n, "breakpoints") # minimal_function_cache_loader throws a value error if a cache for n is not found. except ValueError: minimal_funciton_cell_description_logger.info(f"The cache for {n} breakpoints has not been generated or could not be found.") minimal_funciton_cell_description_logger.info("Generating representative breakpoints.") - k = max([ i for i in cache_info["avail_bkpts"] if i < self._n]) - if k is None: - raise ValueError - prev_gen_bkpts = self._data = cache_loader(self._n, "breakpoints") - if self._n > 4: - minimal_funciton_cell_description_logger.warning(f"This may take a while.") - self._data = make_rep_bkpts_with_len_n(self._n, k, prev_gen_bkpts, backend=self._backend) + avail_bkpts = [ i for i in cache_info["avail_bkpts"] if i < self._n] + if len(avail_bkpts) > 0: + k = max(avail_bkpts) + prev_gen_bkpts = self._data = minimal_function_cache_loader(k, "breakpoints") + if self._n > 4: + minimal_funciton_cell_description_logger.warning(f"This may take a while.") + self._data = make_rep_bkpts_with_len_n(self._n, k, prev_gen_bkpts, backend=self._backend) + else: + if self._n > 4: + minimal_funciton_cell_description_logger.warning(f"This may take a while.") + self._data = make_rep_bkpts_with_len_n(self._n, backend=self._backend) + except ImportError: + if self._n > 4: + minimal_funciton_cell_description_logger.warning(f"This may take a while. Try installing the minimalfunctioncache.") + self._data = make_rep_bkpts_with_len_n(self._n, backend=self._backend) minimal_funciton_cell_description_logger.info("Finished generating breakpoints.") elif manually_load_breakpoint_cache: # this is for generating the cache and advanced use. @@ -496,7 +507,7 @@ def __init__(self, n, backend=None, manually_load_breakpoint_cache=False, **load file_reader = csv.reader(csvfile) for row in file_reader: bkpts.append([QQ(data) for data in row]) - self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) + self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) except KeyError: if loading_kwrds.keys("breakpoints_or_rep_elems").strip(" ").lower() == "rep elems": self._data = [] @@ -639,7 +650,19 @@ def __init__(self, n, backend=None, manually_load_function_cache=False, **loadin if not manually_load_function_cache: try: # Load the minimal function cache. - from cacheUtils.utils import cache_loader + from minimalFunctionCache.utils import minimal_function_cache_loader + try: + self._data = minimal_function_cache_loader(self._n, "rep elems") + # cache loader throws a value error if a cache for n is not found. + except ValueError: + minimal_funciton_cell_description_logger.info(f"The cache for {n} breakpoints has not been generated or could not be found.") + minimal_funciton_cell_description_logger.info("Finding or generating representative breakpoints.") + bkpts = BreakpointComplexClassContainer(self._n, backend=self._backend).get_rep_elems() + if self._n > 4: + minimal_funciton_cell_description_logger.warning(f"This may take a while.") + minimal_funciton_cell_description_logger.info("Finished generating breakpoints.") + minimal_funciton_cell_description_logger.info("Computing repersentative elements.") + self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) except ImportError: if self._n > 4: minimal_funciton_cell_description_logger.warning(f"This may take a while. Try installing the minimalfunctioncache.") @@ -647,20 +670,6 @@ def __init__(self, n, backend=None, manually_load_function_cache=False, **loadin minimal_funciton_cell_description_logger.info("Finished generating breakpoints.") minimal_funciton_cell_description_logger.info("Computing repersentative elements.") self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) - try: - self._data = cache_loader(self._n, "rep elems") # cache loader throws a value error if a cache for n is not found. - except ValueError: - try: - bkpts = (n, "breakpoints") - except ValueError: - minimal_funciton_cell_description_logger.info(f"The cache for {n} breakpoints has not been generated or could not be found.") - minimal_funciton_cell_description_logger.info("Generating representative breakpoints.") - if self._n > 4: - minimal_funciton_cell_description_logger.warning(f"This may take a while.") - bkpts = BreakpointComplexClassContainer(self._n, backend=self._backend) - minimal_funciton_cell_description_logger.info("Finished generating breakpoints.") - minimal_funciton_cell_description_logger.info("Computing repersentative elements.") - self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) minimal_funciton_cell_description_logger.info("PiMin container, Reportin' for duty.") elif manually_load_function_cache: # this is for generating the cache and advanced use. @@ -671,7 +680,7 @@ def __init__(self, n, backend=None, manually_load_function_cache=False, **loadin file_reader = csv.reader(csvfile) for row in file_reader: bkpts.append([QQ(data) for data in row]) - self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) + self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) except KeyError: if loading_kwrds.keys("breakpoints_or_rep_elems").strip(" ").lower() == "rep elems": self._data = [] @@ -745,4 +754,4 @@ def write_data(self, output_file_name_style=None, max_rows=None): except IndexError: break out_file.close() - output_file = file_name_base[:-1]+"{}".format(file_number+1)+".csv" \ No newline at end of file + output_file = file_name_base[:-1]+"{}".format(file_number+1)+".csv" From 45c90ea6719ecd2c9b97e7b73c28a08fd340e4b9 Mon Sep 17 00:00:00 2001 From: Acadia Larsen Date: Tue, 31 Mar 2026 12:17:43 -0700 Subject: [PATCH 52/64] make pplite signature consistent with ppl --- cutgeneratingfunctionology/spam/basic_semialgebraic_pplite.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cutgeneratingfunctionology/spam/basic_semialgebraic_pplite.py b/cutgeneratingfunctionology/spam/basic_semialgebraic_pplite.py index 62c25531a..559be7403 100644 --- a/cutgeneratingfunctionology/spam/basic_semialgebraic_pplite.py +++ b/cutgeneratingfunctionology/spam/basic_semialgebraic_pplite.py @@ -205,6 +205,8 @@ def to_vector(g, ambient_dim): if g.is_point() or g.is_closure_point()] rays = [to_vector(g, self._polyhedron.space_dimension()) for g in self._polyhedron.generators() if g.is_ray()] + if self.is_empty(): + raise EmptyBSA("The underlying bsa is empty set.") if points: p = sum(points) / len(points) if rays: @@ -362,4 +364,4 @@ def is_universe(self): False """ self._polyhedron.minimize() - return self._polyhedron.is_universe() \ No newline at end of file + return self._polyhedron.is_universe() From a4c102758dd8076e84d5684f9b015ffaae6f7ced Mon Sep 17 00:00:00 2001 From: Acadia Larsen Date: Tue, 31 Mar 2026 12:45:38 -0700 Subject: [PATCH 53/64] fixes in writing files --- .../igp/minimal_function_cell_description.sage | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage index 0ddb83555..03ddb24fe 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage @@ -582,7 +582,8 @@ class BreakpointComplexClassContainer: num_files = len(self._data)//max_rows + 1 file_name_base = file_name_base + "_part_0" if max_rows is None: - max_rows = 0 + max_rows = len(self._data) + num_files = 1 output_file = file_name_base +".csv" for file_number in range(num_files): out_file = open(output_file, "w") @@ -674,17 +675,17 @@ class PiMinContContainer: elif manually_load_function_cache: # this is for generating the cache and advanced use. try: - if loading_kwrds.keys("breakpoints_or_rep_elems").strip(" ").lower() == "breakpoints": + if loading_kwrds["breakpoints_or_rep_elems"].strip(" ").lower() == "breakpoints": bkpts = [] - with open(loading_kwrds.keys("path_to_file_or_file_name_in_cwd")) as csvfile: + with open(loading_kwrds["path_to_file_or_file_name_in_cwd"]) as csvfile: file_reader = csv.reader(csvfile) for row in file_reader: bkpts.append([QQ(data) for data in row]) self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) except KeyError: - if loading_kwrds.keys("breakpoints_or_rep_elems").strip(" ").lower() == "rep elems": + if loading_kwrds["breakpoints_or_rep_elems"].strip(" ").lower() == "rep elems": self._data = [] - with open(loading_kwrds.keys("path_to_file_or_file_name_in_cwd")) as csvfile: + with open(loading_kwrds["path_to_file_or_file_name_in_cwd"]) as csvfile: file_reader = csv.reader(csvfile) for row in file_reader: self._data.append([QQ(data) for data in row]) From d8c43a5f7f31e85f3276240fd2267d02aa6cdd11 Mon Sep 17 00:00:00 2001 From: Acadia Larsen Date: Thu, 2 Apr 2026 16:27:34 -0700 Subject: [PATCH 54/64] finish fixing loading --- .../minimal_function_cell_description.sage | 31 ++++++------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage index 03ddb24fe..1c41694c4 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage @@ -109,14 +109,7 @@ def add_breakpoints_and_find_equiv_classes(bkpt_poly): model_bound_bkpts = [0]*k model_bound_bkpts[k-1] = 1 # 0 < lambda_k <1 - # Using ppl and pplite type bsas have the same signature, so this should work regardless of backend. - # this will fail if the BSA attached does not have these methods. - # Ask Matthias about this; in some sense; I don't want to have to write QQ for field everytime I need to have a mathemeatically meaningful number; so a .sage makes sense - # but in terms of integeration/repository goals it makes more sense to write this as a .py file as to not create tech debt. - # Also, we should note that since we are writing this pythontically; the QQ for add linear constraint is - # necessary since we mean the field elements as input. - # If this were a .sage file; coheasion would happen automatically. But it isn't; so we should be explict, when elements should be - # converted to sage types. (Sage types == "mathemamatically accurate") + # we assume the bsa is "polyhedra" B_cap_N_b.add_linear_constraint(model_bound_bkpts, QQ(-1), operator.lt) # model bounds B_cap_N_b.add_linear_constraint(model_bound_bkpts, QQ(0), operator.gt) # model bounds bkpt_order = [0]*k @@ -366,8 +359,11 @@ def bsa_of_rep_element(bkpt, vals, backend=None): OUTPUT: A basic semialgebraic set. EXAMPLES:: - - + + sage: from cutgeneratingfunctionology.igp import * + sage: logging.disable(logging.INFO) # suppress logging for tests + sage: bsa_of_rep_element([0,4/5], [0,1]) # bsa for GMIC + BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(Constraint_System {x5-1==0, x4==0, x0==0, 2*x3-1>0, -2*x1+x2-x3+1>=0, -x3+1>0}, names=[x0, x1, x2, x3, x4, x5]), polynomial_map=[gamma0, lambda1^2*gamma0, lambda1*gamma0, lambda1, lambda0, gamma1]) """ n = len(bkpt) if not log_paramateric_real_field: @@ -465,8 +461,6 @@ class BreakpointComplexClassContainer: sage: all([isinstance(cell, BasicSemialgebraicSet_base) for cell in bkpt_of_len_2.get_nnc_poly_from_bkpt()]) True - - """ def __init__(self, n, backend=None, manually_load_breakpoint_cache=False, **loading_kwrds): self._n = n @@ -501,17 +495,17 @@ class BreakpointComplexClassContainer: elif manually_load_breakpoint_cache: # this is for generating the cache and advanced use. try: - if loading_kwrds.keys("breakpoints_or_rep_elems").strip(" ").lower() == "breakpoints": + if loading_kwrds["breakpoints_or_rep_elems"].strip(" ").lower() == "breakpoints": bkpts = [] - with open(loading_kwrds.keys("path_to_file_or_file_name_in_cwd")) as csvfile: + with open(loading_kwrds["path_to_file_or_file_name_in_cwd"]) as csvfile: file_reader = csv.reader(csvfile) for row in file_reader: bkpts.append([QQ(data) for data in row]) self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) except KeyError: - if loading_kwrds.keys("breakpoints_or_rep_elems").strip(" ").lower() == "rep elems": + if loading_kwrds["breakpoints_or_rep_elems"].strip(" ").lower() == "rep elems": self._data = [] - with open(loading_kwrds.keys("path_to_file_or_file_name_in_cwd")) as csvfile: + with open(loading_kwrds["path_to_file_or_file_name_in_cwd"]) as csvfile: file_reader = csv.reader(csvfile) for row in file_reader: self._data.append([QQ(data) for data in row]) @@ -536,11 +530,6 @@ class BreakpointComplexClassContainer: def num_rep_elems(self): return len(self._data) - # def add_one_bkpt_to_all(self): - # minimal_funciton_cell_description_logger.info("Generating representative elements. This might take a while.") - # self._n = n+1 - # self._data = make_bkpts_with_len_n(self._n, self._n-1, self._data) - def covers_space(self): ### TODO: This method should prove that container is correct. raise NotImplementedError From ef628b9e8c84e0a2de71aedc82cebd7c37f613b0 Mon Sep 17 00:00:00 2001 From: Acadia Larsen Date: Mon, 6 Apr 2026 17:38:23 -0700 Subject: [PATCH 55/64] finalize loading styles --- .../minimal_function_cell_description.sage | 189 +++++++++++------- 1 file changed, 121 insertions(+), 68 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage index 1c41694c4..aacfe8bfb 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage @@ -29,7 +29,7 @@ def nnc_poly_from_bkpt_sequence(bkpt, backend=None): Defines an NNC polyhedron P such that for all b in P the delta complex of b is isomoprhic to the delta complex of bkpt. INPUT: - - ``bkpt`` - sorted list of length 2 or more of sage type in the interval [0,1). + - ``bkpt`` - sorted list of length 1 or more of sage type in the interval [0,1). - ```backend`` - ``None``, ``str(pplite)`` OUTPUT: class::``BasicSemialgebraicSet_veronese`` @@ -41,7 +41,6 @@ def nnc_poly_from_bkpt_sequence(bkpt, backend=None): BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(Constraint_System {x0==0, -x1+1>0, 2*x1-1>0}, names=[x0, x1]), polynomial_map=[lambda0, lambda1]) """ n = len(bkpt) - # assert(n >= 2) coord_names = [] bkpt_vals = bkpt vals = bkpt_vals[0:n] @@ -81,9 +80,8 @@ def nnc_poly_from_bkpt_sequence(bkpt, backend=None): logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(parametric_logging_level) return K._bsa - def add_breakpoints_and_find_equiv_classes(bkpt_poly): - """ + r""" Takes dim k-1 breakpoint NNC polyhedron (as a :class:`BasicSemialgebraicSet_base`), adds a dimension, and finds all possible representive elements for equivlance classes of polyhedral complexes. @@ -97,7 +95,6 @@ def add_breakpoints_and_find_equiv_classes(bkpt_poly): sage: logging.disable(logging.INFO) # suppress logging for tests sage: add_breakpoints_and_find_equiv_classes(nnc_poly_from_bkpt_sequence([0,4/5]).upstairs()) [(0, 9/14, 83/112), (0, 13/20, 33/40), (0, 9/14, 101/112), (0, 7/10, 17/20)] - """ # BSAs are highly mutable, work only with copies. B_cap_N_b = copy(bkpt_poly) @@ -109,7 +106,7 @@ def add_breakpoints_and_find_equiv_classes(bkpt_poly): model_bound_bkpts = [0]*k model_bound_bkpts[k-1] = 1 # 0 < lambda_k <1 - # we assume the bsa is "polyhedra" + # we assume the bsa is "polyhedra type" B_cap_N_b.add_linear_constraint(model_bound_bkpts, QQ(-1), operator.lt) # model bounds B_cap_N_b.add_linear_constraint(model_bound_bkpts, QQ(0), operator.gt) # model bounds bkpt_order = [0]*k @@ -152,7 +149,6 @@ def add_breakpoints_and_find_equiv_classes(bkpt_poly): pass return unique_list(rep_elems) - def make_rep_bkpts_with_len_n(n, k=1, bkpts=None, backend=None): r""" Produce representative elements of every isomorphism class of breakpoints complexes for breakpoint sequences of length n. @@ -183,7 +179,7 @@ def make_rep_bkpts_with_len_n(n, k=1, bkpts=None, backend=None): sage: len(bkpts_rep_with_len_4) 329 """ - # Matthias suggested looking at a directed tree. + # Matthias has suggested looking at a directed tree. # An alternative approach would be to look into using a (graded) lattice as a data strcture. # We have bkpt \leq bkpt' if and only if dim(NNC(bkpt)) \leq dim(NNC(bkpt')) # and embed(NNC(bkpt), dim(NNC(bkpt')) \leq NNC(bkpt') in the poset of NNC polyhedra. @@ -199,21 +195,19 @@ def make_rep_bkpts_with_len_n(n, k=1, bkpts=None, backend=None): new_bkpts += add_breakpoints_and_find_equiv_classes(nnc_poly_from_bkpt_sequence(bkpt, backend=backend).upstairs()) new_bkpts = unique_list(new_bkpts) k += 1 - if k == n: - minimal_funciton_cell_description_logger.info(f"Breakpoints of length {n} have been generated. ") + minimal_funciton_cell_description_logger.info(f"Breakpoints of length {n} have been generated.") return new_bkpts else: minimal_funciton_cell_description_logger.info(f"Breakpoints of lenght {k} have been generated. Now generating breakpoints of length {k+1}.") return make_rep_bkpts_with_len_n(n, k, new_bkpts, backend) - def generate_assumed_symmetric_vertices_continuous(fn, f, bkpt): """ Silently assumes the symmetry condition holds for all vertices (x,y) in bkpt's breakpoints complex such that x+y equiv f. - See fun:``generate_symmetric_vertices_continuous``. + See function::``generate_symmetric_vertices_continuous``. """ for i in range(len(bkpt)): x = bkpt[i] @@ -226,9 +220,8 @@ def generate_assumed_symmetric_vertices_continuous(fn, f, bkpt): fn(x) + fn(y) == 1 yield (x, y, 0, 0) - -def value_nnc_polyhedron_value_cords(bkpt, f_index, backend =None): - """ +def value_nnc_polyhedron_value_cords(bkpt, f_index, backend=None): + r""" For a given breakpoints seqeunce and f index, write the value polyhedron as a basic semialgebraic set in only the value parameters. INPUT: @@ -248,6 +241,7 @@ def value_nnc_polyhedron_value_cords(bkpt, f_index, backend =None): """ # this saves a slight amount of overhead when detemrining points for the value polyhedron since the assumed # minimality test does not have to entierly go though parametric real field. + # This is useful in application. n = len(bkpt) if not log_paramateric_real_field: parametric_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.parametric").getEffectiveLevel() @@ -264,7 +258,7 @@ def value_nnc_polyhedron_value_cords(bkpt, f_index, backend =None): val = [None]*(n) for i in range(n): coord_names.append('gamma'+str(i)) - K = ParametricRealField(names=coord_names, values = val, mutable_values=True, big_cells=True, allow_refinement=False, default_backend=backend) + K = ParametricRealField(names=coord_names, values=val, mutable_values=True, big_cells=True, allow_refinement=False, default_backend=backend) K.gens()[0] == 0 for i in range(1, n): K.gens()[i] <=1 @@ -284,7 +278,7 @@ def value_nnc_polyhedron_value_cords(bkpt, f_index, backend =None): return K._bsa def value_nnc_polyhedron(bkpt, f_index, backend=None): - """ + r""" For a given breakpoints seqeunce and f index, write the value polyhedron as a basic semialgebraic set in the full space of parameters. INPUTS: @@ -326,7 +320,7 @@ def value_nnc_polyhedron(bkpt, f_index, backend=None): # breakpoint parameters are the mesured breakpoint values. for i in range(n): K.gens()[i] == bkpt[i] - # necessary conditions on value parameters + # Necessary conditions on value parameters. K.gens()[n] == 0 for i in range(1, n): K.gens()[i+n] <=1 @@ -345,9 +339,8 @@ def value_nnc_polyhedron(bkpt, f_index, backend=None): logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) return K._bsa - def bsa_of_rep_element(bkpt, vals, backend=None): - """ + r""" Given pi_(bkpt, vals) is {minimal, not minimal}, find BSA subset of R^(2n) such that (bkpt, vals) in BSA and for all p in BSA, pi_p is {minimal, not minimal}. @@ -389,7 +382,7 @@ def bsa_of_rep_element(bkpt, vals, backend=None): def find_minimal_function_reps_from_bkpts(bkpts, prove_minimality=True, backend=None): - """ + r""" Finds representative elements of minimal functions from a given breakpoint sequence. INPUT: @@ -434,17 +427,24 @@ def find_minimal_function_reps_from_bkpts(bkpts, prove_minimality=True, backend= logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) return rep_elems - class BreakpointComplexClassContainer: """ A container for the family of breakpoint complexes for peicewise linear functions with at most n breakpoints. - The container assumes that loaded data is correct and performs no checking - that the loaded data represnts the full space. - - This class contains ways to read/write data for use with minimal function generation. - + The container will attempt to load from the minimalFunctionCache module or will generate + data if the minimalFunctionCache is not available. + + Loading files generated from the container's `write_data()` method is supported. + When manually loading data, it is assume that all loaded data is correct + for the initalized number of breakpoints. + + Initalization - + n - integer 2 or larger. + backend - (optional) `None` or `str(pplite)` + manually_load_breakpoint_cache - (optional) bool + loading_kwrds - folder_or_file, path_to_file_or_folder + EXAMPLES:: sage: from cutgeneratingfunctionology.igp import * @@ -461,6 +461,12 @@ class BreakpointComplexClassContainer: sage: all([isinstance(cell, BasicSemialgebraicSet_base) for cell in bkpt_of_len_2.get_nnc_poly_from_bkpt()]) True + + (Advanced use) Data can be written and reused:: + + sage: bkpt_of_len_2.write_data() # not tested + sage: bkpt_of_len_2_loaded = BreakpointComplexClassContainer(2, manually_load_breakpoint_cache=True, folder_or_file="file", path_to_file_or_folder="bkpts_of_len_2.csv") # not tested + """ def __init__(self, n, backend=None, manually_load_breakpoint_cache=False, **loading_kwrds): self._n = n @@ -471,8 +477,9 @@ class BreakpointComplexClassContainer: # Load the minimal function cache. from minimalFunctionCache.utils import minimal_function_cache_info, minimal_function_cache_loader cache_info = minimal_function_cache_info() + # minimal_function_cache_loader throws a value error if a cache for n is not found. try: - self._data = minimal_function_cache_loader(self._n, "breakpoints") # minimal_function_cache_loader throws a value error if a cache for n is not found. + self._data = minimal_function_cache_loader(self._n, "breakpoints") except ValueError: minimal_funciton_cell_description_logger.info(f"The cache for {n} breakpoints has not been generated or could not be found.") minimal_funciton_cell_description_logger.info("Generating representative breakpoints.") @@ -492,25 +499,27 @@ class BreakpointComplexClassContainer: minimal_funciton_cell_description_logger.warning(f"This may take a while. Try installing the minimalfunctioncache.") self._data = make_rep_bkpts_with_len_n(self._n, backend=self._backend) minimal_funciton_cell_description_logger.info("Finished generating breakpoints.") + # For generating the cache and advanced use. elif manually_load_breakpoint_cache: - # this is for generating the cache and advanced use. try: - if loading_kwrds["breakpoints_or_rep_elems"].strip(" ").lower() == "breakpoints": - bkpts = [] - with open(loading_kwrds["path_to_file_or_file_name_in_cwd"]) as csvfile: - file_reader = csv.reader(csvfile) - for row in file_reader: - bkpts.append([QQ(data) for data in row]) - self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) - except KeyError: - if loading_kwrds["breakpoints_or_rep_elems"].strip(" ").lower() == "rep elems": + if loading_kwrds["folder_or_file"].strip(" ").lower() == "folder": self._data = [] - with open(loading_kwrds["path_to_file_or_file_name_in_cwd"]) as csvfile: + path = loading_kwrds["path_to_file_or_folder"].string(" ").lower() + import os + files_in_folder = os.listdir(path) + for file in files_in_folder: + with open(file) as csvfile: + file_reader = csv.reader(csvfile) + for row in file_reader: + self._data.append([QQ(data) for data in row]) + elif loading_kwrds["folder_or_file"].strip(" ").lower() == "file": + self._data = [] + with open(loading_kwrds["path_to_file_or_folder"]) as csvfile: file_reader = csv.reader(csvfile) for row in file_reader: self._data.append([QQ(data) for data in row]) else: - raise ValueError("No valid inputs provded. Maybe check your spelling?") + raise ValueError("check spelling of folder or file.") except KeyError: raise ValueError("No valid inputs provded. Maybe check your spelling?") else: @@ -520,25 +529,44 @@ class BreakpointComplexClassContainer: return f"Container for the space breakpoint sequences of length {self._n} under equivlance of polyhedral complexes." def get_rep_elems(self): + """ + Iterator yielding a breakpint sequence. + """ for bkpt in self._data: yield bkpt def get_nnc_poly_from_bkpt(self): + """ + Iterator yielding a cell such that all elements in the cell corrospond to 2d polyhedral complexes of the same combinatorial type. + """ for bkpt in self._data: yield nnc_poly_from_bkpt_sequence(bkpt, backend=self._backend) def num_rep_elems(self): + """ + Number of repersentative elements. + """ return len(self._data) + def n(self): + """ + The length of breakpoint sequences. + """ + return self._n + def covers_space(self): - ### TODO: This method should prove that container is correct. + """ + Not Impemented. Future use is intented to be a proof of correctness that all breakpoint sequences are covered. + """ + ### TODO: Impl. raise NotImplementedError def refine_space(self): - """Ensures that repersentative elements are unique and contained in a single cell. """ + """ + Not Impemented. Future use is to prove that the current cell description for breakpoints is, with respect to inclusion, minimal or in the case that it is not minimal, find a minimal description. + """ raise NotImplementedError - ### TODO: Test this. I think I need to write a containment method for the BSA - ### or pick the up the underlying polyhedron in the BSA. + ### TODO: Add a contaimenet checking method to BSA classes and finish impl. self._data = unique_list(self._data) cells_found = [] for bkpt in self._data: @@ -565,7 +593,7 @@ class BreakpointComplexClassContainer: if output_file_name_style is None: file_name_base = "bkpts_of_len_{}".format(self._n) else: - file_name_base =output_file_name_style + file_name_base = output_file_name_style if max_rows is not None: assert(max_rows >= 1) num_files = len(self._data)//max_rows + 1 @@ -591,14 +619,17 @@ class PiMinContContainer: A container for the space of continuous piecewise linear minimal functions with at most n breakpoints paramaterized by breakpoints and values using semialgebraic sets. - The container assumes that loaded data is correct and performs no checking. + The container will attempt to load from the `minimalFunctionCache module` or will generate + data if the minimalFunctionCache is not available. + Loading files generated from the container's `write_data()` method is supported. + When manually loading data, it is assume that all loaded data is correct + for the initalized number of breakpoints. + INPUTS: - n, an integer - keywords: - backend, None or str(pplite) - - load_bkpt_data, .csv file(s) of list of tuples of breakpoitns - - load_rep_elem_data, .csv file(s) of list of tuples of representative elements of the space of minimal functions + - loading_kwrds EXAMPLES:: @@ -627,9 +658,9 @@ class PiMinContContainer: sage: PiMin_2.write_data() # not tested - Written data can be reused :: + Written data can be reused.:: - sage: PiMin_2_loaded_data = PiMinContContainer(2, manually_load_function_cache=True, path_to_file_or_file_name_in_cwd="Pi_Min_2.csv", breakpoints_or_rep_elems="rep elems") # not tested + sage: PiMin_2_loaded_data = PiMinContContainer(2, manually_load_function_cache=True, folder_or_file="file", path_to_file_or_folder="Pi_Min_2.csv", breakpoints_or_rep_elems="rep_elems") # not tested sage: len([rep_elem for rep_elem in PiMin_2_loaded_data.get_rep_elems()]) # not tested 3 """ @@ -655,31 +686,47 @@ class PiMinContContainer: self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) except ImportError: if self._n > 4: - minimal_funciton_cell_description_logger.warning(f"This may take a while. Try installing the minimalfunctioncache.") + minimal_funciton_cell_description_logger.warning(f"This may take a while. Try installing the minimalFunctionCache.") bkpts = make_rep_bkpts_with_len_n(self._n, backend=self._backend) minimal_funciton_cell_description_logger.info("Finished generating breakpoints.") minimal_funciton_cell_description_logger.info("Computing repersentative elements.") self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) minimal_funciton_cell_description_logger.info("PiMin container, Reportin' for duty.") elif manually_load_function_cache: - # this is for generating the cache and advanced use. + # this is for generating the cache and advanced use. + minimal_funciton_cell_description_logger.info("loading files...") try: if loading_kwrds["breakpoints_or_rep_elems"].strip(" ").lower() == "breakpoints": - bkpts = [] - with open(loading_kwrds["path_to_file_or_file_name_in_cwd"]) as csvfile: - file_reader = csv.reader(csvfile) - for row in file_reader: - bkpts.append([QQ(data) for data in row]) + bkpts = BreakpointComplexClassContainer(self._n, backend=self._backend, manually_load_breakpoint_cache=True, file_or_folder=loading_kwrds["file_or_folder"], path_to_file_or_folder=loading_kwrds["path_to_file_or_folder"]).get_rep_elems() + if self._n > 4: + minimal_funciton_cell_description_logger.warning("This may take a while. Try installing the minimalFunctionCache.") self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) - except KeyError: - if loading_kwrds["breakpoints_or_rep_elems"].strip(" ").lower() == "rep elems": - self._data = [] - with open(loading_kwrds["path_to_file_or_file_name_in_cwd"]) as csvfile: - file_reader = csv.reader(csvfile) - for row in file_reader: - self._data.append([QQ(data) for data in row]) + minimal_funciton_cell_description_logger.info("PiMin container, Reportin' for duty.") + elif loading_kwrds["breakpoints_or_rep_elems"].strip(" ").lower() == "rep_elems": + if loading_kwrds["folder_or_file"].strip(" ").lower() == "folder": + self._data = [] + path = loading_kwrds["path_to_file_or_folder"].string(" ").lower() + import os + files_in_folder = os.listdir(path) + for file in files_in_folder: + with open(file) as csvfile: + file_reader = csv.reader(csvfile) + for row in file_reader: + bkpt = [QQ(data) for data in row[0].strip("[]").split(",")] + val = [QQ(data) for data in row[1].strip("[]").split(",")] + self._data.append((bkpt, val)) + minimal_funciton_cell_description_logger.info("PiMin container, Reportin' for duty.") + elif loading_kwrds["folder_or_file"].strip(" ").lower() == "file": + self._data = [] + with open(loading_kwrds["path_to_file_or_folder"]) as csvfile: + file_reader = csv.reader(csvfile) + for row in file_reader: + bkpt = [QQ(data) for data in row[0].strip("[]").split(",")] + val = [QQ(data) for data in row[1].strip("[]").split(",")] + self._data.append((bkpt, val)) + minimal_funciton_cell_description_logger.info("PiMin container, Reportin' for duty.") else: - raise ValueError("No valid inputs provded. Maybe check your spelling?") + raise ValueError("check spelling of folder or file.") except KeyError: raise ValueError("No valid inputs provded. Maybe check your spelling?") else: @@ -708,11 +755,17 @@ class PiMinContContainer: return self._n def covers_space(self): - ### TODO: This method should prove that container is correct. + """ + Not Impemented. Future use is intented to be a proof of correctness that all breakpoint sequences are covered. + """ + ### TODO: Impl. raise NotImplementedError def refine_space(self): - ### TODO: This method should be called when loading multiple file to ensure cells have not been duplicated. + """ + Not Impemented. Future use is to prove that the current cell description for breakpoints is, with respect to inclusion, minimal or in the case that it is not minimal, find a minimal description. + """ + ### TODO: Impl. raise NotImplementedError def write_data(self, output_file_name_style=None, max_rows=None): From 8f5f5cef19a865a5e70716a587269a3cd1c551b5 Mon Sep 17 00:00:00 2001 From: Acadia Larsen <102884863+ComboProblem@users.noreply.github.com> Date: Thu, 9 Apr 2026 06:44:08 -0700 Subject: [PATCH 56/64] fix paths --- .../igp/minimal_function_cell_description.sage | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage index aacfe8bfb..498687b61 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage @@ -504,11 +504,11 @@ class BreakpointComplexClassContainer: try: if loading_kwrds["folder_or_file"].strip(" ").lower() == "folder": self._data = [] - path = loading_kwrds["path_to_file_or_folder"].string(" ").lower() + path = loading_kwrds["path_to_file_or_folder"] import os files_in_folder = os.listdir(path) for file in files_in_folder: - with open(file) as csvfile: + with open(os.path.join(path, file)) as csvfile: file_reader = csv.reader(csvfile) for row in file_reader: self._data.append([QQ(data) for data in row]) @@ -705,11 +705,11 @@ class PiMinContContainer: elif loading_kwrds["breakpoints_or_rep_elems"].strip(" ").lower() == "rep_elems": if loading_kwrds["folder_or_file"].strip(" ").lower() == "folder": self._data = [] - path = loading_kwrds["path_to_file_or_folder"].string(" ").lower() + path = loading_kwrds["path_to_file_or_folder"] import os files_in_folder = os.listdir(path) for file in files_in_folder: - with open(file) as csvfile: + with open(os.path.join(path, file)) as csvfile: file_reader = csv.reader(csvfile) for row in file_reader: bkpt = [QQ(data) for data in row[0].strip("[]").split(",")] From 84544bb15c547eecefecab6422c78d1d9815dd9a Mon Sep 17 00:00:00 2001 From: Acadia Larsen Date: Thu, 9 Apr 2026 09:19:41 -0700 Subject: [PATCH 57/64] fix logic error in loading methods of bkpt and pi min containers --- .../minimal_function_cell_description.sage | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage index 498687b61..52bf80de8 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage @@ -187,7 +187,10 @@ def make_rep_bkpts_with_len_n(n, k=1, bkpts=None, backend=None): new_bkpts = [] if n < 2: raise ValueError("n>=2") - if k == n: + if k == n and bkpts is not None: + minimal_funciton_cell_description_logger.warning(f"Inital imputs suggest that the bkpts provided are already correct. Returning the inital bkpts.") + return bkpts + if k == n and bkpts is None: raise ValueError("k Date: Fri, 10 Apr 2026 13:55:38 -0700 Subject: [PATCH 58/64] correct input value for cache loader --- .../igp/minimal_function_cell_description.sage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage index 52bf80de8..87eb2889e 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage @@ -679,7 +679,7 @@ class PiMinContContainer: # Load the minimal function cache. from minimalFunctionCache.utils import minimal_function_cache_loader try: - self._data = minimal_function_cache_loader(self._n, "rep elems") + self._data = minimal_function_cache_loader(self._n, "rep_elems") # cache loader throws a value error if a cache for n is not found. except ValueError: minimal_funciton_cell_description_logger.info(f"The cache for {n} breakpoints has not been generated or could not be found.") From bde5373c2ea53ae619ef09739b16ae90cbfc0ac1 Mon Sep 17 00:00:00 2001 From: Acadia Larsen <102884863+ComboProblem@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:43:00 -0700 Subject: [PATCH 59/64] first pass proof reading --- .../minimal_function_cell_description.sage | 270 +++++++++--------- 1 file changed, 134 insertions(+), 136 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage index 87eb2889e..12be3195b 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage @@ -9,11 +9,11 @@ minimal_funciton_cell_description_logger = logging.getLogger("cutgeneratingfunct minimal_funciton_cell_description_logger.setLevel(logging.INFO) ### Note to future reader: ### -### bkpt is assumed to be a breakpoint sequence of length n>= 2. -### Breakpoint sequence are sorted lists of real numbers in [0,1). +### bkpt is assumed to be a breakpoint sequence of length n>= 2. +### Breakpoint sequence are sorted lists of real numbers in [0,1). ### A breakpoint sequences should always have 0 as an element. ### This is never strictly enforced in this file and it is assumed that -### the user is always provided a breakpoint sequence. +### the user is always provided a breakpoint sequence. # global defaults for logging from portions of igp; change to log different parts of igp. log_paramateric_real_field = False @@ -27,15 +27,15 @@ class RepElemGenFailure(Exception): def nnc_poly_from_bkpt_sequence(bkpt, backend=None): r""" Defines an NNC polyhedron P such that for all b in P the delta complex of b is isomoprhic to the delta complex of bkpt. - + INPUT: - - ``bkpt`` - sorted list of length 1 or more of sage type in the interval [0,1). - - ```backend`` - ``None``, ``str(pplite)`` - + - ``bkpt`` - sorted list of length 1 or more of sage type in the interval [0,1). + - ```backend`` - ``None``, ``str(pplite)`` + OUTPUT: class::``BasicSemialgebraicSet_veronese`` - + EXAMPLES:: - sage: from cutgeneratingfunctionology.igp import * + sage: from cutgeneratingfunctionology.igp import * sage: logging.disable(logging.INFO) # suppress logging for tests sage: nnc_poly_from_bkpt_sequence([0, 4/5]) BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(Constraint_System {x0==0, -x1+1>0, 2*x1-1>0}, names=[x0, x1]), polynomial_map=[lambda0, lambda1]) @@ -50,14 +50,14 @@ def nnc_poly_from_bkpt_sequence(bkpt, backend=None): logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(logging.ERROR) for i in range(0,n): coord_names.append('lambda'+str(i)) - K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, default_backend=backend) + K = ParametricRealField(names=coord_names, values=vals, mutable_values=True, big_cells=True, default_backend=backend) K.gens()[0] == 0 for i in range(n-1): K.gens()[i] < K.gens()[i+1] K.gens()[n-1] < 1 for i in range(n): for j in range(n): - if bkpt[i]+bkpt[j]>= 1: + if bkpt[i]+bkpt[j] >= 1: w = 1 else: w = 0 @@ -83,15 +83,15 @@ def nnc_poly_from_bkpt_sequence(bkpt, backend=None): def add_breakpoints_and_find_equiv_classes(bkpt_poly): r""" Takes dim k-1 breakpoint NNC polyhedron (as a :class:`BasicSemialgebraicSet_base`), adds a dimension, - and finds all possible representive elements for equivlance classes of polyhedral complexes. - + and finds all possible representative elements for equivalence classes of polyhedral complexes. + INPUT: :class:`BasicSemialgebraicSet_Polyhedral` - - OUTPUT: unique list of breakpoint sequnes of lenght k (as tuples). - + + OUTPUT: unique list of breakpoint sequnes of length k (as tuples). + EXAMPLES:: - sage: from cutgeneratingfunctionology.igp import * + sage: from cutgeneratingfunctionology.igp import * sage: logging.disable(logging.INFO) # suppress logging for tests sage: add_breakpoints_and_find_equiv_classes(nnc_poly_from_bkpt_sequence([0,4/5]).upstairs()) [(0, 9/14, 83/112), (0, 13/20, 33/40), (0, 9/14, 101/112), (0, 7/10, 17/20)] @@ -101,14 +101,14 @@ def add_breakpoints_and_find_equiv_classes(bkpt_poly): B_cap_N_b.add_space_dimensions_and_embed(1) # get new number of breakpoints. k = B_cap_N_b.ambient_dim() - if k< 1: + if k < 1: raise ValueError("bkpt_poly should have space dim at least 1.") model_bound_bkpts = [0]*k model_bound_bkpts[k-1] = 1 - # 0 < lambda_k <1 + # 0 < lambda_k < 1 # we assume the bsa is "polyhedra type" B_cap_N_b.add_linear_constraint(model_bound_bkpts, QQ(-1), operator.lt) # model bounds - B_cap_N_b.add_linear_constraint(model_bound_bkpts, QQ(0), operator.gt) # model bounds + B_cap_N_b.add_linear_constraint(model_bound_bkpts, QQ(0), operator.gt) # model bounds bkpt_order = [0]*k bkpt_order[k-2] = 1 bkpt_order[k-1] = -1 @@ -131,7 +131,7 @@ def add_breakpoints_and_find_equiv_classes(bkpt_poly): lhs_i_plus_1 = [0]*k lhs_i_plus_1[k-1] = -2 if i < k-1: - lhs_i_plus_1[i+1] = 1 + lhs_i_plus_1[i+1] = 1 B_cap_N_b_copy.add_linear_constraint(lhs_i_plus_1, QQ(interval_w), operator.gt) else: B_cap_N_b_copy.add_linear_constraint(lhs_i_plus_1, QQ(interval_w + 1), operator.gt) @@ -153,47 +153,47 @@ def make_rep_bkpts_with_len_n(n, k=1, bkpts=None, backend=None): r""" Produce representative elements of every isomorphism class of breakpoints complexes for breakpoint sequences of length n. - INPUT: - - n, integer, maximum length of breakpoint sequence. - - k, assumed length of breakpoint sequences in ``bkpts`` - - bkpts, list of breakpoint sequenes (sorted , length of every element, bkpts, an iterable of breakpoints all of length k. + INPUT: + - n, integer, maximum length of breakpoint sequence. + - k, assumed length of breakpoint sequences in ``bkpts``. + - bkpts, list of breakpoint sequences of length k. OUTPUT: A list of representative elements of every isomorphism class of breakpoints complexes for breakpoint sequences of length n extrapolated from bkpts. - + EXAMPLES:: - - sage: from cutgeneratingfunctionology.igp import * + + sage: from cutgeneratingfunctionology.igp import * sage: logging.disable(logging.INFO) # suppress logging for tests sage: make_rep_bkpts_with_len_n(2) [(0, 1/2), (0, 13/18), (0, 5/18)] - + The number of representative elements grows quickly:: - - sage: bkpts_rep_with_len_3 = make_rep_bkpts_with_len_n(3) + + sage: bkpts_rep_with_len_3 = make_rep_bkpts_with_len_n(3) sage: len(bkpts_rep_with_len_3) 34 - - Previous computations can be reused:: - + + Previous computations can be reused:: + sage: bkpts_rep_with_len_4 = make_rep_bkpts_with_len_n(4, 3, bkpts_rep_with_len_3) sage: len(bkpts_rep_with_len_4) 329 """ - # Matthias has suggested looking at a directed tree. - # An alternative approach would be to look into using a (graded) lattice as a data strcture. - # We have bkpt \leq bkpt' if and only if dim(NNC(bkpt)) \leq dim(NNC(bkpt')) + # Matthias has suggested looking at a directed tree. + # An alternative approach would be to look into using a (graded) lattice as a data structure. + # We have bkpt \leq bkpt' if and only if dim(NNC(bkpt)) \leq dim(NNC(bkpt')) # and embed(NNC(bkpt), dim(NNC(bkpt')) \leq NNC(bkpt') in the poset of NNC polyhedra. # This might speed up/less the load of verifying unquiness of cells which is the time bounding task here. new_bkpts = [] if n < 2: raise ValueError("n>=2") if k == n and bkpts is not None: - minimal_funciton_cell_description_logger.warning(f"Inital imputs suggest that the bkpts provided are already correct. Returning the inital bkpts.") + minimal_funciton_cell_description_logger.warning(f"Initial inputs suggest that the bkpts provided are already correct. Returning the initial bkpts.") return bkpts if k == n and bkpts is None: raise ValueError("k 0 h = piecewise_function_from_breakpoints_and_values(bkpt + [1], K.gens() + [0], merge=False) # Assumes minimality for the partially defined function. @@ -282,19 +282,19 @@ def value_nnc_polyhedron_value_cords(bkpt, f_index, backend=None): def value_nnc_polyhedron(bkpt, f_index, backend=None): r""" - For a given breakpoints seqeunce and f index, write the value polyhedron as a basic semialgebraic set in the full space of parameters. - + For a given breakpoints sequence and f index, write the value polyhedron as a basic semialgebraic set in the full space of parameters. + INPUTS: - - ``bkpt`` - sorted list of length 2 or more of sage type in the interval [0,1). - - ``f_index`` - integer between 1 and length of ``len(bkpt) -1``. - - ``backend`` - ``None``, ``str(pplite)`` - - OUTPUT: - - class::``BasicSemialgebraicSet_veronese`` - + - ``bkpt`` - sorted list of length 2 or more of sage type in the interval [0,1). + - ``f_index`` - integer between 1 and length of ``len(bkpt) -1``. + - ``backend`` - ``None``, ``str(pplite)`` + + OUTPUT: + - class::``BasicSemialgebraicSet_veronese`` - The value polyehdron in only the breakpoint and value parameter space. + EXAMPLES:: - sage: from cutgeneratingfunctionology.igp import * + sage: from cutgeneratingfunctionology.igp import * sage: logging.disable(logging.INFO) # suppress logging for tests sage: value_nnc_polyhedron([0,4/5], 1) # gmic with f=4/5, a trivial value polyhedron BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(Constraint_System {x3-1==0, x2==0, 5*x1-4==0, x0==0}, names=[x0, x1, x2, x3]), polynomial_map=[lambda0, lambda1, gamma0, gamma1]) @@ -319,16 +319,16 @@ def value_nnc_polyhedron(bkpt, f_index, backend=None): coord_names.append('lambda'+str(i)) for i in range(n): coord_names.append('gamma'+str(i)) - K = ParametricRealField(names=coord_names, values = vals, mutable_values=True, big_cells=True, allow_refinement=False, default_backend=backend) - # breakpoint parameters are the mesured breakpoint values. + K = ParametricRealField(names=coord_names, values=vals, mutable_values=True, big_cells=True, allow_refinement=False, default_backend=backend) + # breakpoint parameters are the measured breakpoint values. for i in range(n): K.gens()[i] == bkpt[i] # Necessary conditions on value parameters. - K.gens()[n] == 0 + K.gens()[n] == 0 for i in range(1, n): - K.gens()[i+n] <=1 + K.gens()[i+n] <= 1 K.gens()[i+n] > 0 - h = piecewise_function_from_breakpoints_and_values(K.gens()[0:n] + [1], K.gens()[n:2*n] + [0], merge=False) + h = piecewise_function_from_breakpoints_and_values(K.gens()[0:n] + [1], K.gens()[n:2*n] + [0], merge=False) # Assumes minimality for the partially defined function. for vert in generate_type_1_vertices_continuous(h, operator.ge, K.gens()[0:n] + [1]): vert @@ -336,7 +336,7 @@ def value_nnc_polyhedron(bkpt, f_index, backend=None): vert for vert in generate_assumed_symmetric_vertices_continuous(h, K.gens()[f_index], [0] + K.gens()[0:n] + [1]): vert - if not log_paramateric_real_field: + if not log_paramateric_real_field logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(parametric_logging_level) if not log_pw_functions: logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) @@ -347,16 +347,16 @@ def bsa_of_rep_element(bkpt, vals, backend=None): Given pi_(bkpt, vals) is {minimal, not minimal}, find BSA subset of R^(2n) such that (bkpt, vals) in BSA and for all p in BSA, pi_p is {minimal, not minimal}. - INPUT: - - ``bkpt`` - a breakpoint seqeunce - - ``vals`` - list like of sage numerical types corrosponding values for the breakpoint sequence. + INPUT: + - ``bkpt`` - a breakpoint sequence + - ``vals`` - list like of sage numerical types corresponding values for the breakpoint sequence. - ``backend`` - None, ``str(pplite)`` OUTPUT: A basic semialgebraic set. - + EXAMPLES:: - sage: from cutgeneratingfunctionology.igp import * + sage: from cutgeneratingfunctionology.igp import * sage: logging.disable(logging.INFO) # suppress logging for tests sage: bsa_of_rep_element([0,4/5], [0,1]) # bsa for GMIC BasicSemialgebraicSet_veronese(BasicSemialgebraicSet_polyhedral_ppl_NNC_Polyhedron(Constraint_System {x5-1==0, x4==0, x0==0, 2*x3-1>0, -2*x1+x2-x3+1>=0, -x3+1>0}, names=[x0, x1, x2, x3, x4, x5]), polynomial_map=[gamma0, lambda1^2*gamma0, lambda1*gamma0, lambda1, lambda0, gamma1]) @@ -368,13 +368,13 @@ def bsa_of_rep_element(bkpt, vals, backend=None): if not log_pw_functions: pw_logging_level = logging.getLogger("cutgeneratingfunctionology.igp.functions").getEffectiveLevel() logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(logging.ERROR) - assert(n>=2) + assert(n >= 2) coord_names = [] for i in range(n): coord_names.append('lambda'+str(i)) for i in range(n): coord_names.append('gamma'+str(i)) - K = ParametricRealField(names=coord_names, values = bkpt+vals, big_cells=True, default_backend=backend) + K = ParametricRealField(names=coord_names, values=bkpt+vals, big_cells=True, default_backend=backend) h = piecewise_function_from_breakpoints_and_values(K.gens()[0:n] + [1], K.gens()[n:2*n] + [0], merge=False) minimality_test(h) if not log_paramateric_real_field: @@ -383,27 +383,26 @@ def bsa_of_rep_element(bkpt, vals, backend=None): logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) return K.make_proof_cell().bsa - def find_minimal_function_reps_from_bkpts(bkpts, prove_minimality=True, backend=None): r""" Finds representative elements of minimal functions from a given breakpoint sequence. - + INPUT: - - ``bkpts`` - an interable of breakpoint seqeunces. - - ``prove_minimality`` - bool, proves minimality of paramaterized function + - ``bkpts`` - an iterable of breakpoint sequences. + - ``prove_minimality`` - bool, proves minimality of parameterized function. - ``backend`` - None, ``str(pplite)`` - + OUTPUT: - - List of tuples of lists - + - List of (breakpoint, value) pairs. + EXAMPLES:: - sage: from cutgeneratingfunctionology.igp import * - sage: logging.disable(logging.INFO) # suppress logging for tests + sage: from cutgeneratingfunctionology.igp import * + sage: logging.disable(logging.INFO) # suppress logging for tests sage: bkpts = make_rep_bkpts_with_len_n(2) sage: find_minimal_function_reps_from_bkpts(bkpts) [([0, 1/2], [0, 1]), ([0, 13/18], [0, 1]), ([0, 5/18], [0, 1])] - + """ rep_elems = [] if not log_pw_functions: @@ -424,7 +423,7 @@ def find_minimal_function_reps_from_bkpts(bkpts, prove_minimality=True, backend= if prove_minimality: h = piecewise_function_from_breakpoints_and_values(list(bkpt)+[1], test_val+[0]) if not minimality_test(h): # The following error should never be raised when this function is used as intended. - raise ValueError(f"({bkpt}, {test_val}) paramaterized by breakpoints and values is not a minimal function but assuming a breakpoint sequence is input, this should be minimal.") + raise ValueError(f"({bkpt}, {test_val}) parameterized by breakpoints and values is not a minimal function but assuming a breakpoint sequence is input, this should be minimal.") rep_elems.append((list(bkpt), test_val)) if not log_pw_functions: logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) @@ -432,17 +431,17 @@ def find_minimal_function_reps_from_bkpts(bkpts, prove_minimality=True, backend= class BreakpointComplexClassContainer: """ - A container for the family of breakpoint complexes for peicewise linear functions + A container for the family of breakpoint complexes for piecewise linear functions with at most n breakpoints. - + The container will attempt to load from the minimalFunctionCache module or will generate data if the minimalFunctionCache is not available. - Loading files generated from the container's `write_data()` method is supported. + Loading files generated from the container's `write_data()` method is supported. When manually loading data, it is assume that all loaded data is correct - for the initalized number of breakpoints. + for the Initialized number of breakpoints. - Initalization - + Initialization: n - integer 2 or larger. backend - (optional) `None` or `str(pplite)` manually_load_breakpoint_cache - (optional) bool @@ -450,7 +449,7 @@ class BreakpointComplexClassContainer: EXAMPLES:: - sage: from cutgeneratingfunctionology.igp import * + sage: from cutgeneratingfunctionology.igp import * sage: logging.disable(logging.INFO) # suppress logging for tests sage: bkpt_of_len_2 = BreakpointComplexClassContainer(2) sage: bkpt_of_len_2.num_rep_elems() @@ -459,17 +458,16 @@ class BreakpointComplexClassContainer: [(0, 1/2), (0, 13/18), (0, 5/18)] sage: make_rep_bkpts_with_len_n(2) [(0, 1/2), (0, 13/18), (0, 5/18)] - - A cell descrption of semialgebraic sets can be accessed:: - + + A cell description of semialgebraic sets can be accessed:: + sage: all([isinstance(cell, BasicSemialgebraicSet_base) for cell in bkpt_of_len_2.get_nnc_poly_from_bkpt()]) True (Advanced use) Data can be written and reused:: - + sage: bkpt_of_len_2.write_data() # not tested sage: bkpt_of_len_2_loaded = BreakpointComplexClassContainer(2, manually_load_breakpoint_cache=True, file_or_folder="file", path_to_file_or_folder="bkpts_of_len_2.csv") # not tested - """ def __init__(self, n, backend=None, manually_load_breakpoint_cache=False, **loading_kwrds): self._n = n @@ -500,7 +498,7 @@ class BreakpointComplexClassContainer: except ImportError: if self._n > 4: minimal_funciton_cell_description_logger.warning(f"This may take a while. Try installing the minimalfunctioncache.") - self._data = make_rep_bkpts_with_len_n(self._n, backend=self._backend) + self._data = make_rep_bkpts_with_len_n(self._n, backend=self._backend) minimal_funciton_cell_description_logger.info("Finished generating breakpoints.") # For generating the cache and advanced use. elif manually_load_breakpoint_cache: @@ -515,7 +513,7 @@ class BreakpointComplexClassContainer: file_reader = csv.reader(csvfile) for row in file_reader: loaded_data.append([QQ(data) for data in row]) - k = len(loaded_data[0]) # assume all the data is correct and of teh same lenght. + k = len(loaded_data[0]) # assume all the data is correct and of the same length. self._data = make_rep_bkpts_with_len_n(self._n, k, loaded_data, backend=self._backend) elif loading_kwrds["file_or_folder"].strip(" ").lower() == "file": loaded_data = [] @@ -523,17 +521,17 @@ class BreakpointComplexClassContainer: file_reader = csv.reader(csvfile) for row in file_reader: loaded_data.append([QQ(data) for data in row]) - k = len(loaded_data[0]) # assume all the data is correct and of teh same lenght. + k = len(loaded_data[0]) # assume all the data is correct and of the same length. self._data = make_rep_bkpts_with_len_n(self._n, k, loaded_data, backend=self._backend) else: - raise ValueError("check spelling of folder or file.") + raise ValueError("Check spelling of folder or file.") except KeyError: - raise ValueError("No valid inputs provded. Maybe check your spelling?") + raise ValueError("No valid inputs provided. Maybe check your spelling?") else: raise ValueError("No elements have been loaded. Check inputs") def __repr__(self): - return f"Container for the space breakpoint sequences of length {self._n} under equivlance of polyhedral complexes." + return f"Container for the space breakpoint sequences of length {self._n} under equivalence of polyhedral complexes." def get_rep_elems(self): """ @@ -544,7 +542,7 @@ class BreakpointComplexClassContainer: def get_nnc_poly_from_bkpt(self): """ - Iterator yielding a cell such that all elements in the cell corrospond to 2d polyhedral complexes of the same combinatorial type. + Iterator yielding a cell such that all elements in the cell correspond to 2d polyhedral complexes of the same combinatorial type. """ for bkpt in self._data: yield nnc_poly_from_bkpt_sequence(bkpt, backend=self._backend) @@ -563,17 +561,17 @@ class BreakpointComplexClassContainer: def covers_space(self): """ - Not Impemented. Future use is intented to be a proof of correctness that all breakpoint sequences are covered. + Not Implemented. Future use is intended to be a proof of correctness that all breakpoint sequences are covered. """ ### TODO: Impl. raise NotImplementedError def refine_space(self): """ - Not Impemented. Future use is to prove that the current cell description for breakpoints is, with respect to inclusion, minimal or in the case that it is not minimal, find a minimal description. + Not Implemented. Future use is to prove that the current cell description for breakpoints is, with respect to inclusion, minimal or in the case that it is not minimal, find a minimal description. """ raise NotImplementedError ### TODO: Add a contaimenet checking method to BSA classes and finish impl. - self._data = unique_list(self._data) + self._data = unique_list(self._data) cells_found = [] for bkpt in self._data: bkpt_contained_in_cell = False @@ -603,11 +601,11 @@ class BreakpointComplexClassContainer: if max_rows is not None: assert(max_rows >= 1) num_files = len(self._data)//max_rows + 1 - file_name_base = file_name_base + "_part_0" + file_name_base = file_name_base+"_part_0" if max_rows is None: max_rows = len(self._data) num_files = 1 - output_file = file_name_base +".csv" + output_file = file_name_base+".csv" for file_number in range(num_files): out_file = open(output_file, "w") data_writer = csv.writer(out_file, csv.QUOTE_NONE) @@ -623,14 +621,14 @@ class BreakpointComplexClassContainer: class PiMinContContainer: """ A container for the space of continuous piecewise linear minimal functions with at - most n breakpoints paramaterized by breakpoints and values using semialgebraic sets. - + most n breakpoints parameterized by breakpoints and values using semialgebraic sets. + The container will attempt to load from the `minimalFunctionCache module` or will generate data if the minimalFunctionCache is not available. - - Loading files generated from the container's `write_data()` method is supported. + + Loading files generated from the container's `write_data()` method is supported. When manually loading data, it is assume that all loaded data is correct - for the initalized number of breakpoints. + for the Initialized number of breakpoints. INPUTS: - n, an integer @@ -639,33 +637,33 @@ class PiMinContContainer: EXAMPLES:: - sage: from cutgeneratingfunctionology.igp import * + sage: from cutgeneratingfunctionology.igp import * sage: logging.disable(logging.INFO) # suppress logging for tests sage: PiMin_2 = PiMinContContainer(2) sage: all([minimality_test(pi) for pi in PiMin_2.get_rep_functions()]) True sage: PiMin_2 Space of minimal functions with at most 2 breakpoints parameterized by breakpoints and values using semialgebraic sets. - - A cell descrption of semialgebraic sets can be accessed:: - + + A cell description of semialgebraic sets can be accessed:: + sage: all([isinstance(cell, BasicSemialgebraicSet_base) for cell in PiMin_2.get_semialgebraic_sets()]) True - + Data is stored as repersenative elements. The number of repersentative elements grows quickly. :: - + sage: PiMin_4 = PiMinContContainer(4) # not tested sage: len([rep_elem for rep_elem in PiMin_4.get_rep_elems()]) # not tested 987 sage: len([rep_elem for rep_elem in PiMin_2.get_rep_elems()]) 3 - + The container provides methods of writing data.:: - + sage: PiMin_2.write_data() # not tested - + Written data can be reused.:: - + sage: PiMin_2_loaded_data = PiMinContContainer(2, manually_load_function_cache=True, file_or_folder="file", path_to_file_or_folder="Pi_Min_2.csv", breakpoints_or_rep_elems="rep_elems") # not tested sage: len([rep_elem for rep_elem in PiMin_2_loaded_data.get_rep_elems()]) # not tested 3 @@ -689,7 +687,7 @@ class PiMinContContainer: minimal_funciton_cell_description_logger.warning(f"This may take a while.") minimal_funciton_cell_description_logger.info("Finished generating breakpoints.") minimal_funciton_cell_description_logger.info("Computing repersentative elements.") - self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) + self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) except ImportError: if self._n > 4: minimal_funciton_cell_description_logger.warning(f"This may take a while. Try installing the minimalFunctionCache.") @@ -734,7 +732,7 @@ class PiMinContContainer: else: raise ValueError("check spelling of folder or file.") except KeyError: - raise ValueError("No valid inputs provded. Maybe check your spelling?") + raise ValueError("No valid inputs provided. Maybe check your spelling?") else: raise ValueError("No elements have been loaded. Check inputs") @@ -752,24 +750,24 @@ class PiMinContContainer: yield (list(b), list(v)) def get_rep_functions(self): - """Iterator for representative functinos.""" + """Iterator for representative functions.""" for b, v in self._data: yield piecewise_function_from_breakpoints_and_values(list(b)+[1], list(v)+[0]) def n(self): - """The maximum number of proper breakpoints of paramaterized functions in the space.""" + """The maximum number of proper breakpoints of parameterized functions in the space.""" return self._n - + def covers_space(self): """ - Not Impemented. Future use is intented to be a proof of correctness that all breakpoint sequences are covered. + Not Implemented. Future use is intended to be a proof of correctness that all breakpoint sequences are covered. """ ### TODO: Impl. raise NotImplementedError def refine_space(self): """ - Not Impemented. Future use is to prove that the current cell description for breakpoints is, with respect to inclusion, minimal or in the case that it is not minimal, find a minimal description. + Not Implemented. Future use is to prove that the current cell description for breakpoints is, with respect to inclusion, minimal or in the case that it is not minimal, find a minimal description. """ ### TODO: Impl. raise NotImplementedError @@ -785,7 +783,7 @@ class PiMinContContainer: if output_file_name_style is None: file_name_base = "Pi_Min_{}".format(self._n) else: - file_name_base =output_file_name_style + file_name_base = output_file_name_style if max_rows is not None: assert(max_rows >= 1) num_files = len(self._data)//max_rows + 1 @@ -793,7 +791,7 @@ class PiMinContContainer: if max_rows is None: max_rows = len(self._data) num_files = 1 - output_file = file_name_base +".csv" + output_file = file_name_base + ".csv" for file_number in range(num_files): out_file = open(output_file, "w") data_writer = csv.writer(out_file, csv.QUOTE_NONE) From 946ac7cdcced39229a4dba95d45f55afc01478fc Mon Sep 17 00:00:00 2001 From: Acadia Larsen <102884863+ComboProblem@users.noreply.github.com> Date: Tue, 14 Apr 2026 09:04:17 -0700 Subject: [PATCH 60/64] add plotting diagrams to illustrate adding a breakpoint to a complex --- .../minimal_function_cell_description.sage | 78 ++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage index 12be3195b..f41e21038 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage @@ -6,7 +6,7 @@ import logging import os minimal_funciton_cell_description_logger = logging.getLogger("cutgeneratingfunctionology.igp.minimal_funciton_cell_description") -minimal_funciton_cell_description_logger.setLevel(logging.INFO) +minimal_funciton_cell_description_logger.setLevel(logging.DEBUG) ### Note to future reader: ### ### bkpt is assumed to be a breakpoint sequence of length n>= 2. @@ -336,7 +336,7 @@ def value_nnc_polyhedron(bkpt, f_index, backend=None): vert for vert in generate_assumed_symmetric_vertices_continuous(h, K.gens()[f_index], [0] + K.gens()[0:n] + [1]): vert - if not log_paramateric_real_field + if not log_paramateric_real_field: logging.getLogger("cutgeneratingfunctionology.igp.parametric").setLevel(parametric_logging_level) if not log_pw_functions: logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) @@ -802,3 +802,77 @@ class PiMinContContainer: break out_file.close() output_file = file_name_base[:-1]+"{}".format(file_number+1)+".csv" + + +def plot_2_d_polyhedral_complex(bkpt, highlight_index=-1, highlight_color='blue', **kwds): + r""" + Returns a plot of 2-d polyehdral complex of the breakpoint sequence of length n, Complex Delta P_bkpt. + Lines generated from a partiuclar breakpoint can be highlighted. + Assumes that the breakpoint sequence is correct and ``highlight_index`` is between 0 and ``len(bkpt)-1``, inclusive. + + EXAMPLES:: + sage: from cutgeneratingfunctiology.igp import * + sage: plot_2_d_polyhedral_complex([0, 4/5]) # not tested + + A breakpoint can be highlighted:: + sage: plot_2_d_polyhedral_complex([0, 1/2, 4/5], highlight_index=1) # not tested + + An the color changed:: + sage: plot_2_d_polyhedral_complex([0, 4/5], highlight_index=1, highlight_color='green') # not tested + """ + bkpt_ext = bkpt + [1] + x = var('x') + p = Graphics() + n = len(bkpt) + if highlight_index is None: + highlight_index = -1 + for i in range(1,n+1): + if i == highlight_index: + p += line([(0, bkpt_ext[i]), (bkpt_ext[i], 0)], color=highlight_color, linestyle='dashed') + else: + p += line([(0, bkpt_ext[i]), (bkpt_ext[i], 0)], color='grey') + for i in range(1,n): + if i == highlight_index: + p += line([(bkpt_ext[i], 1), (1, bkpt_ext[i])], color=highlight_color, linestyle='dashed') + else: + p += line([(bkpt_ext[i], 1), (1, bkpt_ext[i])], color='grey') + for i in range(n+1): + if i == highlight_index: + p += plot(bkpt_ext[i], (0, 1), color=highlight_color, linestyle='dashed') + else: + p += plot(bkpt_ext[i], (0, 1), color='grey') + y=var('y') + for i in range(n): + if i == highlight_index: + p += parametric_plot((bkpt_ext[i],y), (y,0,1), color=highlight_color, linestyle='dashed') + else: + p += parametric_plot((bkpt_ext[i],y), (y,0,1), color='grey') + p += line([(1,0), (1,1)], color='grey') + return p + +def plot_2_d_polyhedral_complex_and_descendants(bkpt, max_row_length=5, max_number_additional_diagrams=15, **kwds): + r""" + Returns a plot of 2-d polyehdral complex of the breakpoint sequence of length n, Complex Delta P_bkpt + together with plots of Complex :math:`Delta P_{bkpt \cup \lambda^*}` where Complex Delta P_bkpt is a + subcomplex of :math:`Delta P_{bkpt \cup \lambda^*}`. + + EXAMPLES:: + sage: from cutgeneratingfunctiology.igp import * + sage: plot_2_d_polyhedral_complex_and_descendants([0, 1/2]) # not tested + """ + n = len(bkpt) + n_plus_one_breakpoints = unique_list(make_rep_bkpts_with_len_n(n+1, k=n, bkpts=[bkpt])) + number_of_new_diagrams = len(n_plus_one_breakpoints) + m = max_row_length + i = 0 + plots = [] + while i < max_number_additional_diagrams and i < number_of_new_diagrams: + plots.append((plot_2_d_polyhedral_complex(list(n_plus_one_breakpoints[i]), n, **kwds), (i%m , int(i/m) ,.75,.75))) + i += 1 + if i < m: + plots.append((plot_2_d_polyhedral_complex(bkpt), (int(i/2), -1,.75,.75))) + else: + plots.append((plot_2_d_polyhedral_complex(bkpt), (int(m/2), -1 ,.75,.75))) + return multi_graphics(plots) + + From fc2a99448af0db27288d59f4552050d4d6c6a4ea Mon Sep 17 00:00:00 2001 From: Acadia Larsen <102884863+ComboProblem@users.noreply.github.com> Date: Tue, 14 Apr 2026 09:29:46 -0700 Subject: [PATCH 61/64] relocate FactorUndetermined to spam --- cutgeneratingfunctionology/igp/parametric.sage | 2 +- .../{shared => spam}/EvaluationExceptions.py | 0 .../spam/parametric_real_field_element.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename cutgeneratingfunctionology/{shared => spam}/EvaluationExceptions.py (100%) diff --git a/cutgeneratingfunctionology/igp/parametric.sage b/cutgeneratingfunctionology/igp/parametric.sage index 975ef9129..672c66dea 100644 --- a/cutgeneratingfunctionology/igp/parametric.sage +++ b/cutgeneratingfunctionology/igp/parametric.sage @@ -20,7 +20,7 @@ from cutgeneratingfunctionology.spam.basic_semialgebraic_local import BasicSemia from cutgeneratingfunctionology.spam.semialgebraic_mathematica import BasicSemialgebraicSet_mathematica, from_mathematica from cutgeneratingfunctionology.spam.basic_semialgebraic_groebner_basis import BasicSemialgebraicSet_groebner_basis from cutgeneratingfunctionology.spam.polyhedral_complex import PolyhedralComplex -from cutgeneratingfunctionology.shared.EvaluationExceptions import FactorUndetermined +from cutgeneratingfunctionology.EvaluationExceptions import FactorUndetermined from .parametric_family import Classcall, ParametricFamily_base, ParametricFamily import logging diff --git a/cutgeneratingfunctionology/shared/EvaluationExceptions.py b/cutgeneratingfunctionology/spam/EvaluationExceptions.py similarity index 100% rename from cutgeneratingfunctionology/shared/EvaluationExceptions.py rename to cutgeneratingfunctionology/spam/EvaluationExceptions.py diff --git a/cutgeneratingfunctionology/spam/parametric_real_field_element.py b/cutgeneratingfunctionology/spam/parametric_real_field_element.py index f9defcbb0..dd2f8647c 100644 --- a/cutgeneratingfunctionology/spam/parametric_real_field_element.py +++ b/cutgeneratingfunctionology/spam/parametric_real_field_element.py @@ -9,7 +9,7 @@ from sage.rings.real_mpfr import RR from sage.functions.other import ceil, floor from sage.functions.generalized import sign -from cutgeneratingfunctionology.shared.EvaluationExceptions import FactorUndetermined +from cutgeneratingfunctionology.EvaluationExceptions import FactorUndetermined import operator From 1560108add45e57b7f1ba8a487f683d2854781ce Mon Sep 17 00:00:00 2001 From: Acadia Larsen <102884863+ComboProblem@users.noreply.github.com> Date: Tue, 14 Apr 2026 10:00:29 -0700 Subject: [PATCH 62/64] finish linting --- .../igp/minimal_function_cell_description.sage | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage index f41e21038..a23ed16d3 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage @@ -687,7 +687,7 @@ class PiMinContContainer: minimal_funciton_cell_description_logger.warning(f"This may take a while.") minimal_funciton_cell_description_logger.info("Finished generating breakpoints.") minimal_funciton_cell_description_logger.info("Computing repersentative elements.") - self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) + self._data = find_minimal_function_reps_from_bkpts(bkpts, backend=self._backend) except ImportError: if self._n > 4: minimal_funciton_cell_description_logger.warning(f"This may take a while. Try installing the minimalFunctionCache.") @@ -832,7 +832,7 @@ def plot_2_d_polyhedral_complex(bkpt, highlight_index=-1, highlight_color='blue' else: p += line([(0, bkpt_ext[i]), (bkpt_ext[i], 0)], color='grey') for i in range(1,n): - if i == highlight_index: + if i == highlight_index: p += line([(bkpt_ext[i], 1), (1, bkpt_ext[i])], color=highlight_color, linestyle='dashed') else: p += line([(bkpt_ext[i], 1), (1, bkpt_ext[i])], color='grey') @@ -841,7 +841,7 @@ def plot_2_d_polyhedral_complex(bkpt, highlight_index=-1, highlight_color='blue' p += plot(bkpt_ext[i], (0, 1), color=highlight_color, linestyle='dashed') else: p += plot(bkpt_ext[i], (0, 1), color='grey') - y=var('y') + y = var('y') for i in range(n): if i == highlight_index: p += parametric_plot((bkpt_ext[i],y), (y,0,1), color=highlight_color, linestyle='dashed') @@ -852,7 +852,7 @@ def plot_2_d_polyhedral_complex(bkpt, highlight_index=-1, highlight_color='blue' def plot_2_d_polyhedral_complex_and_descendants(bkpt, max_row_length=5, max_number_additional_diagrams=15, **kwds): r""" - Returns a plot of 2-d polyehdral complex of the breakpoint sequence of length n, Complex Delta P_bkpt + Returns a plot of 2-d polyehdral complex of the breakpoint sequence of length n, Complex Delta P_bkpt together with plots of Complex :math:`Delta P_{bkpt \cup \lambda^*}` where Complex Delta P_bkpt is a subcomplex of :math:`Delta P_{bkpt \cup \lambda^*}`. @@ -860,14 +860,14 @@ def plot_2_d_polyhedral_complex_and_descendants(bkpt, max_row_length=5, max_numb sage: from cutgeneratingfunctiology.igp import * sage: plot_2_d_polyhedral_complex_and_descendants([0, 1/2]) # not tested """ - n = len(bkpt) + n = len(bkpt) n_plus_one_breakpoints = unique_list(make_rep_bkpts_with_len_n(n+1, k=n, bkpts=[bkpt])) number_of_new_diagrams = len(n_plus_one_breakpoints) m = max_row_length i = 0 plots = [] while i < max_number_additional_diagrams and i < number_of_new_diagrams: - plots.append((plot_2_d_polyhedral_complex(list(n_plus_one_breakpoints[i]), n, **kwds), (i%m , int(i/m) ,.75,.75))) + plots.append((plot_2_d_polyhedral_complex(list(n_plus_one_breakpoints[i]), n, **kwds), (i % m , int(i/m) ,.75,.75))) i += 1 if i < m: plots.append((plot_2_d_polyhedral_complex(bkpt), (int(i/2), -1,.75,.75))) @@ -875,4 +875,3 @@ def plot_2_d_polyhedral_complex_and_descendants(bkpt, max_row_length=5, max_numb plots.append((plot_2_d_polyhedral_complex(bkpt), (int(m/2), -1 ,.75,.75))) return multi_graphics(plots) - From 1d589c1f145162746f1f9e51d3558a138fe4e5ab Mon Sep 17 00:00:00 2001 From: Acadia Larsen <102884863+ComboProblem@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:45:43 -0700 Subject: [PATCH 63/64] fix imports --- cutgeneratingfunctionology/igp/parametric.sage | 2 +- .../spam/parametric_real_field_element.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cutgeneratingfunctionology/igp/parametric.sage b/cutgeneratingfunctionology/igp/parametric.sage index 672c66dea..a8f7b3b47 100644 --- a/cutgeneratingfunctionology/igp/parametric.sage +++ b/cutgeneratingfunctionology/igp/parametric.sage @@ -20,7 +20,7 @@ from cutgeneratingfunctionology.spam.basic_semialgebraic_local import BasicSemia from cutgeneratingfunctionology.spam.semialgebraic_mathematica import BasicSemialgebraicSet_mathematica, from_mathematica from cutgeneratingfunctionology.spam.basic_semialgebraic_groebner_basis import BasicSemialgebraicSet_groebner_basis from cutgeneratingfunctionology.spam.polyhedral_complex import PolyhedralComplex -from cutgeneratingfunctionology.EvaluationExceptions import FactorUndetermined +from cutgeneratingfunctionology.spam.EvaluationExceptions import FactorUndetermined from .parametric_family import Classcall, ParametricFamily_base, ParametricFamily import logging diff --git a/cutgeneratingfunctionology/spam/parametric_real_field_element.py b/cutgeneratingfunctionology/spam/parametric_real_field_element.py index dd2f8647c..c6ba3bfa7 100644 --- a/cutgeneratingfunctionology/spam/parametric_real_field_element.py +++ b/cutgeneratingfunctionology/spam/parametric_real_field_element.py @@ -9,7 +9,7 @@ from sage.rings.real_mpfr import RR from sage.functions.other import ceil, floor from sage.functions.generalized import sign -from cutgeneratingfunctionology.EvaluationExceptions import FactorUndetermined +from cutgeneratingfunctionology.spam.EvaluationExceptions import FactorUndetermined import operator From 11657f27cad9fc13d42f6ce2716665e32d309251 Mon Sep 17 00:00:00 2001 From: Acadia Larsen <102884863+ComboProblem@users.noreply.github.com> Date: Tue, 5 May 2026 10:16:47 -0700 Subject: [PATCH 64/64] minor updates --- .../igp/minimal_function_cell_description.sage | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage index a23ed16d3..591216fad 100644 --- a/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage +++ b/cutgeneratingfunctionology/igp/minimal_function_cell_description.sage @@ -6,7 +6,7 @@ import logging import os minimal_funciton_cell_description_logger = logging.getLogger("cutgeneratingfunctionology.igp.minimal_funciton_cell_description") -minimal_funciton_cell_description_logger.setLevel(logging.DEBUG) +minimal_funciton_cell_description_logger.setLevel(logging.INFO) ### Note to future reader: ### ### bkpt is assumed to be a breakpoint sequence of length n>= 2. @@ -15,16 +15,11 @@ minimal_funciton_cell_description_logger.setLevel(logging.DEBUG) ### This is never strictly enforced in this file and it is assumed that ### the user is always provided a breakpoint sequence. -# global defaults for logging from portions of igp; change to log different parts of igp. -log_paramateric_real_field = False -log_pw_functions = False - - class RepElemGenFailure(Exception): pass -def nnc_poly_from_bkpt_sequence(bkpt, backend=None): +def nnc_poly_from_bkpt_sequence(bkpt, backend=None, log_paramateric_real_field = False,log_pw_functions = False): r""" Defines an NNC polyhedron P such that for all b in P the delta complex of b is isomoprhic to the delta complex of bkpt. @@ -223,7 +218,7 @@ def generate_assumed_symmetric_vertices_continuous(fn, f, bkpt): fn(x) + fn(y) == 1 yield (x, y, 0, 0) -def value_nnc_polyhedron_value_cords(bkpt, f_index, backend=None): +def value_nnc_polyhedron_value_cords(bkpt, f_index, backend=None, log_paramateric_real_field = False, log_pw_functions = False): r""" For a given breakpoints sequence and f index, write the value polyhedron as a basic semialgebraic set in only the value parameters. @@ -280,7 +275,7 @@ def value_nnc_polyhedron_value_cords(bkpt, f_index, backend=None): logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) return K._bsa -def value_nnc_polyhedron(bkpt, f_index, backend=None): +def value_nnc_polyhedron(bkpt, f_index, backend=None, log_paramateric_real_field = False,log_pw_functions = False): r""" For a given breakpoints sequence and f index, write the value polyhedron as a basic semialgebraic set in the full space of parameters. @@ -342,7 +337,7 @@ def value_nnc_polyhedron(bkpt, f_index, backend=None): logging.getLogger("cutgeneratingfunctionology.igp.functions").setLevel(pw_logging_level) return K._bsa -def bsa_of_rep_element(bkpt, vals, backend=None): +def bsa_of_rep_element(bkpt, vals, backend=None, log_paramateric_real_field = False,log_pw_functions = False): r""" Given pi_(bkpt, vals) is {minimal, not minimal}, find BSA subset of R^(2n) such that (bkpt, vals) in BSA and for all p in BSA, pi_p is {minimal, not minimal}.