Example #1
0
 def is_Cn_symmetric(self):
     if not self.is_equal(self.isomorphic_NAC_coloring(self.omega),
                          moduloConjugation=False):
         return False
     if len(self.blue_subgraph().subgraph(
             flatten(
                 self._partially_invariant_components['red'])).edges()) > 0:
         return False
     if len(self.red_subgraph().subgraph(
             flatten(self._partially_invariant_components['blue'])).edges()
            ) > 0:
         return False
     return True
Example #2
0
    def deduce(assumes, eqts):
        """
        Examples

        sage: logger.set_level(VLog.DEBUG)

        sage: var('r a b q y x'); IeqDeduce.deduce([r>=2*b],[b==y*a,x==q*y+r])
        (r, a, b, q, y, x)
        dig_polynomials:Debug:* deduce(|assumes|=1,|eqts|=2)
        dig_polynomials:Debug:assumed ps: r >= 2*b
        [r >= 2*a*y, -q*y + x >= 2*b]

        sage: var('s n a t'); IeqDeduce.deduce([s<=n],[t==2*a+1,s==a**2+2*a+1])
        (s, n, a, t)
        dig_polynomials:Debug:* deduce(|assumes|=1,|eqts|=2)
        dig_polynomials:Debug:assumed ps: s <= n
        [a^2 + 2*a + 1 <= n]

        """
        logger.debug('* deduce(|assumes|={},|eqts|={})'.format(
            len(assumes), len(eqts)))
        logger.debug('assumed ps: {}'.format(', '.join(map(str, assumes))))

        combs = [(aps, ei) for aps in assumes for ei in eqts
                 if any(x in get_vars(aps) for x in get_vars(ei))]

        sols = [IeqDeduce.substitute(e1, e2) for e1, e2 in combs]
        sols = flatten(sols, list)

        return sols
    def deduce(assumes ,eqts):
        """
        Examples

        sage: logger.set_level(VLog.DEBUG)

        sage: var('r a b q y x'); IeqDeduce.deduce([r>=2*b],[b==y*a,x==q*y+r])
        (r, a, b, q, y, x)
        dig_polynomials:Debug:* deduce(|assumes|=1,|eqts|=2)
        dig_polynomials:Debug:assumed ps: r >= 2*b
        [r >= 2*a*y, -q*y + x >= 2*b]

        sage: var('s n a t'); IeqDeduce.deduce([s<=n],[t==2*a+1,s==a**2+2*a+1])
        (s, n, a, t)
        dig_polynomials:Debug:* deduce(|assumes|=1,|eqts|=2)
        dig_polynomials:Debug:assumed ps: s <= n
        [a^2 + 2*a + 1 <= n]

        """
        logger.debug('* deduce(|assumes|={},|eqts|={})'
                     .format(len(assumes),len(eqts)))
        logger.debug('assumed ps: {}'.format(', '.join(map(str,assumes))))

        combs = [(aps,ei) for aps in assumes for ei in eqts
                 if any(x in get_vars(aps) for x in get_vars(ei))]

        sols= [IeqDeduce.substitute(e1,e2) for e1,e2 in combs] 
        sols = flatten(sols,list)

        return sols
    def motion_types2active_NACs(self, motion_types):
        r"""
        Return the active NAC-colorings for given motion types, if uniquely determined.
        """
        zeros, eqs = self.consequences_of_nonnegative_solution_assumption(
            flatten([self.ramification_formula(c, motion_types[c]) for c in motion_types]))

        if self._ring_ramification.ideal(eqs).dimension()==1:
            return [delta for delta in self._graph.NAC_colorings() if not self.mu(delta) in zeros]
        else:
            raise NotImplementedError('There might be more solutions (dim '+str(
                self._ring_ramification.ideal(eqs).dimension()) + ')')
Example #5
0
    def substitute(e1, e2):
        """
        Examples:

        sage: var('x t q b y a')
        (x, t, q, b, y, a)

        sage: IeqDeduce.substitute(t-2*b>=0,x==q*y+t)
        [-q*y - 2*b + x >= 0]

        sage: IeqDeduce.substitute(t-2*b>=0,b-y*a==0)
        [-2*a*y + t >= 0]

        sage: IeqDeduce.substitute(t-2*b>=0,b-4==0)
        [t - 8 >= 0]

        sage: IeqDeduce.substitute(t-2*b>=0,b+4==0)
        [t + 8 >= 0]

        sage: IeqDeduce.substitute(t-2*b>=0,b^2+4==0)
        [t + 4*I >= 0, t - 4*I >= 0]

        sage: IeqDeduce.substitute(t-2*b>=0,b^2-4==0)
        [t + 4 >= 0, t - 4 >= 0]

        #todo: cannot do when e2 is not equation
        sage: IeqDeduce.substitute(2*b>=0,b>=5)
        dig_polynomials:Warn:substitution fails on b >= 5
        2*b >= 0

        sage: IeqDeduce.substitute(2*b==0,b>=5)
        dig_polynomials:Warn:substitution fails on b >= 5
        2*b == 0
        """

        e1_vs = get_vars(e1)
        e2_vs = get_vars(e2)

        rs = [
            solve(e2, e2_v, solution_dict=True) for e2_v in e2_vs
            if e2_v in e1_vs
        ]

        rs = flatten(rs)

        try:
            rs = [e1.subs(rs_) for rs_ in rs]
            return rs
        except Exception:
            logger.warn('substitution fails on {}'.format(e2))
            return e1
Example #6
0
    def grid_coordinates(self, ordered_red=[], ordered_blue=[]):
        r"""
        Return coordinates for the grid construction.
        
        The optional parameters `ordered_red`, `ordered_blue` can be used to specify the order of components to be taken.

        See [GLS2018]_ for the description of the grid construction.
        
        TODO:
        
        test
        """
        pos = {}
        red_comps = self.red_components()
        blue_comps = self.blue_components()
        if ordered_red:
            if type(ordered_red) != list or len(ordered_red) != len(
                    red_comps) or Set(flatten(ordered_red)) != Set(
                        self._graph.vertices()):
                raise ValueError(
                    '`ordered_red` must be a list of all red components, not '
                    + str(ordered_red))
            red_comps = ordered_red
        if ordered_blue:
            if type(ordered_blue) != list or len(
                    ordered_blue) != len(blue_comps) or Set(
                        flatten(ordered_blue)) != Set(self._graph.vertices()):
                raise ValueError(
                    '`ordered_blue` must be a list of all blue components, not '
                    + str(ordered_blue))
            blue_comps = ordered_blue
        for (i, red) in enumerate(red_comps):
            for (j, blue) in enumerate(blue_comps):
                for v in blue:
                    if v in red:
                        pos[v] = (i, j)
        return pos
    def substitute(e1,e2):
        """
        Examples:

        sage: var('x t q b y a')
        (x, t, q, b, y, a)

        sage: IeqDeduce.substitute(t-2*b>=0,x==q*y+t)
        [-q*y - 2*b + x >= 0]

        sage: IeqDeduce.substitute(t-2*b>=0,b-y*a==0)
        [-2*a*y + t >= 0]

        sage: IeqDeduce.substitute(t-2*b>=0,b-4==0)
        [t - 8 >= 0]

        sage: IeqDeduce.substitute(t-2*b>=0,b+4==0)
        [t + 8 >= 0]

        sage: IeqDeduce.substitute(t-2*b>=0,b^2+4==0)
        [t + 4*I >= 0, t - 4*I >= 0]

        sage: IeqDeduce.substitute(t-2*b>=0,b^2-4==0)
        [t + 4 >= 0, t - 4 >= 0]

        #todo: cannot do when e2 is not equation
        sage: IeqDeduce.substitute(2*b>=0,b>=5)
        dig_polynomials:Warn:substitution fails on b >= 5
        2*b >= 0

        sage: IeqDeduce.substitute(2*b==0,b>=5)
        dig_polynomials:Warn:substitution fails on b >= 5
        2*b == 0
        """

        e1_vs = get_vars(e1)
        e2_vs = get_vars(e2)

        rs = [solve(e2,e2_v,solution_dict=True)
              for e2_v in e2_vs if e2_v in e1_vs]

        rs = flatten(rs)

        try:
            rs = [e1.subs(rs_) for rs_ in rs]
            return rs
        except Exception:
            logger.warn('substitution fails on {}'.format(e2))
            return e1
Example #8
0
    def solve(self): #FlatArray
        
        #Create new variables and traces
        logger.info('Compute new traces (treating array elems as new vars)')
        ainfo  = {}  #{A_0_0=[0,0],B_1_2_3=[1,2,3]}
        tsinfo = {} #{A: [A_0,A_1, ..], B:[B_0_0,B_0_1,..]}
        print self.tcs
        
        tcs = [FlatArray.compute_new_trace(tc, ainfo, tsinfo) 
               for tc in self.tcs]

        ps = self.get_rels_elems1(tcs)
        #ps = self.get_rels_elems2(tcs,tsinfo)
        
        #Group arrays and in each group, find rels among array idxs 
        gs = FlatArray.group_arr_eqts(ps, ainfo)
        logger.info('Partition {} eqts into {} groups'
                    .format(len(ps), len(gs)))
        
        sols = []
        for i,(gns,gps) in enumerate(gs.iteritems()):
            if __debug__:
                assert not is_empty(gps)
                
            # Modify/reformat if necessary        
            gps = FlatArray.modify_arr_eqts(gps, ainfo)
           
            #Find rels over array indices
            logger.debug("{}. Find rels over idx from {} eqts (group {})"
                         .format(i,len(gps),gns))
            gps = [FlatArray.parse_arr_eqt(p,ainfo) for p in gps]
            gsols = FlatArray.find_rels(gps)
            
            sols.extend(gsols)

        
        if is_empty(sols):
            logger.warn('No rels found over arr idxs, use orig results')
            sols = flatten(ps)
            self.sols = map(InvEqt, sols)
            self.do_refine = False
        else:
            self.sols = map(InvFlatArray, sols)

        self.print_sols()
Example #9
0
    def K23Graph():
        r"""
        Return the graph $K_{2,3}$.

        EXAMPLES::

            sage: from flexrilog import GraphGenerator, FlexRiGraph
            sage: FlexRiGraph(graphs.CompleteBipartiteGraph(2,3)).is_isomorphic(GraphGenerator.K23Graph())
            True

        .. PLOT::
            :scale: 70

            from flexrilog import GraphGenerator
            G = GraphGenerator.K23Graph()
            sphinx_plot(G)
        """
        K23 = FlexRiGraph([(1, 2), (1, 4), (3, 2), (3, 4), (5, 2), (5, 4)],
                          name='K23',
                          pos={
                              4: (0, 1),
                              5: (1, 0),
                              3: (0.00, 0.000),
                              2: (0, -1),
                              1: (-1, 0)
                          })
        for delta in K23.NAC_colorings():
            for edges in [delta.red_edges(), delta.blue_edges()]:
                if len(edges) == 2:
                    delta.set_name('alpha' +
                                   str(edges[0].intersection(edges[1])[0]))
                elif len(edges) == 3:
                    if Set(edges[0]).intersection(Set(edges[1])).intersection(
                            Set(edges[2])):
                        delta.set_name('gamma')
                    else:
                        for e in edges:
                            if not e.intersection(
                                    Set(
                                        flatten([
                                            list(e2) for e2 in edges if e2 != e
                                        ]))):
                                delta.set_name('beta' + str(e[0]) + str(e[1]))
        return K23
 def motion_types2equations(self, motion_types,
                                        active_NACs=None,
                                        groebner_basis=True,
                                        extra_eqs=[]):
     r"""
     Return equations enforced by edge lengths and singleton active NAC-colorings.
     """
     if active_NACs==None:
         active_NACs = self.motion_types2active_NACs(motion_types)
     
     eqs_same_lengths = self.motion_types2same_lengths_equations(motion_types)
     eqs = flatten([self.equations_from_leading_coefs(delta, check=False,
                                              extra_eqs=eqs_same_lengths + extra_eqs)
                 for delta in active_NACs if delta.is_singleton(active_NACs)
             ])
     if groebner_basis:
         return ideal(eqs).groebner_basis()
     else:
         return eqs
Example #11
0
    def get_beta_marks(self, alpha_inv_s):
        if not self.G.has_marks():
            #print "G has no marks!"
            yield dict()
            return
    
        inv_marks_perms_list = []
        Gmarks = []
        for vG,  alpha_inv_vG in zip(self.G.vertices(), alpha_inv_s):
            inv_marks = []
            for vA in alpha_inv_vG:
                inv_marks += self.A.marks_on_v(vA)
            
            vGmarks = self.G.marks_on_v(vG)    
            Gmarks += vGmarks
            if len(inv_marks) != len(vGmarks):
                return 
            if len(vGmarks) == 0:
                continue
            
            inv_marks_perms = []
            #print "inv_marks", inv_marks
            for p in Permutations(inv_marks):
                bad = False
                for i,j in zip(vGmarks,p):
                    if i[1] != j[1]:
                        bad = True
                        break
                if not bad:
                    inv_marks_perms.append(list(p))
                    #print "inv_marks_perm", inv_marks_perms
            
            if len(inv_marks_perms) > 0:
                inv_marks_perms_list.append(inv_marks_perms)
            else:
                return       
                
        #print "made this",Gmarks, inv_marks_perms_list

                
        for p in itertools.product(*inv_marks_perms_list):
            pf = flatten(p,max_level=1)
            yield {i[0]:j[0] for i,j in zip(Gmarks, pf)}
Example #12
0
    def K33Graph():
        r"""
        Return the graph $K_{3,3}$.

        EXAMPLES::

            sage: from flexrilog import GraphGenerator, FlexRiGraph
            sage: FlexRiGraph(graphs.CompleteBipartiteGraph(3,3)).is_isomorphic(GraphGenerator.K33Graph())
            True

        .. PLOT::
            :scale: 70

            from flexrilog import GraphGenerator
            G = GraphGenerator.K33Graph()
            sphinx_plot(G)
        """
        K33 = FlexRiGraph(
            [(1, 2), (1, 4), (1, 6), (3, 2), (3, 4), (3, 6), (5, 2), (5, 4),
             (5, 6)],
            name='K33',
            pos={
                4: (0.500, 0.866),
                5: (-0.500, 0.866),
                6: (-1.00, 0.000),
                3: (1.00, 0.000),
                2: (0.500, -0.866),
                1: (-0.500, -0.866)
            })
        for delta in K33.NAC_colorings():
            for edges in [delta.red_edges(), delta.blue_edges()]:
                if len(edges) == 3:
                    delta.set_name('omega' + str(edges[0].intersection(
                        edges[1]).intersection(edges[2])[0]))
                elif len(edges) == 5:
                    for e in edges:
                        if not e.intersection(
                                Set(
                                    flatten(
                                        [list(e2)
                                         for e2 in edges if e2 != e]))):
                            delta.set_name('epsilon' + str(e[0]) + str(e[1]))
        return K33
Example #13
0
    def get_beta_marks(self, alpha_inv_s):
        if not self.G.has_marks():
            #print "G has no marks!"
            yield dict()
            return

        inv_marks_perms_list = []
        Gmarks = []
        for vG, alpha_inv_vG in zip(self.G.vertices(), alpha_inv_s):
            inv_marks = []
            for vA in alpha_inv_vG:
                inv_marks += self.A.marks_on_v(vA)

            vGmarks = self.G.marks_on_v(vG)
            Gmarks += vGmarks
            if len(inv_marks) != len(vGmarks):
                return
            if len(vGmarks) == 0:
                continue

            inv_marks_perms = []
            #print "inv_marks", inv_marks
            for p in Permutations(inv_marks):
                bad = False
                for i, j in zip(vGmarks, p):
                    if i[1] != j[1]:
                        bad = True
                        break
                if not bad:
                    inv_marks_perms.append(list(p))
                    #print "inv_marks_perm", inv_marks_perms

            if len(inv_marks_perms) > 0:
                inv_marks_perms_list.append(inv_marks_perms)
            else:
                return

        #print "made this",Gmarks, inv_marks_perms_list

        for p in itertools.product(*inv_marks_perms_list):
            pf = flatten(p, max_level=1)
            yield {i[0]: j[0] for i, j in zip(Gmarks, pf)}
Example #14
0
File: psort.py Project: LMFDB/lmfdb
def ZpX_key(k):
    return lambda f: [f.degree()] + flatten(zip(*[padded_list(c,k) for c in f.list()]))
Example #15
0
def to_pols_normalized(ll):
    pols = [R(to_pol(l)) for l in ll]
    l = flatten([a.coefficients() for a in pols])
    idl = K.ideal(l)
    a = idl.gens_reduced()[0]
    return [p / a for p in pols]
Example #16
0
    def __init__(self, *args, **kwargs):
        if not args:
            raise ValueError('no arguments provided')
        elif len(args) > 2:
            raise ValueError('too many arguments provided')

        # The first argument can be one of three things.

        # (1) Another CicoDatum.
        try:
            cd = args[0]
            self.nvertices = cd.nvertices
            self.edges = sort_edges(cd.edges)
            self.weights = list(cd.weights)
            self.signs = list(cd.signs)
            self.polyhedron = cd.polyhedron
            self.cone = cd.cone
            return
        except AttributeError:
            pass

        # (2) A non-negative integer indicating the number of vertices.
        try:
            nvertices = int(args[0])
            edges = sort_edges(args[1]) if len(args) == 2 else []
        except TypeError:
            # (3) A Sage graph.
            # NOTE: existing edge labels of the graph are quietly ignored!
            G = args[0]
            try:
                V = G.vertices()
                E = G.edges()
            except AttributeError:
                raise ValueError('input should be a graph')
            nvertices = len(V)
            I = list(range(nvertices))
            edges = sort_edges([(V.index(u), V.index(v)) for u, v, _ in E])

        if any(a not in range(nvertices) for a in flatten(edges)):
            raise ValueError('invalid edges [indices]')

        if any(len(e) not in [1, 2] for e in edges):
            raise ValueError('invalid edges [counts]')

        edges = [(e[0], e[1]) if len(e) == 2 else (e[0], e[0]) for e in edges]

        signs = kwargs.get('signs', -1)
        if signs in [+1, -1]:
            signs = len(edges) * [signs]

        if any(s not in [+1, -1] for s in signs):
            raise ValueError('invalid signs')

        weights = kwargs.get('weights', len(edges) * [(ZZ**nvertices)(0)])
        if any(w not in (ZZ**nvertices) for w in weights):
            raise ValueError('invalid weights')

        if not (len(edges) == len(weights) == len(signs)):
            raise ValueError('mismatch between edges, weights, and signs')

        polyhedron = kwargs.get('polyhedron', PositiveOrthant(nvertices))
        cone = conify_polyhedron(polyhedron)

        # Confirm that the weight axiom of WSMs is satisfied.
        I = identity_matrix(ZZ, nvertices)
        if any(not belongs_to_dual(I[i] + weights[j], cone)
               for j, e in enumerate(edges) for i in set(e)):
            raise ValueError('the weights and the polyhedron are incompatible')

        self.nvertices = nvertices
        self.edges = edges
        self.weights = list(weights)
        self.signs = list(signs)
        self.polyhedron = polyhedron
        self.cone = cone
    def equations_from_leading_coefs(self, delta, extra_eqs=[], check=True):
        r"""
        Return equations for edge lengths from leading coefficients system.

        EXAMPLES::

            sage: from flexrilog import GraphGenerator, MotionClassifier
            sage: K33 = GraphGenerator.K33Graph()
            sage: M = MotionClassifier(K33)
            sage: M.equations_from_leading_coefs('epsilon56')
            [lambda1_2^2 - lambda1_4^2 - lambda2_3^2 + lambda3_4^2]

        ::

            sage: M.equations_from_leading_coefs('omega1')
            Traceback (most recent call last):
            ...
            ValueError: The NAC-coloring must be a singleton.

        ::

            sage: M.equations_from_leading_coefs('omega1', check=False)
            [lambda2_5^2*lambda3_4^2 - lambda2_5^2*lambda3_6^2 - lambda2_3^2*lambda4_5^2 + lambda3_6^2*lambda4_5^2 + lambda2_3^2*lambda5_6^2 - lambda3_4^2*lambda5_6^2]
        """

        if type(delta) == str:
            delta = self._graph.name2NAC_coloring(delta)

        if check:
            if not delta.is_singleton():
                raise exceptions.ValueError('The NAC-coloring must be a singleton.')
        eqs_lengths=[]
        for e in self._graph.edges():
            eqs_lengths.append(self._z(e)*self._w(e) - self._lam(e)**_sage_const_2)


        eqs_w=[]
        eqs_z=[]
        for T in self._graph.spanning_trees():
            for e in self._graph.edges():
                eqw = 0
                eqw_all = 0
                eqz = 0
                eqz_all = 0
                path = T.shortest_path(e[0],e[1])
                for u,v in zip(path, path[1:]+[path[0]]):
                    if delta.is_red(u,v):
                        eqz+=self._z([u,v])
                    else:
                        eqw+=self._w([u,v])
                    eqw_all+=self._w([u,v])
                    eqz_all+=self._z([u,v])
                if eqw:
                    eqs_w.append(eqw)
                else:
                    eqs_w.append(eqw_all)
                if eqz:
                    eqs_z.append(eqz)
                else:
                    eqs_z.append(eqz_all)

        equations = (ideal(eqs_w).groebner_basis()
                     + ideal(eqs_z).groebner_basis()
                     + eqs_lengths
                     + [self._ringLC(eq) for eq in extra_eqs])
        return [self._ring_lambdas(eq)
                for eq in ideal(equations).elimination_ideal(flatten(
                    [[self._w(e), self._z(e)] for e in self._graph.edges()])).basis
                ]
    def possible_motion_types_and_active_NACs(self,
                                              comments = {},
                                              show_table=True,
                                              one_representative=True,
                                              tab_rows=False,
                                              keep_orth_failed=False,
                                              equations=False):
        r"""
        Wraps the function for consistent motion types, conditions on orthogonality of diagonals and splitting into equivalence classes.
        """
        types = self.consistent_motion_types()
        classes = self.motion_types_equivalent_classes(types)
        valid_classes = []
        
        motions = [ 'g','a','p','d']
        if one_representative:
            header = [['index', '#', 'motion types'] + motions + ['active NACs', 'comment']]
        else:
            header = [['index', '#', 'elem.', 'motion types'] + motions + ['active NACs', 'comment']]
        if equations:
            header[0].append('equations')
        rows = []
        for i, cls in enumerate(classes):
            rows_cls = []
            to_be_appended = True
            for j, t in enumerate(cls):
                row = [i, len(cls)]
                if not one_representative:
                    row.append(j)
                row.append(' '.join([t[c] for c in self.four_cycles_ordered()]))
                row += [Counter([('d' if s in ['e','o'] else s) for c, s in t.items()])[m] for m in motions]
                try:
                    active = self.active_NAC_coloring_names(t)
                    row.append([self.mu(name) for name in sorted(active)])
                    if self.check_orthogonal_diagonals(t, active):
                        row.append(comments.get(i,''))
                    else:
                        to_be_appended = False
                        if not keep_orth_failed:
                            continue
                        else:
                            row.append('orthogonality check failed' + str(comments.get(i,'')))
                            
                except NotImplementedError as e:
                    zeros, eqs = self.consequences_of_nonnegative_solution_assumption(
                        flatten([self.ramification_formula(c, t[c]) for c in t]))
                    row.append([eq for eq in eqs if not eq in zeros])
                    row.append(str(comments.get(i,'')) + str(e))
                    
                if equations:
                    zeros, eqs = self.consequences_of_nonnegative_solution_assumption(
                        flatten([self.ramification_formula(c, t[c]) for c in t]))
                    row.append([eq for eq in eqs if not eq in zeros])
                    
                rows_cls.append(row)
                
                if one_representative:
                    break
            if to_be_appended or keep_orth_failed:
                valid_classes.append(cls)
                if one_representative:
                    rows += rows_cls
                else:
                    rows.append(rows_cls)
        if show_table:
            if one_representative:
                T = table(header + rows)
            else:
                T = table(header + [row for rows_cls in rows for row in rows_cls])
            T.options()['header_row'] = True
            display(T)

        if tab_rows:
            return valid_classes, rows
        return valid_classes
Example #19
0
def ZpX_key(k):
    return lambda f: [f.degree()] + flatten(
        list(zip(*[padded_list(c, k) for c in f.list()])))
Example #20
0
    def add(self, S):
        if self._file.closed:
            raise SURFError('Lost access to the SURFSum file')

        self._count += 1

        # Binary format:
        # k scalar a[0] b[0] a[1] b[1] ... a[k-1] b[k-1]

        # NOTE:
        # In rare cases, we can run into numbers that don't fit into 32-bit
        # integers. We then use an ad hoc hack to reduce to smaller numbers.
        # To trigger this, count ideals in L(6,11).

        raw = list(map(int, [len(S.rays), S.scalar]))
        for a, b in S.rays:
            try:
                _ = array('i', [int(a), int(b)])
                raw.extend([int(a), int(b)])
            except OverflowError:
                if a:
                    raise SURFError('Ray does not fit into a pair of 32-bit integers')
            else:
                continue

            fac = factor(b)
            li = flatten([[p] * e for (p, e) in fac])
            li[0] *= fac.unit()
            # assert b == prod(li)
            if len(li) % 2 == 0:
                li[0] = -li[0]
            # assert -b == prod(-c for c in li)
            for c in li:
                raw.extend([int(0), int(c)])
            raw[0] += len(li) - 1

        try:
            self._file.write(array('i', raw).tobytes())
        except OverflowError:
            raise SURFError('Number too large to fit into a 32-bit integer')

        # Update the candidate denominator.
        E = {}
        for a, b in S.rays:
            if a == 0:
                continue

            self._critical.add(QQ(b) / QQ(a))

            # Only consider a*s-b with gcd(a,b) = 1 and a > 0.
            g = int(gcd((a, b)))
            a, b = a // g, b // g

            if a < 0:
                a, b = -a, -b

            # (possible, depending on the counting problem) TODO:
            # Get rid of things like s+1 (for subobjects) that cannot show up
            # in the final result.

            if (a, b) in E:
                E[(a, b)] += 1
            else:
                E[(a, b)] = 1

        # Now 'E' gives the multiplicities of candidate terms for S.
        # We take the largest multiplicities over all 'S'.
        for r in E:
            if r not in self._cand or self._cand[r] < E[r]:
                self._cand[r] = E[r]