\\ Copyright 2017, 2018, 2019, 2020, 2021, 2022 Kevin Ryde
\\
\\ vpar.gp is free software; you can redistribute it and/or modify it
\\ under the terms of the GNU General Public License as published by the Free
\\ Software Foundation; either version 3, or (at your option) any later
\\ version.
\\
\\ vpar.gp is distributed in the hope that it will be useful, but
\\ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
\\ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
\\ for more details.
\\
\\ You should have received a copy of the GNU General Public License along
\\ with vpar.gp. See the file COPYING. If not, see
\\ .
\\ http://user42.tuxfamily.org/pari-vpar/index.html
\\ Usage: read("vpar.gp");
\\ vpar = vpar_make_star(5);
\\ print(vpar_diameter(vpar));
\\
\\ This is some functions acting on labelled oriented trees represented by a
\\ vector vpar[1..n] of parent vertex numbers. addhelp() strings have docs
\\ of each function and see general notes in the comments below.
\\
\\ vpar_validate(vpar)
\\ bool = vpar_is_tree(vpar) num roots <= 1
\\ num = vpar_num_edges(vpar)
\\
\\ v = vpar_root(vpar) root vertex of tree
\\ vec = vpar_roots(vpar) root vertex numbers of forest
\\ num = vpar_num_roots(vpar)
\\ r = vpar_root_of_vertex(vpar,v)
\\ vec = vpar_root_of_vertex_vector(vpar)
\\
\\ num = vpar_num_rootings(vpar) possible sets of roots
\\ num = vpar_num_rootings_max(n)
\\ vec = vpar_roots_ith(vpar,i)
\\ vpar = vpar_reroot_ith(vpar,i)
\\
\\ vec = vpar_children_of_vertex(vpar,v) vertex numbers
\\ vec = vpar_children_vector(vpar) vector of vectors
\\ num = vpar_num_children_of_vertex(vpar,v)
\\ vec = vpar_num_children_vector(vpar)
\\ bool = vpar_is_num_children_vector(vec,forest=0)
\\ count = vpar_num_children_vectors_count(n,forest=0) distinct vecs
\\ vpar = vpar_make_num_children(vec) vpar of num children
\\ c = vpar_minmax_child_of_vertex(vpar,v,func)
\\ vec = vpar_minmax_child_vector(vpar,func)
\\
\\ bool = vpar_is_childful(vpar,v) has a child
\\ vec = vpar_is_childful_vector(vpar)
\\ vec = vpar_childful_vertices(vpar)
\\ num = vpar_num_childful(vpar)
\\ bool = vpar_is_childless(vpar,v) has no children
\\ vec = vpar_is_childless_vector(vpar)
\\ vec = vpar_childless_vertices(vpar)
\\ num = vpar_num_childless(vpar)
\\
\\ vec = vpar_siblings_of_vertex(vpar,v) siblings
\\ num = vpar_num_siblings_of_vertex(vpar,v)
\\ vec = vpar_num_siblings_vector(vpar)
\\ bool = vpar_is_num_siblings_vector(vec,forest=0)
\\ count = vpar_num_siblings_vectors_count(n,forest=0)
\\ vec = vpar_siblings_sets(vpar)
\\ num = vpar_num_siblings_sets(vpar)
\\ vec = vpar_siblings_setnum_vector(vpar)
\\
\\ pos = vpar_siblingpos_of_vertex(vpar,v,down=0)
\\ vec = vpar_siblingpos_vector(vpar,down=0)
\\ bool = vpar_is_siblingpos_vector(vec,down=0,forest=0)
\\ count = vpar_siblingpos_vectors_count(n,forest=0)
\\
\\ s = vpar_next_sibling_of_vertex(vpar,v,down=0) and previous sibling
\\ vec = vpar_next_sibling_vector(vpar,down=0)
\\ bool = vpar_is_next_sibling_vector(vec,down=0,forest=0)
\\ count = vpar_next_sibling_vectors_count(n,forest=0)
\\ vec = vpar_minmax_sibling_vector(vpar,func)
\\
\\ pos = vpar_rowpos_of_vertex(vpar,v,down=0)
\\ vec = vpar_rowpos_vector(vpar,down=0)
\\
\\ bool = vpar_is_neighbour(vpar,u,v)
\\ vec = vpar_neighbours_of_vertex(vpar,v) vertex numbers
\\ vec = vpar_neighbours_vector(vpar) vector of vectors
\\
\\ degree = vpar_degree_of_vertex(vpar,v) num neighbours
\\ vec = vpar_degrees_vector(vpar)
\\ bool = vpar_is_degrees_vector(vec,forest=0)
\\ count = vpar_degrees_vectors_count(n,forest=0)
\\
\\ bool = vpar_is_leaf(vpar,v) leaf, degree=1
\\ vec = vpar_leaves(vpar)
\\ num = vpar_num_leaves(vpar)
\\
\\ bool = vpar_is_singleton(vpar,v) isolated vertex
\\ vec = vpar_singletons(vpar) vertex numbers
\\ num = vpar_num_singletons(vpar)
\\ count = vpar_count_no_singletons(n)
\\
\\ count = vpar_num_claws(vpar)
\\ count = vpar_num_claws_total(n,forest=0)
\\
\\ set = vpar_parents_of_set(vpar,set)
\\ num = vpar_num_parents_of_set(vpar,set)
\\ set = vpar_children_of_set(vpar,set)
\\ num = vpar_num_children_of_set(vpar,set)
\\ set = vpar_neighbours_of_set(vpar,set)
\\ num = vpar_num_neighbours_of_set(vpar,set)
\\
\\ depth = vpar_depth_of_vertex(vpar,v) distance up to root
\\ [depth,root] = vpar_depth_and_root_of_vertex(vpar,v)
\\ vec = vpar_depths_vector(vpar)
\\ bool = vpar_is_depths_vector(vec,forest=0)
\\ count = vpar_depths_vectors_count(n,forest=0) distinct depths vecs
\\ bool = vpar_is_preorder_depths_vector(vec,forest=0)
\\ vpar = vpar_make_preorder_depths(depths)
\\
\\ bool = vpar_is_equaldepths(vpar) vpar == depths vector
\\ count = vpar_count_equaldepths(n,forest=0)
\\
\\ vec = vpar_horizpos_vector(vpar,down=0)
\\ bool = vpar_is_horizpos_vector(vec,forest=0)
\\ bool = vpar_is_preorder_horizpos_vector(vec,forest=0)
\\
\\ vec = vpar_diagdepths_vector(vpar,down=0)
\\
\\ b = vpar_to_balanced_binary(vpar) coding as preorder
\\ vpar = vpar_from_balanced_binary(b) preorder forest
\\ bool = vpar_is_balanced_binary(b)
\\ b = vpar_balanced_binary_next(b) iterate
\\ b = vpar_balanced_binary_prev(b)
\\
\\ height = vpar_height(vpar) maximum depth
\\ vec = vpar_height_vertices(vpar)
\\ num = vpar_height_num_vertices(vpar)
\\ vec = vpar_component_heights(vpar) each component of forest
\\ bool = vpar_is_component_heights(vec,n)
\\ num = vpar_height_num_components(vpar)
\\ count = vpar_count_height(n,h,forest=0) of height =h
\\ count = vpar_count_height_le(n,h,forest=0) of height <=h
\\ height2 = vpar_2height(vpar)
\\
\\ height = vpar_subtree_height(vpar,v) height under v
\\ vec = vpar_subtree_heights_vector(vpar)
\\ bool = vpar_is_subtree_heights_vector(vec,forest=0)
\\ count = vpar_subtree_heights_vectors_count(n,forest=0) distinct vecs
\\ count = vpar_count_subtree_heights_increasing(n,forest=0)
\\
\\ num = vpar_subtree_size(vpar,v) at and below v
\\ vec = vpar_subtree_sizes_vector(vpar)
\\ vec = vpar_subtree_vertices(vpar,v) subtree vertex numbers
\\ vpar = vpar_subtree(vpar,v) subtree as new vpar
\\
\\ num = vpar_subtree_num_childless(vpar,v) at and below v
\\ vec = vpar_subtree_num_childless_vector(vpar)
\\ vec = vpar_subtree_childless_vertices(vpar,v)
\\
\\ descendant = vpar_minmax_descendant_of_vertex(vpar,v,func)
\\ vec = vpar_minmax_descendants_vector(vpar,func) below each v
\\ count = vpar_count_min_descendant_increasing(n,forest=0)
\\
\\ vec = vpar_ancestors(vpar,v)
\\ bool = vpar_is_ancestor(vpar,v,ancestor)
\\ ancestor = vpar_minmax_ancestor_of_vertex(vpar,v,func)
\\ vec = vpar_minmax_ancestors_vector(vpar,func)
\\ count = vpar_count_min_ancestor_increasing(n,forest=0)
\\
\\ width = vpar_row_width_of_vertex(vpar,v) num vertices at depth
\\ vec = vpar_row_widths_vector(vpar)
\\ vec = vpar_row_vertices_vector(vpar) vector of vectors
\\
\\ ecc = vpar_eccentricity_of_vertex(vpar,v)
\\ vec = vpar_eccentricities_vector(vpar)
\\ bool = vpar_is_eccentricities_vector(vec)
\\ count = vpar_eccentricities_vectors_count(n)
\\
\\ diam = vpar_diameter(vpar)
\\ [diam,count] = vpar_diameter_and_count(vpar)
\\ num = vpar_diameter_num_endpoints(vpar)
\\ num = vpar_diameter_num_vertices(vpar)
\\ radius = vpar_radius(vpar)
\\ vec = vpar_centres(vpar) middle(s) of diameter
\\ vec = vpar_centres_of_forest(vpar) middle(s) of all component diams
\\ W = vpar_Wiener_index(vpar,terminal=0)
\\ num = vpar_total_cophenetic(vpar)
\\
\\ num = vpar_path_length(vpar,u,v)
\\ vec = vpar_path_lengths_vector(vpar,v)
\\ m = vpar_path_lengths_matrix(vpar)
\\ vec = vpar_path_vertices(vpar,u,v)
\\ c = vpar_common_ancestor(vpar,u,v)
\\
\\ weight = vpar_weight_of_vertex(vpar,v) biggest neighbour subtree size
\\ vec = vpar_weights_vector(vpar)
\\ vec = vpar_centroids(vpar)
\\ [vec,weight] = vpar_centroids_and_weight(vpar)
\\
\\ num = vpar_protnum(vpar)
\\ num = vpar_protnum_of_vertex(vpar,v)
\\ bool = vpar_is_protected_vertex(vpar,v,k)
\\ vec = vpar_protnums_vector(vpar)
\\ bool = vpar_is_protnums_vector(vec,forest=0)
\\ count = vpar_protnums_vectors_count(n,forest=0)
\\
\\ bool = vpar_is_leastchildmonk(vpar) root least child childless
\\ count = vpar_count_leastchildmonk(n,forest=0)
\\ count = vpar_count_leastchildmonk_and_rootmin(n,forest=0)
\\ bool = vpar_is_hereditaryleastsingle(vpar) all least children childless
\\ count = vpar_count_hereditaryleastsingle(n,forest=0)
\\
\\ num = vpar_num_components(vpar)
\\ vec = vpar_compnum_vector(vpar) component numbers in forest
\\ vec = vpar_component_sizes(vpar)
\\ vec = vpar_component_vertices(vpar) vector of vectors
\\ vec = vpar_component_trees(vpar) vector of vpar
\\ vec = vpar_component_minmax(vpar,func) vertex numbers
\\ bool = vpar_is_root_minmax(vpar,func)
\\ vec = vpar_component_random_vertices(vpar) vertex from each component
\\
\\ bool = vpar_is_strongly_monotone_forest(vpar)
\\ count = vpar_count_strongly_monotone_forests(n)
\\ count = vpar_strongly_monotone_partitions_count(n)
\\
\\ m = vpar_adjacency_matrix(vpar) m[u,v]=1 neighbours
\\ poly = vpar_adjacency_poly(vpar,var='x) characteristic polynomial
\\ vpar = vpar_from_adjacency_matrix(m)
\\
\\ m = vpar_skew_adjacency_matrix(vpar) m[u,v]=+/-1 parent,child
\\ poly = vpar_skew_adjacency_poly(vpar,var='x)
\\ vpar = vpar_from_skew_adjacency_matrix(m)
\\
\\ m = vpar_Laplacian_matrix(vpar)
\\ poly = vpar_Laplacian_poly(vpar,var='x)
\\ m = vpar_incidence_matrix(vpar) vertices and edges
\\ m = vpar_reachability_matrix(vpar)
\\
\\ bool = vpar_is_indset(vpar,set) independent set
\\ bool = vpar_is_indset_flags(vpar,flags)
\\ vec = vpar_indsets(vpar)
\\ count = vpar_indsets_count(vpar)
\\ poly = vpar_indpoly(vpar,var='x) independence polynomial
\\ num = vpar_indnum(vpar) independence number
\\ [num,count] = vpar_indnum_and_count(vpar)
\\ vec = vpar_indnum_sets(vpar)
\\
\\ bool = vpar_is_matching_set(vpar,set) matching
\\ bool = vpar_is_matching_flags(vpar,flags)
\\ vec = vpar_matchsets(vpar)
\\ count = vpar_matchings_count(vpar) number of matchings
\\ poly = vpar_matchpoly(vpar,var='x) match polynomial
\\ num = vpar_matchnum(vpar) matching number
\\ [num,count] = vpar_matchnum_and_count(vpar)
\\
\\ bool = vpar_has_perfect_matching(vpar,near=0)
\\ count = vpar_count_perfect_matching(n,near=0,forest=0)
\\
\\ bool = vpar_is_maximal_matching_set(vpar,set)
\\ bool = vpar_is_maximal_matching_flags(vpar,flags)
\\ count = vpar_maximal_matchings_count(vpar)
\\ poly = vpar_maximal_matchpoly(vpar,var='x)
\\
\\ bool = vpar_is_equimatchable(vpar) maximals=matchnum
\\ bool = vpar_is_stable_equimatchable(vpar) still on edge removal
\\ bool = vpar_is_almost_equimatchable(vpar) 2 sizes maximals
\\
\\ bool = vpar_is_domset(vpar,set) dominating set
\\ bool = vpar_is_domset_flags(vpar,flags)
\\ vec = vpar_domsets(vpar)
\\ count = vpar_domsets_count(vpar)
\\ poly = vpar_dompoly(vpar,var='x) domination polynomial
\\ num = vpar_domnum(vpar) domination number
\\ [num,count] = vpar_domnum_and_count(vpar)
\\ vec = vpar_domnum_sets(vpar)
\\
\\ bool = vpar_is_minimal_domset(vpar,set) minimal dominating sets
\\ bool = vpar_is_minimal_domset_flags(vpar,flags)
\\ vec = vpar_minimal_domsets(vpar)
\\ count = vpar_minimal_domsets_count(vpar)
\\ poly = vpar_minimal_dompoly(vpar,var='x) minimal domination polynomial
\\
\\ bool = vpar_is_indomset(vpar,set) independent dominating sets
\\ bool = vpar_is_indomset_flags(vpar,flags) (= maximal independent)
\\ vec = vpar_indomsets(vpar)
\\ count = vpar_indomsets_count(vpar)
\\ count = vpar_indomsets_count_max(n,forest=0)
\\ poly = vpar_indompoly(vpar,var='x) independent domination polynomial
\\ num = vpar_indomnum(vpar) independent domination number
\\ [num,count] = vpar_indomnum_and_count(vpar)
\\ vec = vpar_indomnum_sets(vpar)
\\
\\ bool = vpar_is_totdomset(vpar,set) total dominating set
\\ bool = vpar_is_totdomset_flags(vpar,flags)
\\ vec = vpar_totdomsets(vpar)
\\ count = vpar_totdomsets_count(vpar)
\\ poly = vpar_totdompoly(vpar,var='x) total domination polynomial
\\ num = vpar_totdomnum(vpar) total domination number
\\ [num,count] = vpar_totdomnum_and_count(vpar)
\\ vec = vpar_totdomnum_sets(vpar)
\\
\\ bool = vpar_is_semitotdomset(vpar,set) semi-total dominating set
\\ bool = vpar_is_semitotdomset_flags(vpar,flags)
\\ num = vpar_semitotdomnum(vpar) semi-total domination number
\\ [num,count] = vpar_semitotdomnum_and_count(vpar)
\\
\\ bool = vpar_is_perfect_domset(vpar,set) perfect dominating set
\\ bool = vpar_is_perfect_domset_flags(vpar,flags)
\\ count = vpar_perfect_domsets_count(vpar)
\\ poly = vpar_perfect_dompoly(vpar,var='x)
\\ num = vpar_perfect_domnum(vpar) perfect domination num
\\ [num,count] = vpar_perfect_domnum_and_count(vpar)
\\
\\ num = vpar_disjoint_domnum(vpar) disjoint domination
\\ [num,count] = vpar_disjoint_domnum_and_count(vpar)
\\
\\ bool = vpar_is_edgecover_flags(vpar,flags)
\\ bool = vpar_is_edgecover_set(vpar,set)
\\ count = vpar_edgecovers_count(vpar)
\\ poly = vpar_edgecovers_poly(vpar,var='x)
\\
\\ primecode = vpar_to_primecode(vpar) tree coding of Matula and Goebel
\\ vec = vpar_to_primecode_vector(vpar)
\\ vpar = vpar_from_primecode(primecode)
\\
\\ vpar = vpar_join(vpar,v,sub_vpar) attach tree under v
\\ vpar = vpar_concat_forest(vpar1,vpar2)
\\ vpar = vpar_concat_forests(vpars)
\\ vpar = vpar_add_root_above_forest(vpar)
\\ vpar = vpar_delete_vertex(vpar,v)
\\ vpar = vpar_delete_vertices(vpar,vec)
\\ vpar = vpar_delete_subtree(vpar,v) delete v and below v
\\ vpar = vpar_keep_vertices(vpar,vec)
\\ vpar = vpar_keep_flags(vpar,flags)
\\
\\ vpar = vpar_subdivide(vpar,s=1) insert vertices into edges
\\ vpar = vpar_contract_vertex(vpar,v) delete and parent adopt children
\\ vpar = vpar_branch_reduce(vpar) contract non-root degree=2s
\\ vpar = vpar_free_branch_reduce(vpar) contract all degree=2s
\\ vpar = vpar_reverse_cycles(vpar)
\\ vpar = vpar_transpose(vpar,opp=0) swap first child / next sibling
\\
\\ vpar = vpar_reroot(vpar,v)
\\ vpar = vpar_reroot_forest(vpar,roots)
\\ vpar = vpar_reroot_minmax(vpar,func)
\\ vpar = vpar_reroot_random(vpar)
\\
\\ vpar = vpar_relabel_perm(vpar,perm)
\\ vpar = vpar_relabel_seq(vpar,seq)
\\ vpar = vpar_relabel_swap(vpar,u,v)
\\ vpar = vpar_relabel_reverse(vpar)
\\ vpar = vpar_relabel_preorder(vpar,down=0)
\\ vpar = vpar_relabel_postorder(vpar,down=0)
\\ vpar = vpar_relabel_random(vpar)
\\
\\ bool = vpar_is_isomorphic(vpar1,vpar2) rooted tree isomorphism
\\ perm = vpar_isomorphic_perm(vpar1,vpar2)
\\ num = vpar_num_isomorphic(vpar)
\\ vpar = vpar_to_unlabelled(vpar) canonical form
\\ vpar = vpar_to_lexmin(vpar)
\\
\\ bool = vpar_is_free_isomorphic(vpar1,vpar2) free tree isomorphism
\\ perm = vpar_free_isomorphic_perm(vpar1,vpar2)
\\ num = vpar_num_free_isomorphic(vpar)
\\ vpar = vpar_add_roots_above_centres(vpar)
\\ vpar = vpar_to_free(vpar)
\\
\\ bool = vpar_is_ordered_isomorphic(vpar1,vpar2) ordered tree isomorphism
\\ perm = vpar_ordered_isomorphic_perm(vpar1,vpar2)
\\
\\ bool = vpar_is_automorphism(vpar,perm) no-change perm
\\ vec = vpar_automorphisms(vpar)
\\ num = vpar_num_automorphisms(vpar)
\\ vec = vpar_automorphism_generators(vpar)
\\ bool = vpar_is_automorphisms_le(vpar,m,t=m) <=m siblings, t in nest
\\ vec = vpar_automorphism_ids_vector(vpar) subtree type ids
\\ num = vpar_num_automorphism_ids(vpar)
\\ group = vpar_automorphism_group(vpar) Pari small group
\\
\\ bool = vpar_is_asymmetric(vpar) only identity automorphism
\\ count = vpar_count_asymmetric(n,forest=0)
\\ count = vpar_count_unlabelled_asymmetric(n,forest=0)
\\ vec = vpar_count_unlabelled_asymmetric_vector(n,forest=0)
\\
\\ vec = vpar_orbit_sets(vpar) "similar" vertices
\\ vec = vpar_orbit_sizes(vpar)
\\ vec = vpar_orbitnum_vector(vpar)
\\ num = vpar_num_orbits(vpar)
\\
\\ bool = vpar_is_free_automorphism(vpar,perm) neighbours no-change
\\ vec = vpar_free_automorphisms(vpar)
\\ num = vpar_num_free_automorphisms(vpar)
\\ vec = vpar_free_automorphism_generators(vpar)
\\
\\ bool = vpar_is_free_asymmetric(vpar)
\\ count = vpar_count_free_asymmetric(n,forest=0)
\\
\\ vec = vpar_free_orbit_sets(vpar)
\\ vec = vpar_free_orbit_sizes(vpar)
\\ vec = vpar_free_orbitnum_vector(vpar)
\\ num = vpar_num_free_orbits(vpar)
\\
\\ vpar = vpar_unlabelled_first(n) unlabelled iteration
\\ vpar = vpar_unlabelled_last(n,forest=0)
\\ vpar = vpar_unlabelled_next(vpar,forest=0)
\\ bool = vpar_unlabelled_nextR(~vpar,forest=0)
\\ vpar = vpar_unlabelled_prev(vpar,forest=0)
\\ bool = vpar_is_unlabelled(vpar)
\\
\\ vpar = vpar_free_first(n) free tree iteration
\\ vpar = vpar_free_last(n)
\\ vpar = vpar_free_next(vpar)
\\
\\ bool = vpar_is_preorder(vpar) labelled in preorder
\\ bool = vpar_is_postorder(vpar) labelled in postorder
\\ vpar = vpar_preorder_first(n) ordered iteration
\\ vpar = vpar_preorder_last(n,forest=0)
\\ vpar = vpar_preorder_next(vpar,forest=0)
\\ bool = vpar_preorder_nextR(~vpar,forest=0)
\\ vpar = vpar_preorder_prev(vpar,forest=0)
\\ vpar = vpar_preorder_ith(n,i,forest=0) indexed access
\\ i = vpar_preorder_to_i(vpar,forest=0)
\\
\\ bool = vpar_is_upwards(vpar) labelled child < parent
\\ bool = vpar_is_downwards(vpar) labelled child > parent
\\ vpar = vpar_downwards_ith(n,i,forest=0)
\\ i = vpar_downwards_to_i(vpar,forest=0)
\\
\\ vpar = vpar_make_path(n) construct some trees
\\ vpar = vpar_make_star(n)
\\ vpar = vpar_make_bistar(n,m)
\\ vpar = vpar_make_binomial_tree(n)
\\ vpar = vpar_make_complete_tree(c,h)
\\ vpar = vpar_make_random(n,forest=0)
\\ vpar = vpar_make_random_preorder(n,forest=0)
\\ vpar = vpar_make_random_downwards(n,forest=0)
\\
\\ vpar = vpar_make_caterpillar(vec)
\\ bool = vpar_is_caterpillar(vpar)
\\
\\ seq = vpar_seq_preorder(vpar,down=0) vertex sequences
\\ seq = vpar_seq_postorder(vpar,down=0)
\\ seq = vpar_seq_rows(vpar,up=0) breadth-first
\\ seq = vpar_seq_siblings(vpar)
\\ seq = vpar_seq_premax(vpar) pre-order lex-max
\\ seq = vpar_seq_lexmin(vpar)
\\
\\ seq = vpar_seq_upascend(vpar,ahead=0) childless ascending
\\ vpar = vpar_from_upascend(vec,ahead=0) (Prufer, Neville)
\\ seq = vpar_seq_upqueue(vpar) becoming childless
\\ vpar = vpar_from_upqueue(vec) (Deo,Micikevicius)
\\ seq = vpar_seq_downqueue(vpar)
\\ seq = vpar_seq_diagqueue(vpar,down=0)
\\ seq = vpar_seq_subtree_height(vpar,down=0) leaf phases
\\ vpar = vpar_from_subtree_height(vec,down=0) (Neville)
\\ seq = vpar_seq_subtree_size(vpar,down=0)
\\ vpar = vpar_from_subtree_size(vec,down=0)
\\ seq = vpar_seq_num_children(vpar,down=0)
\\ vpar = vpar_from_num_children(vec,down=0)
\\ seq = vpar_seq_successive_paths(vpar,down=0) Fleiner
\\ vpar = vpar_from_successive_paths(vec,down=0)
\\ seq = vpar_seq_childless_paths(vpar,down=0) Knuth exercise
\\ vpar = vpar_from_childless_paths(vec,down=0)
\\ seq = vpar_seq_blob(vpar) Orlin, Kreweras and Moszkowski
\\ vpar = vpar_from_blob(vec)
\\
\\ vec = vpar_forvec_from(n,forest=0) loop over extracts
\\ bool = vpar_seq_is_upwards(vpar,seq)
\\ bool = vpar_seq_is_downwards(vpar,seq)
\\ bool = vpar_seq_is_ordered(vpar,seq,down=0)
\\
\\ count = vpar_count(n,forest=0) number of vpar
\\ count = vpar_count_unlabelled(n,forest=0) unlabelled but rooted
\\ vec = vpar_count_unlabelled_vector(n,forest=0)
\\ count = vpar_count_unrooted(n,forest=0) unrooted but still labelled
\\ count = vpar_count_free(n,forest=0) unlabelled and unrooted
\\ vec = vpar_count_free_vector(n,forest=0)
\\ count = vpar_count_ordered(n,forest=0) ordered and rooted
\\ count = vpar_count_updown(n,forest=0) up or down labelled
\\ count = vpar_count_contiguous_forests(n) with contiguous components
\\
\\ v = vpar_random_vertex(vpar)
\\ flags = vpar_random_flags(vpar)
\\ set = vpar_random_set(vpar)
\\ set = vpar_set_complement(vpar,set)
\\
\\ str = vpar_to_graph6(vpar) string
\\ str = vpar_to_sparse6(vpar)
\\ str = vpar_to_digraph6(vpar,down=0)
\\
\\ bool = vpar_is_cyclic(vpar) usually should not be cyclic
\\ num = vpar_num_cycles(vpar)
\\ bool = vpar_is_cyclic_vertex(vpar,v) v in a cycle
\\ vec = vpar_cyclic_vertices(vpar)
\\ num = vpar_num_cyclic_vertices(vpar)
\\ vec = vpar_component_cycle_lengths(vpar)
\\ bool = vpar_is_bipartite(vpar) no odd cycle
\\
\\
\\ Vpar Representation
\\ -------------------
\\
\\ Vertices are numbered v = 1 to n. vpar is a vector of n entries.
\\ vpar[v] is the parent of v, or 0 if v has no parent (tree root).
\\ An empty vpar=[] is a tree of no vertices.
\\
\\ 3
\\ ^ ^ tree, root=3
\\ / \
\\ 1 5 vpar=[3,1,0,1,3]
\\ ^ ^
\\ / \
\\ 2 4
\\
\\ vpar can be a forest, meaning multiple trees each with their own root.
\\
\\ 3 4 forest with 2 component trees
\\ ^ ^ ^
\\ / \ | vpar=[3,4,0,0,3]
\\ 1 5 2
\\
\\ In the function descriptions, "forest" means all acyclic vpars, including
\\ trees reckoned as 1-component forests. Some functions such as
\\ eccentricity, diameter, or Wiener index, are only meaningful on a tree (a
\\ single component forest).
\\
\\ In the function names and descriptions, "upwards" and "downwards" are
\\ based on root at the top and down to children. So upwards is towards the
\\ root and downwards is away (and greater depth). See "Upwards and
\\ Downwards" below for trees labelled this way.
\\
\\ | root ^
\\ | / \ |
\\ downwards child child upwards
\\ | | | |
\\ v grandchild grandchild |
\\
\\
\\ Cycles
\\ ------
\\
\\ vpar[] should not have any cycles in its parent-child relations. Most
\\ functions won't notice a cycle and some may loop endlessly. Those
\\ allowing cycles say so in their addhelp. vpar_validate() can check no
\\ cycles and check parent numbers in range. Parent numbers not in range
\\ usually show as a vector index error when used, but vpar_validate() is a
\\ better error message.
\\
\\ A vector with entries anywhere in the range 0..n is sometimes called a
\\ "functional digraph" since it represents a directed graph with one edge
\\ leaving each vertex, like a function f(v)=p has one value out for each
\\ input v. This is a digraph of out degree <= 1. One output each vertex
\\ means a connected component has at its top either a root or a cycle
\\ (possibly a self-loop where v is its own parent), hence
\\
\\ vpar_num_components(vpar) == vpar_num_roots(vpar) + vpar_num_cycles(vpar)
\\
\\
\\ Rooted and Labelled
\\ -------------------
\\
\\ A vpar tree is oriented (or rooted) in the sense that it has edges
\\ directed upwards to a designated root. Any vertex number can be the
\\ root. The root or roots are where vpar[v]==0.
\\
\\ vpar_reroot() can change edge directions to make a different vertex the
\\ root. It leaves each vertex with the same set of neighbouring vertex
\\ numbers, but some may switch from parent to child. For example the
\\ sample tree 3,1,0,1,3 above with vertex 1 made the root is
\\
\\ 1 tree rerooted to root=1
\\ ^^^
\\ / | \ vpar=[0,1,1,1,3]
\\ 2 3 4
\\ ^
\\ |
\\ 5
\\
\\ A vpar tree is labelled in the sense that each vertex has a number.
\\ The vpar_relabel_() functions can change the numbers. Relabelling keeps
\\ the same structure of edge orientations and roots, but changes the vertex
\\ numbers assigned. See "Unlabelled Trees" below on vpars differing only
\\ in their labels, and see "Automorphisms" below on relabellings which are
\\ no change.
\\
\\ When building a tree out of parts, it can be helpful to keep target
\\ vertices as particular numbers in the tree, ready for further attachment,
\\ calculating distances, etc. vpar_relabel_swap() is good for that. It
\\ brings a vertex to the desired number by swapping whatever had been there.
\\
\\ Various calculations such as diameter, centroid, independence number,
\\ etc, are the same for any root and any labelling. For them, the
\\ labelling and parents just specify tree structure and can be anything
\\ convenient to construct etc.
\\
\\
\\ Root=1 Trees
\\ ------------
\\
\\ Various functions take a "forest" parameter which can be -1 to test or
\\ count trees with root vertex fixed at v=1. This is understood as "root
\\ first" so empty vpar=[] is reckoned as root=1 too.
\\
\\ Fixing the root can represent a labelled but unrooted tree, insofar as
\\ each vertex has a certain set of neighbours and it is represented in a
\\ rooted tree by fixing the root as root=1. See "Unrooted Trees" below.
\\
\\ In the count functions, the number of root=1 trees with some property is
\\ often the same as number of forests of n-1 vertices with that property,
\\ by being a forest n-1 below the root=1.
\\
\\
\\ Component Trees
\\ ---------------
\\
\\ "Component" functions act on the connected components of a forest.
\\ vpar_component_vertices() and various others reckon components in order
\\ of the smallest vertex number in each component.
\\
\\ forest 2 3 7
\\ [2,0,0,7,2,3,0,7] / \ | / \
\\ 1 5 6 4 8
\\
\\ component number: 1 2 3
\\ min vertex: 1 3 4
\\
\\ vpar_component_trees() splits a forest into separate tree vpars. This
\\ loses original vertex numbering, but gives components ready to consider
\\ separately.
\\
\\ For a tree or forest, the number of components is equal to the number of
\\ roots. For a cyclic vpar, each cyclic component has no root, hence total
\\ roots plus cycles described under "Cycles" above. Various component
\\ functions allow cyclic vpar.
\\
\\
\\ Vector Types
\\ ------------
\\
\\ vpar vectors created by vpar_make_...() are ordinary Vec() vectors.
\\ Parent vertex numbers are small enough to use Vecsmall(), but Pari/GP
\\ circa 2.7 through 2.13 has limited support for Vecsmalls in operations
\\ like select() or apply(), and they can't be compared "==" to ordinary
\\ vectors, which is inconvenient.
\\
\\ Inputs as Vecsmall() work in various functions here. Don't want to
\\ guarantee that, but would like it when no other consideration, mainly for
\\ the benefit of fitting more in memory when trying very big trees.
\\
\\ Vecsmall() is a good way to save memory when accumulating many trees or
\\ vectors in a List() or Map(). Lookups by setsearch() or mapget() of
\\ Vecsmall keys can be faster than Vec keys too. But it might be necessary
\\ to Vec() them up again after extracting from such storage.
\\
\\
\\ Vertex Sequences
\\ ----------------
\\
\\ "vpar_seq_" functions return a Vecsmall containing the vertex numbers
\\ 1..#vpar in some order. The type is Vecsmall since they are a
\\ permutation of those integers and Vecsmall is the usual representation
\\ for a permutation. Vecsmall allows perm*perm multiply for permutation
\\ composition, perm^-1 for inverse, etc.
\\
\\ Some sequences go "upwards" in the sense that a child occurs before its
\\ parent (vpar_seq_is_upwards()). An algorithm wanting to work upwards in
\\ the tree can use any of the upwards sequences. If siblings are wanted in
\\ ascending numerical order too then vpar_seq_preorder() or vpar_seq_rows().
\\
\\ my(seq=vpar_seq_upascend(vpar));
\\ for(i=1,#seq,
\\ my(v=seq[i]);
\\ print("vertex v="v));
\\
\\ Some sequences go "downwards" in the sense that a parent occurs before
\\ its children (vpar_seq_is_downwards()). An upwards sequence reversed is
\\ downwards, or vice versa.
\\
\\ Working upwards in a vpar can also be done "explicitly" by following for
\\ example the Prufer coding (up from childless while v==max_child[p] and
\\ v
= vpar_is_isomorphic(vpar1,vpar2)
\\
\\ vpar_add_roots_above_centres() can split centres and add roots to turn a
\\ free tree into a (bigger) rooted tree. Free isomorphism of the original
\\ is rooted isomorphism of the transformed.
\\
\\ Free trees can be iterated with vpar_free_first() and vpar_free_next() to
\\ get a representative tree (or forest) from each equivalence class, using
\\ the algorithm of Wright, Richmond, Odlyzko and McKay. This is a
\\ sub-sequence of the vpar_unlabelled_next() iteration, so is pre-order,
\\ lex-max, descending lex, but taking only certain "primary" members to be
\\ free representatives.
\\
\\ vpar_to_free() relabels and reroots a tree to the free primary form.
\\ Equality after this means vpar_is_free_isomorphic() of the originals,
\\ so it can be used to unduplicate or to collect trees of same free
\\ structure. Note the free primary form is currently only for trees, not
\\ forests, so vpar_to_free() is trees only.
\\
\\ The free tree generation by Li and Ruskey, which is used in the Nauty
\\ gentreeg program, is another way to iterate free trees. It gives the
\\ same primaries, but in different sequence (when n>=5). gentreeg can
\\ emit vpar format which is suitable for use here after a light massage.
\\ See devel/gentreeg-to-try.pl for rough code doing that.
\\
\\
\\ Ordered Trees
\\ -------------
\\
\\ A rooted ordered tree has a particular order among each set of siblings.
\\ In a forest, roots are reckoned siblings of each other and are ordered.
\\ Vpar vertex numbers specify such an order.
\\
\\ Two vpar trees or forests are isomorphic as ordered trees when some
\\ relabelling which preserves sibling order makes them equal vectors.
\\ vpar_seq_is_ordered() tests for an order preserving seq. Relabelling to
\\ such a sequence gives new vertex numbers to each set of siblings, but
\\ their relative order is the same as before.
\\
\\ vpar_is_ordered_isomorphic(vpar1,vpar2) tests for ordered isomorphism.
\\ vpar_ordered_isomorphic_perm() returns a suitable order-preserving
\\ permutation for relabelling. vpar_count_ordered() is the number of
\\ ordered trees (or forests), which is the number of equivalence classes
\\ arising from ordered isomorphism. This is the Catalan numbers.
\\
\\ The restriction to relabellings which preserve sibling order means less
\\ opportunity for equivalence so
\\
\\ vpar_is_isomorphic(vpar1,vpar2)
\\ >= vpar_is_ordered_isomorphic(vpar1,vpar2)
\\
\\ vpar_relabel_preorder() or vpar_relabel_postorder() are canonical
\\ order-preserved labellings. Ordered isomorphism is equality of those
\\ forms, so they can be used as keys for unduplicating etc.
\\
\\ vpar_to_balanced_binary() is a canonical integer of 2*n bits (a Dyck word
\\ coded into bits) for vpar as ordered forest. vpar_preorder_to_i() is
\\ also a canonical integer index (among forests of the given number of
\\ vertices) and is smaller than balanced binary, but balanced binary should
\\ be a little faster to calculate. As n grows, the index i approaches 2*n
\\ bits anyway since Catalan numbers C(n) grow roughly as 4^n.
\\
\\ vpar_preorder_next() iterates through pre-order trees or forests. It
\\ goes by lex() decreasing depths vector, which is also lex() decreasing
\\ vpar vector since depths vector order is the same as vpar vector order on
\\ preorder trees. The unlabelled and free iterations described above are
\\ sub-sequences of this (same order, but taking only some of the elements).
\\
\\ Ordered trees are sometimes called "plane". This refers to the way
\\ ordering forces the embedding (drawing) in the plane, but this is unclear
\\ and discouraged since all trees are planar graphs and it's the ordering
\\ which is of interest.
\\
\\
\\ Binary Trees
\\ ------------
\\
\\ Binary trees are defined as each vertex having a left and right child,
\\ one or both possibly absent (empty subtrees).
\\
\\ * 1 1 4 6
\\ / \ / \ / \ | |
\\ left right 2 4 2 3 5 7
\\ \ / \
\\ 3 5 6 ordered forest
\\ / natural correspondence
\\ 7
\\ binary tree
\\
\\ There's nothing specific here for such trees because in vpar an only
\\ child doesn't have a notion of being left or right. However the "natural
\\ correspondence" to ordered forests might be of use,
\\
\\ binary left child = ordered forest first child
\\ binary right child = ordered forest next sibling
\\
\\ Pre-order traversals are the same in binary tree or natural
\\ correspondence ordered forest (so vpar_to_balanced_binary() the same).
\\ In-order traversal of the binary tree is post-order traversal of the
\\ ordered forest. The count of binary trees is count of ordered forests
\\ vpar_count_ordered(n,1) = Catalan numbers.
\\
\\ Various measures on a binary tree will have corresponding measure in the
\\ forest, of greater or lesser naturalness. One binary tree measure is the
\\ "weights" vector of
\\
\\ J. M. Pallo, "Enumerating, Ranking and Unranking Binary Trees",
\\ The Computer Journal, volume 29, number 2, 1986, pages 171-175.
\\ http://academic.oup.com/comjnl/article/29/2/171/460530
\\
\\ This is vpar_subtree_sizes_vector() of post-order labelled forest.
\\
\\ Binary trees are also sometimes reckoned filled out with "external" nodes
\\ at otherwise empty left or right positions. Externals might be target
\\ data records of a binary search tree, or merely the "nil" of Lisp cons
\\ cells. In any case the result is everywhere either 0 or 2 children.
\\
\\ 1
\\ / \
\\ 2 4 binary tree from above
\\ / \ / \ with "external" vertices
\\ e 3 5 6
\\ / \ / \ / \
\\ e e e e 7 e
\\ / \
\\ e e
\\
\\ Assigning vertex numbers to the externals gives a vpar tree with normal
\\ operations. Some sort of convention could be made in the numbering to
\\ distinguish externals more easily than vpar_childless_vertices() etc.
\\ For example Knuth (see examples/random-decorated.gp) goes even and odd,
\\ but only so 2-apart can be an index into a vector where a pair of entries
\\ are a cons cell.
\\
\\
\\ Unrooted Trees
\\ --------------
\\
\\ A few functions treat vpar as labelled but unrooted. The undirected
\\ vpar_adjacency_matrix() and similar do this by considering vertex labels
\\ and edge adjacency, but ignoring edge direction.
\\
\\ vpar_reroot_minmax() gives a canonical rooting, and thus canonical edge
\\ directions. Equality is equivalence as labelled unrooted.
\\ vpar_count_unrooted() is the number of labelled unrooted trees or forests.
\\ vpar_num_rootings() is how many ways to reroot a given tree or forest, so
\\ how many vpar are equivalent as unrooted (including itself). Unrooted
\\ trees can be represented by root=1 as per "Root=1 Trees" above.
\\
\\
\\ Upwards and Downwards Trees
\\ ---------------------------
\\
\\ A tree or forest is labelled "downwards" when vertex numbers increase
\\ going downwards, so everywhere parent vertex number < child vertex number.
\\ Conversely for labelled "upwards". Preorder trees are a subset of
\\ downwards, and postorder are a subset of upwards.
\\
\\ 1 7
\\ / \ downwards labelled, / \ upwards labelled,
\\ 2 4 increasing as go down, 4 6 increasing as go up,
\\ / \ \ vpar_is_downwards() / \ / \ vpar_is_upwards()
\\ 3 5 6 1 3 2 5
\\
\\ vpar_is_downwards() and vpar_is_upwards() test for such labelling.
\\ vpar_count_updown() is how many such trees or forests. This is factorial
\\ (n-1)! or n!. The count of upwards and of downwards are the same, simply
\\ by reversing vertex numbers (vpar_relabel_reverse()).
\\
\\ vpar_downwards_ith() and its inverse vpar_downwards_to_i() go from an
\\ index i to a tree or forest, in order of lexicographically descending
\\ vpar vector. vpar_make_random_downwards() is a random downwards tree or
\\ forest.
\\
\\ n! is the number of permutations of n things. A simple mapping between a
\\ permutation and a downwards forest can be made by taking each in a
\\ lexicographic order (as numtoperm() does),
\\
\\ \\ perm -> forest
\\ vpar = vpar_downwards_ith(#perm, permtonum(perm)+1, 1)
\\
\\ \\ forest -> perm
\\ perm = numtoperm(#vpar, vpar_downwards_to_i(vpar,1)-1)
\\
\\ Various authors give mappings which preserve or relate some property such
\\ as perm cycles or permorder(). Some arise from "parking functions".
\\ See examples/perm-to-upwards.gp for a mapping by Dennis Walsh where each
\\ perm cycle becomes a component tree. See examples/parking-to-labelled.gp
\\ which goes perm to upwards and extends for full parking to labelled.
\\
\\
\\ Automorphisms
\\ -------------
\\
\\ For a given vpar, vpar_relabel_perm() of some permutations may leave the
\\ vpar vector unchanged. Those permutations are automorphisms of vpar.
\\ The simplest no-change is swapping childless siblings. For example
\\
\\ 1 vpar=[0, 1,1]
\\ / \ perm=[1, 3,2]
\\ 2 3 vpar_relabel_perm(vpar,perm) == vpar \\ unchanged
\\
\\ Siblings with the same (meaning isomorphic) subtrees can have those whole
\\ subtrees swapped likewise. Such structure might have within it further
\\ possible swaps, etc. vpar_is_automorphism() tests a perm,
\\ vpar_num_automorphisms() counts perms, and vpar_automorphisms() gives a
\\ full list (possibly big).
\\
\\ Non-automorphism permutations give a different vpar vector on
\\ relabelling. Such another vpar is isomorphic to the original because
\\ reached by a relabelling (see "Unlabelled Trees" above).
\\ vpar_num_isomorphic(vpar) is the number of distinct vpar vectors
\\ isomorphic to a given vpar (including itself). This is related to
\\ automorphisms by
\\
\\ vpar_num_isomorphic(vpar) * vpar_num_automorphisms(vpar) == n!
\\
\\ This is because any permutation going to a different vpar vector can be
\\ multiplied by an automorphism too, so vpar_num_automorphisms() many perms
\\ go to each different vpar. See vpar_isomorphic_perm() addhelp on pre or
\\ post multiplying automorphism perms to get a full set of isomorphism perms.
\\
\\ "Free" automorphisms are when the rooting is ignored so
\\ vpar_relabel_perm() gives unchanged neighbour vertex numbers, but
\\ possibly swapped parent to child. This is vpar vector either unchanged
\\ (the rooted case like above) or changed only by a re-rooting. The number
\\ of free automorphisms and distinct free isomorphics are related again
\\
\\ vpar_num_free_isomorphic(vpar) * vpar_num_free_automorphisms(vpar) == n!
\\
\\ "Asymmetric" trees (or forests) have no automorphisms except the identity
\\ permutation. Likewise "free asymmetric" no free automorphisms.
\\ vpar_is_asymmetric() and vpar_is_free_asymmetric() test for this, and
\\ vpar_count_asymmetric() etc is the number of such trees or forests.
\\
\\
\\ Automorphism Orbits
\\ -------------------
\\
\\ Two vertices u,v are "similar" when some automorphism maps perm[u]==v.
\\ Similarity is an equivalence relation since the inverse perm^-1[v]==u is
\\ also an automorphism, and since multiplying automorphism perms
\\ (composition) is also an automorphism so transitive map u->v->w.
\\ The equivalence class comprising a set of mutually similar vertices is an
\\ automorphism "orbit".
\\
\\ 1 orbits (rooted and free
\\ / | \ same in this example)
\\ 2 3 4 [1] [2,3] [4]
\\ /|\ /|\ | \ [5,6,7,8,9,10] [11,12]
\\ 5 6 7 8 9 10 11 12
\\
\\ Roughly speaking, vertices in an orbit have the rest of the tree (or
\\ forest) structure looking the same from their perspective (just different
\\ labels). For free automorphism orbits, this is same number of neighbours
\\ and same structures working outward. For rooted automorphism orbits, the
\\ directions to the root are respected, so same number and structure
\\ children, same depth, and same rest of tree structure above.
\\
\\ Childless siblings are always together in an orbit, and also possibly
\\ together with other sets of corresponding childless from a same higher
\\ subtree. In the example above, 5,6,7 are together and also 8,9,10 since
\\ the 2 and 3 subtrees are the same. Siblings with same subtree structures
\\ are together in an orbit such as 2,3 above, and again possibly yet
\\ further same sibling subtrees above might put them together with others.
\\ For a free tree this becomes leaf vertices together, same neighbour
\\ branches, etc.
\\
\\ For a free tree, vertices u,v are similar precisely when rooting at
\\ either of them gives the same (isomorphic) rooted tree. The free
\\ automorphism which maps u to v is an isomorphism permutation between the
\\ rooted forms, and vice versa. (For similar vertices in a rooted tree, a
\\ corresponding notion would require distinguishing the existing root too.)
\\
\\ vpar_orbitnum_vector() is an orbit number for each vertex. Similarity is
\\ then orbitnum[u]==orbitnum[v]. vpar_orbit_sets() is the orbits as Set()s
\\ of vertices. vpar_free_orbitnum_vector() and vpar_free_orbit_sets() are
\\ for free automorphisms.
\\
\\ When testing or considering vertices in a tree, it might be enough to try
\\ just one from each orbit, knowing the tree or forest looks same (up to
\\ relabelling) from the perspective of the others in an orbit.
\\ See examples/isomorphic-halves.gp for a slightly rough example of that.
\\
\\
\\ Automorphism Groups
\\ -------------------
\\
\\ Two automorphism perms applied successively is also no change to vpar,
\\ so automorphisms are a group under permutation products perm1*perm2.
\\ (The usual GP multiplication of Vecsmall perms.)
\\
\\ vpar_automorphism_generators() and vpar_free_automorphism_generators()
\\ give generators for the group. Generators can be more use than a full
\\ list since the full list can be big, up to factorial (n-1)! tree or n!
\\ forest. The current code gives at most about n/2 generators, and often
\\ fewer.
\\
\\ Rooted automorphisms are a subset of free automorphisms, so a subgroup,
\\ and so vpar_num_free_automorphisms() is some multiple of
\\ vpar_num_automorphisms(). But, in the current code, generators found in
\\ each are unspecified and may be wildly different even though the rooted
\\ is a subgroup and even perhaps the same group.
\\
\\ Tree or forest automorphism groups are direct products and wreath
\\ products of symmetric groups S(n),
\\
\\ TreeGroup = trivial group
\\ or S(n) symmetric group
\\ or TreeGroup x TreeGroup direct product
\\ or TreeGroup wr S(n) wreath product
\\
\\ The symmetric group is where a set of n isomorphic sibling subtrees
\\ permute. If they have no symmetries below then just S(n). If symmetries
\\ below then copies of the group below permute, which is wreath product.
\\ Direct product is symmetries in separate parts of the tree or forest, not
\\ nested.
\\
\\
\\ Automorphism Small Group
\\ ------------------------
\\
\\ vpar_automorphism_group() and vpar_free_automorphism_group() give a Pari
\\ "small group". This form is described in galoisinit() and the Pari
\\ library manual "Small Groups". The form is limited to "weakly
\\ supersolvable" groups which means only some tree groups. Elements are
\\ numbered 1 .. num_automorphisms. Currently there's nothing to relate
\\ these numbers to the actual vpar automorphism perms.
\\
\\ The group facility can do things like find subgroups, form quotient
\\ groups, and export in GAP style. (Would vpar_automorphism_generators(),
\\ lightly massaged, also be enough for GAP too?) Most of the group
\\ facility requires install() of relevant Pari library functions.
\\
\\
\\ Independent Sets Etc
\\ --------------------
\\
\\ Various functions count or find independent sets, dominating sets,
\\ matchings, etc.
\\
\\ Individual set testing functions take a Set() of vertex numbers, so for
\\ example
\\
\\ vpar_is_indset(vpar_make_path(6), Set([1,5])) == 1
\\ vpar_is_domset(vpar_make_path(6), Set([1,5])) == 0
\\
\\ A Set() is simply a vector of vertex numbers sorted and unduplicated, so
\\ the Set() call is not needed when already that form.
\\
\\ The "flags" test functions take a vector of boolean flags 0,1 to indicate
\\ set membership.
\\
\\ vpar_is_indset_flags(vpar_make_path(6), [1,0,0,0,1,0]) == 1
\\ vpar_is_domset_flags(vpar_make_path(6), [1,0,0,0,1,0]) == 0
\\
\\ The flags forms can be used for iterating all sets with forvec().
\\
\\ vpar = vpar_make_path(6);
\\ forvec(flags=vector(#vpar,i,[0,1]), \
\\ if(vpar_is_indomset_flags(vpar,flags), print(flags)));
\\
\\ Range [0,1] is a vertex included or not, or [0,0] always exclude or [1,1]
\\ always include. Of course each both included or not is 2^n combinations,
\\ so suitable only for small trees or only a few varying vertices in a set.
\\
\\ Various set and size counting functions return polynomials where term
\\ c*x^n is count c of how many sets size n.
\\
\\ poly = vpar_indpoly(vpar)
\\ polcoeff(poly,0) = num independent sets of size 0 vertices
\\ polcoeff(poly,1) = num independent sets of size 1 vertices
\\ ...
\\
\\ Polynomials for this might seem a touch obscure, but they arise naturally
\\ as polynomial multiplication when accumulating subtree counts into parent
\\ counts. They also allow an n=0 term, whereas the same in a vector would
\\ need to adapt to 1-based vector indices.
\\
\\ Polynomials are used in the theory of spectra, which is what roots the
\\ polys have. For those polynomials which are related to matrices, these
\\ roots are the eigenvalues of the matrix. The plain count functions are
\\ currently implemented using the polynomial code with a constant 1 instead
\\ of variable 'x -- but don't rely on that yet.
\\
\\ The lists of sets like vpar_indsets() are based on joining sets from
\\ subtrees to sets at the parent. This is like the polynomial
\\ multiplication but set products, and concats to add. The counting and
\\ polynomial calculations sometimes use subtractions such as count all
\\ "without" vertex v then subtract those "without undominated" to get
\\ "without dominated". The sets work instead always adding. That can be
\\ more complicated products, but the number of sets grows quickly so it's
\\ good not to make more sets than necessary.
\\
\\
\\ Matula-Goebel Prime Codes
\\ -------------------------
\\
\\ Matula and Goebel independently give an encoding of an unlabelled tree
\\ using prime factorization. 1 is a childless singleton and then child
\\ subtree codes child1 etc under the root are
\\
\\ primecode = prime(child1) * prime(child2) * ...
\\
\\ vpar_to_primecode() and vpar_from_primecode() convert to and from vpar.
\\ These codes are a bijection between natural numbers and unlabelled trees.
\\ Factoring and prime(x) become impractical beyond small trees.
\\ (Set a large default(primelimit) for more speed from prime(x).)
\\
\\
\\ Other Notes
\\ -----------
\\
\\ The "minmax" functions take a func parameter which is either min or max,
\\ ie. those builtin function closures. Don't pass anything else as func.
\\ The minmax functions take a parameter since the code for min or max cases
\\ is nearly identical and can have a single implementation with a
\\ parameter. Sometimes a func parameter is passed through to a
\\ corresponding subroutine doing most of the work and in that case separate
\\ min and max would be identical other than the choice of subroutine.
\\
\\ The "vpar_count_()" functions are counts of how many vpar trees or
\\ forests with some property. They are for n>=0 vertices and behaviour on
\\ negative n is unspecified. Plain vpar_count() is all labelled rooted
\\ trees, or forests. Count functions for things other than vpars have
\\ "count" somewhere later in the name to try to distinguish. Relevant OEIS
\\ A-numbers are shown in the addhelp strings when exist, known, etc.
\\ Web pages for those are for example
\\
\\ http://oeis.org/A000169 labelled rooted trees (vpar_count())
\\
\\ Various OEIS sequences reckon 0 many trees of n=0 vertices. This is
\\ usually in rooted trees such as A000081. Not sure of the rule, it might
\\ be because rooted tree needs a root. On the other hand, free trees have
\\ 1 tree of n=0 vertices such as in A000055. In the code here, there is
\\ always 1 tree of n=0 vertices, being vpar=[] empty vector.
\\
\\ One place 0 of n=0 can be desirable is in generating function relations
\\ which assemble a tree of n vertices from smaller trees underneath.
\\ Usually that's cleaner with no n=0s, but is just a matter of being
\\ careful whether the gf is to include a constant term or not.
\\
\\ The "forest" parameter in count and iterator functions should be 0, 1, or
\\ sometimes -1. The code tries to provoke an error if given a non-number
\\ there. This catches an unset variable, since an unset variable is a
\\ symbol (not a number). The same consideration could apply to most
\\ parameters, but it tends to be the optional parameters where mistakes are
\\ more likely. Expect errors to be obscure, but better than silently
\\ unnoticed.
\\
\\
\\ Speed
\\ -----
\\
\\ Most calculations on a vpar are linear time and space for number of
\\ vertices so can be used on large trees. Matrix outputs will take n^2
\\ time just due to output size. Polynomial outputs might take time
\\ approaching n^2 due to intermediate polynomials formed. Some functions
\\ like counts of sets in a tree may give bignum results (up to 2^n). Lists
\\ of those sets can become big very quickly.
\\
\\ Generally the code is not fast enough for exhaustive searching of all
\\ vpar trees, or only up to small n, since the number of trees quickly
\\ becomes big. Restricting to free, unlabelled, or ordered, forms grows
\\ slower, but still roughly 3* or 4* the previous n. The intended use for
\\ vpar.gp is generally on specific trees but possibly large ones.
\\
\\ Count functions of how many trees, vectors, etc, of some property usually
\\ have good formulas and should be limited only by size of the final
\\ result. In some cases, an isolated n can't be done much better than
\\ forming all of 1..n, so space for all those needed. For big n, an
\\ asymptotic or approximate formula might be more use than an exact answer,
\\ but there's nothing for that here presently. Relevant OEIS sequences
\\ often give asymptotics (of moderately bizarre expressions or powers).
\\
\\ Some algorithms might benefit from other tree representations. For
\\ example, algorithms working upwards or inwards might like a suitable
\\ pre-calculated vertex sequence, or labelling in that sequence already.
\\ Or those working downwards might like roots and pre-calculated lists of
\\ children. vpar has the attraction of a simple representation for
\\ constructing or modifying, and in practice extra wanted data is only an
\\ extra pass over the vector.
\\
\\ There's no gp2c type decorations on variables and parameters, mainly
\\ since if you want to use C then the same vpar[] can be done directly
\\ there without too much trouble and with much greater speed. The author's
\\ nautyextra.c (spending a lot of time unfinished, might become a vpar.c)
\\ has some vpar C functions.
\\
\\ As noted above, the nauty tools gentreeg.c program generates free trees
\\ as vpar. It can be used command-line or from C code.
\\
\\
\\ Requirements
\\ ------------
\\
\\ Designed for GP 2.7 up. Clean to option "strictargs".
\\ Dynamic library install() not necessary, but if available then is used in
\\ small ways for Pari library functions.
\\
\\
\\ Changes
\\ -------
\\
\\ Version 1 - the first version.
\\ Version 2 - polynomials, matrices, more path lengths, weight.
\\ Version 3 - some sequences, keep, contract, primecode, counts.
\\ Version 4 - mucho more, including isomorphism, components.
\\ Version 5 - yet mucho more, including sets, iteration, ordered.
\\ Version 6 - root=1 tree counts etc, siblings, stable,almost equimatchable.
\\ Version 7 - graph6,sparse6, free iteration, matrix polys, and more
\\ - fix for vpar_is_ancestor() with v==ancestor.
\\ Version 8 - automorphisms, asymmetric, perfect domination.
\\ Version 9 - to unlabelled,free, downwards ith, Prufer sequence.
\\ Version 10 - docs fix, automorphism generators are not minimum.
\\ - balanced binary, automorphisms lists, leaves, childless.
\\ Version 11 - lexmin, strongly monotone.
\\ Version 12 - equaldepths, downqueue sequence, more siblings.
\\ Version 13 - orbits, digraph6, more cyclics.
\\ Version 14 - subtree num childless, faster vpar_make_random_preorder.
\\ Version 15 - depth and path lengths cyclic.
\\ Version 16 - childful, transpose, balanced binary iterate, preorder down.
\\ Version 17 - horizpos, diagdepths, more automorphisms.
\\ Version 18 - is_bipartite, a few more cyclics.
\\ Version 19 - is_unlabelled, orbit_sizes.
\\ Version 20 - fix for vpar_to_free() on bicentrals (see notes with the code).
\\ Version 21 - new vpar_unlabelled_nextR(), vpar_preorder_nextR(),
\\ vpar_Wiener_index() childless terminal option.
\\ Version 22 - caterpillars, edge covers, total cophenetic.
\\
\\-----------------------------------------------------------------------------
\\ For gp-inline, mainly testing addhelp() sample values shown.
\\ GP-DEFINE default(strictargs,1);
\\ GP-DEFINE default(recover,0);
\\ GP-DEFINE read("vpar.gp");
\\ GP-DEFINE read("test-oeis-samples.gp");
\\ GP-DEFINE read("devel/memoize.gp");
\\-----------------------------------------------------------------------------
\\ Checks and Diagnostics
vpar_validate(vpar) =
{
\\ print("vpar_validate() ",vpar);
for(v=1,#vpar,
if(vpar[v]<0||vpar[v]>#vpar,
error("vpar bad, vpar[",v,"]=",vpar[v]," out of range 0 to ",#vpar)));
my(seen=vectorsmall(#vpar));
for(i=1,#vpar,
my(v=i);
while(v,
if(seen[v], \\ a revisit
if(seen[v]==i, \\ in present search means cycle
error("vpar bad, cycle ",Vec(select(s->s==i,Vec(seen),1))));
break); \\ revisit to something previous found acyclic
seen[v]=i;
v=vpar[v]));
}
{
addhelp(vpar_validate,
"vpar_validate(vpar)
Validate the contents of vpar. error() on anything bad.
Currently this looks for any cycles in the parent-child relations, and any
parent values outside the proper range 0 to #vpar (inclusive).");
}
\\-----------------------------------------------------------------------------
\\ Internal Helpers
\\ vpar_INTERNAL_identity_perm() = Pari library identity_perm() when
\\ possible to install() it, otherwise GP code.
\\ The fallback code is not a numtoperm() since that was full Vec() in GP
\\ 2.7.x whereas want Vecsmall.
\\ install() in "secure" mode will query the user, don't attempt in that case.
\\ install() throws an error if dlopen() not available on the system.
\\ Must install() before any funcs using it, even though those funcs not
\\ called until later.
'vpar_INTERNAL_identity_perm;
kill(vpar_INTERNAL_identity_perm);
{
if(! if(! default(secure),
iferr(install(identity_perm, "L", "vpar_INTERNAL_identity_perm"); 1,
e, 0)),
vpar_INTERNAL_identity_perm = (n)->vectorsmall(n,i,i));
}
\\ vpar_INTERNAL_gnot(x) = Pari library gnot() which is boolean not "!"
\\ operator as a function. This is convenient for apply(), select(), etc.
\\ gequal0() does the same "not", but returns an int instead of a GEN.
\\ Think a GEN return might save a couple of nanoseconds in the dispatcher.
'vpar_INTERNAL_gnot;
kill(vpar_INTERNAL_gnot);
{
if(! if(! default(secure),
iferr(install(gnot, "G", "vpar_INTERNAL_gnot"); 1,
e, 0)),
vpar_INTERNAL_gnot = (x)->!x);
}
\\ vpar_seq_upascend sans the "ahead" option, and optional pre-calculated
\\ num_children
vpar_INTERNAL_seq_upwards(vpar,num_children=vpar_INTERNAL_num_children_vecsmall(vpar)) =
{
my(seq=vectorsmall(#vpar),
pos=0);
for(u=1,#vpar,
my(v=u);
while(num_children[v]==0,
seq[pos++]=v; \\ pre-increment
num_children[v]=-1;
(v=vpar[v]) || break;
num_children[v]--)); \\ parent
seq;
}
\\ keys is a vector of integers.
\\ Return a Vecsmall of the vertices 1..#vpar sorted in order of ascending
\\ keys[v]. For down=1, sort by descending keys[v] instead.
\\ In both cases among equal keys[v] sort by ascending v.
vpar_INTERNAL_seq_bucket_sort(keys,down=0) =
{
\\ vecsort() flag=4 for descending puts indices of equal elements
\\ descending too. That's merely Vecrev which is a bit unhelpful.
\\ The desire here is to keep ascending indexes on equal elements.
\\ This is done by negating keys when down==1. Note this means keys[]
\\ must be a full Vec not Vecsmall since GP circa 2.9.x won't negate a
\\ Vecsmall, not with "-" operator.
\\
if(down,keys=-keys);
vecsort(keys,,1);
\\ The bucket sort code below is linear, but because it's interpreted code
\\ it runs slower than builtin vecsort() which is a merge sort n*log(n) as
\\ of Pari circa 2.9.x. Or at least it's slower until somewhere around 3
\\ million keys, or even bigger if keys Vecsmall. So for now just
\\ vecsort().
\\ \\ bucket[b] = v is the first vertex which has key b = keys[v]-base.
\\ \\ Then linked list chain[v] = v2 is the second such, chain[v2] = v3 the
\\ \\ third, etc, until vn=0.
\\ \\ An empty bucket has bucket[b]=0.
\\ \\ These linked lists have vertices pushed high to low, so come out low to
\\ \\ high for final traversal putting into seq.
\\ \\ "down" changes order buckets are processed, but not the linked lists.
\\
\\ my(seq=vectorsmall(#keys));
\\ if(#keys,
\\ my(chain = seq,
\\ base = vecmin(keys)-1,
\\ buckets = vectorsmall(vecmax(keys)-base));
\\
\\ \\ make linked lists in buckets
\\ forstep(v=#keys,1,-1,
\\ my(b=keys[v]-base);
\\ chain[v]=buckets[b]; buckets[b]=v);
\\
\\ \\ linked lists into seq
\\ my(upto=0); \\ ready for pre-increment
\\ forstep(b=if(down,#buckets,1),if(down,1,#buckets),if(down,-1,1),
\\ my(v=buckets[b]);
\\ while(v,
\\ seq[upto++]=v; \\ pre-increment
\\ v=chain[v])));
\\ seq;
}
\\ {
\\ addhelp(vpar_INTERNAL_seq_bucket_sort,
\\ "seq = vpar_INTERNAL_seq_bucket_sort(keys,down=0)
\\ Return a Vecsmall of the vertex numbers v=1..#keys sorted by ascending
\\ keys[v] values, and among equal keys by ascending vertex number v.
\\ The return type is Vecsmall since this is a permutation of the integers
\\ 1..#keys.
\\
\\ Optional parameter down can be 1 to sort instead by descending keys[v] and
\\ among equal keys by ascending v.
\\
\\ This is a bucket sort. Each keys[v] must be an integer. Their range of
\\ values can be anything (including negatives), but the vector of buckets is
\\ sized vecmax(keys)-vecmin(keys)+1 for storage and a linear final pass is
\\ made over it. The sort is linear time and space when the range of keys is
\\ roughly the number of vertices #keys (or smaller). Big ranges of keys are
\\ better handled with general purpose
\\
\\ vecsort(keys,,1+if(down,4)).");
\\ }
\\ A000110 Bell numbers = ways to partition 1..n into one or more sets
\\ (no ordering among the sets)
vpar_INTERNAL_Bell_number(n) = sum(k=0,n, stirling(n,k,2));
\\ A000108 Catalan numbers binomial(2n,n) / (n+1)
vpar_INTERNAL_Catalan_number(n) = binomial(n<<1,n) / (n+1);
\\ vpar_INTERNAL_Catalan_triangle(n,k) = number of preorder forests of n
\\ vertices which start a path down [0,1,2,...] and the last k vertices
\\ continuing from there.
\\
\\ k=n is no initial path down, the whole forest is the rest, so count is
\\ simply all forests vpar_INTERNAL_Catalan_number().
\\
\\ k=n-1 (for n>=1) is the same as k=n because the first vertex is always
\\ depth 0, so demanding that as initial path length 1 is no change.
\\
\\ k=0 is everything initial path down, nothing further, so count 1 way.
\\
\\ A009766 Catalan triangle by rows
\\ A030237 Catalan triangle by rows, sans duplicate at last of each row
vpar_INTERNAL_Catalan_triangle(n,k) = binomial(n+k,n) * (n-k+1)/(n+1);
\\-----------------------------------------------------------------------------
vpar_root(vpar) =
{
for(v=1,#vpar,
vpar[v] || return(v));
0;
}
{
addhelp(vpar_root,
"v = vpar_root(vpar)
Return the root vertex of vpar.
This is the first vertex v with vpar[v]==0.
This function is intended for use with a tree so there is a single root.
If vpar is a forest then return the smallest number root. If vpar==[] empty
then return 0. 0 is not a valid vertex number, but the idea is that it's
more convenient to get a return to test than to throw an error.
See vpar_roots() for all roots of a forest.");
}
\\ returning full Vec, not Vecsmall
vpar_roots(vpar) = Vec(select(vpar_INTERNAL_gnot,vpar,1));
{
addhelp(vpar_roots,
"vec = vpar_roots(vpar)
Return a vector of the root vertices in vpar, sorted by increasing vertex
number (so is a Set()). This is all v with vpar[v]==0.");
}
vpar_num_roots(vpar) = #vpar - hammingweight(vpar); \\ number of zeros
{
addhelp(vpar_num_roots,
"num = vpar_num_roots(vpar)
Return the number of root vertices in vpar. This is how many vpar[v]==0.
For interest, the total number of roots among all labelled forests of n
vertices is 2*n*(n+1)^(n-2) = 0,1,4,24,200,2160,... (A089946). This is by
counting 0s in the Prufer code. It has n values with last always 0 and the
rest 0 to n. The last 0 has (n+1)^(n-1) other combinations, and a 0 in the
rest has (n+1)^(n-2) other combinations, so (n+1)^(n-1) + (n-1)*(n+1)^(n-2).
Dividing by total number of forests vpar_count(n,1) = (n+1)^(n-1) is limit
mean 2 roots in a forest.
When there are no cycles in vpar, the number of roots is the number of
components. But each cyclic component has no root so vpar_num_components()
is bigger when there are cycles.");
}
\\ GP-Test vector(6,n,n--; 2*n*(n+1)^(n-2)) == [0,1,4,24,200,2160]
\\ GP-Test vector(6,n,n--; 2*n*(n+1)^(n-2)) == \
\\ GP-Test vector(6,n,n--; (n+1)^(n-1) + (n-1)*(n+1)^(n-2))
vpar_num_rootings(vpar) =
{
\\ ENHANCE-ME: Maybe support cyclic vpar by reckoning cyclic components
\\ just 1 rooting.
my(vec=vpar_component_sizes(vpar));
prod(i=1,#vec, vec[i]);
}
{
addhelp(vpar_num_rootings,
"num = vpar_num_rootings(vpar)
Return the number of possible rootings of vpar. This is how many different
vpar vectors can be had by rerooting with vpar_reroot_forest() or similar,
including vpar itself unchanged.
For a tree, num=#vpar choices of one vertex as the root.
For a forest, if component trees have t1,t2,... many vertices
(vpar_component_sizes()) then the number of rootings is product t1*t2*etc.
For n vertices, num ranges 1 <= num <= vpar_num_rootings_max(n).
Total rootings over all labelled trees is n*vpar_count(n) = n^n =
1,4,27,256,... (A000312). Total over all forests is the usual exponential
transform of this to 1,5,40,437,... (A202477).
num is expressed as count of rootings, but is also any kind of choice of 1
vertex from each component tree. Such a choice can be for establishing
roots, or for other things.");
}
vpar_num_rootings_max(n) =
{
\\ n want
\\ 2 mod 3 3 * 2
\\ 0 mod 3 3 * 3 3
\\ 1 mod 3 3 * 4 3 * 2 * 2
\\ 2 mod 3 3 * 2 3 * 2
\\ n-2 mod 3 then rem+2 is remainder 2,3,4
if(n<=1, 1,
my(qd=divrem(n-2,3));
3^qd[1] * (qd[2]+2));
}
{
addhelp(vpar_num_rootings_max,
"num = vpar_num_rootings_max(n)
Return the maximum number of rootings for forests of n vertices, so the
maximum return from vpar_num_rootings() for n vertices.
This is maximum product of a partition of n, which is when component trees
are all 3 vertices except one of 2 or 4 when n == +/-1 mod 3 (or 4 as two 2s
of course), which is num = 1,1,2,3,4,6,9,12,18,27,... (A000792).");
}
\\ MAYBE: Inverse could be i = vpar_roots_to_i(vpar,roots), or maybe default
\\ roots to the current roots of vpar.
\\
vpar_roots_ith(vpar,i) =
{
my(compnum=vpar_compnum_vector(vpar),
component_sizes=vpar_INTERNAL_histogram(compnum));
i--;
\\ component_sizes[] to mixed radix digits of i, starting from 1
forstep(c=#component_sizes,1,-1,
i=divrem(i,component_sizes[c]);
component_sizes[c] = i[2] + 1;
i = i[1]);
my(ret=vector(#component_sizes));
i=0;
for(v=1,#vpar,
my(c=compnum[v]);
if(component_sizes[c]--==0, \\ vertex of this index reached
ret[i++]=v));
ret;
}
{
addhelp(vpar_roots_ith,
"vec = vpar_roots_ith(vpar,i)
Return the i'th vector of possible root vertices in vpar.
i can range from 1 to vpar_num_rootings(vpar) inclusive.
vec has vertices in ascending order, so is a Set() and if vpar is rerooted
to vec then vpar_roots(vpar)==vec.
For a tree, the return is simply vec=[i].
For a forest, components are numbered by their lowest vertex the same as
vpar_compnum_vector(). i=1 is smallest vertex number each component, the
same as vpar_component_minmax(vpar,min). i=2 is the last component stepped
to its second smallest vertex. Successive i step the last component each
time, second last when that wraps around, and so on to first component
stepping slowest. The last i is all biggest vertex numbers, the same as
vpar_component_minmax(vpar,max).
This function is named \"roots\" for its use choosing roots, but the vertex
numbers returned are simply one from each component and could be used for
other purposes.
See also vpar_reroot_ith().");
}
vpar_reroot_ith(vpar,i) = \
vpar_reroot_forest(vpar, vpar_roots_ith(vpar,i));
{
addhelp(vpar_reroot_ith,
"vpar = vpar_reroot_ith(vpar,i)
Return vpar rerooted to its i'th possible rooting per vpar_roots_ith().
i can range from 1 to vpar_num_rootings(vpar) inclusive.
See also vpar_reroot_random().");
}
\\-----------------------------------------------------------------------------
vpar_is_tree(vpar) =
{
my(num_roots=0);
for(v=1,#vpar, if((num_roots+=!vpar[v])>=2, return(0)));
1;
}
{
addhelp(vpar_is_tree,
"bool = vpar_is_tree(vpar)
Return 1 if vpar is a tree, or return 0 if not.
A tree has at most one root, so vpar_num_roots(vpar) <= 1.
The empty vpar=[] is reckoned as a tree of no roots.");
}
vpar_num_edges(vpar) = hammingweight(vpar); \\ number of non-zeros
{
addhelp(vpar_num_edges,
"num = vpar_num_edges(vpar)
Return the number of edges in vpar.
This is the number of vertices with parents, so how many vpar[v]!=0.
For a tree this is simply #vpar-1. For a forest each root has no edge above
so vpar_num_edges(vpar) = #vpar - vpar_num_roots(vpar).
For interest, total num edges among all labelled forests of n vertices is
(n-1)*n*(n+1)^(n-2) = 0,0,2,24,300,4320,... (A065513). This follows by
total vertices n*vpar_count(n,1) less total roots as described in
vpar_num_roots().
Some edges are upwards parent p>v, and others are downwards v>p. A reverse
numbering (vpar_relabel_reverse()) swaps them, so by symmetry total edges of
each type over all forests is half the overall total, 0,0,1,12,150,2160,...
(A053507). Kreweras and Moszkowski (reference with vpar_seq_blob())
consider a generating function for trees of k many downwards edges.");
}
\\ GP-Test vector(6,n,n--; (n-1)*n*(n+1)^(n-2)) == [0,0,2,24,300,4320]
\\ GP-Test vector(6,n,n--; (n-1)*n*(n+1)^(n-2) / 2) == [0,0,1,12,150,2160]
\\ my(n=100); (n-1)*n*(n+1)^(n-2) / vpar_count(n,1)*1.0
\\ GP-Test vector(10,n,n--; (n-1)*n*(n+1)^(n-2)) == \
\\ GP-Test vector(10,n,n--; n*vpar_count(n,1) - 2*n*(n+1)^(n-2))
vpar_root_of_vertex(vpar,v) =
{
\\ u running up at half speed catches a short distance to cycle.
\\ Loop catches a big cycle at one v traversal, not a second to reach u.
\\
\\ n=1 is (1>>1)==0 must loop at least once
\\ n=3 path needs check v=3,v=2, then next loop see v=1; (3>>1)==1
my(u=v);
for(i=0,#vpar>>1,
vpar[v] || return(v); if((v=vpar[v])==u, break);
vpar[v] || return(v); if((v=vpar[v])==(u=vpar[u]), break));
0;
}
{
addhelp(vpar_root_of_vertex,
"r = vpar_root_of_vertex(vpar,v)
Return the root vertex of the component tree containing v in vpar.
vpar can contain cycles. If v has a cycle above it, not a root, then return 0.");
}
vpar_root_of_vertex_vector(vpar) =
{
\\ roots[v] = the root of v, or 0 if v under a cycle, or -1 if unknown.
\\ At each vertex i, follow up its ancestors to find the root.
\\ If reach vpar[v]==0 then that v is the root.
\\ If reach roots[v]>0 then that roots[v] is root found by previous search.
\\ Then re-traverse upwards to fill in.
\\
\\ To catch cycles, roots[v]=0 is successively set, replacing the -1 for
\\ unknown. If reach roots[v]=0 then that is either a cycle traversed, or
\\ a cycle above found by a previous search. In both cases have set
\\ roots[v]=0 on the way up which is the desired result.
\\
\\ In general, a cycle can be noticed by flagging the previously traversed
\\ vertices, so that a cycle will see a flag. Here roots[] is used to
\\ flag. The desired value in roots[] will be 0 for a cycle or some r to
\\ be determined for a root. Flagging by setting 0 means one of the two
\\ possibilities is already done.
my(roots=vector(#vpar,i,-1));
for(i=1,#vpar,
if(roots[i]<0,
my(v=i,r);
until(!(v=vpar[r=v]),
if((r=roots[v])>0, break,
!r, next(2)); \\ cyclic, and already filled with 0
roots[v]=0);
v=i;
until(!(v=vpar[v]) || roots[v], roots[v]=r))); \\ fill with r
roots;
}
{
addhelp(vpar_root_of_vertex_vector,
"vec = vpar_root_of_vertex_vector(vpar)
Return a vector where each vec[v] is the root of vertex v.
This is vec[v] = vpar_root_of_vertex(vpar,v) but calculated all together.
vpar can contain cycles. vec[v]=0 when v has a cycle above it, not a root.
The number of different vectors which occur from vpar forests is
F(n) = vpar_count_height_le(n,1,1)
= sum(k=0,n, binomial(n,k)*k^(n-k))
= 1, 1, 3, 10, 41, 196, ... (A000248)
The sum is choose k roots and assign the rest under them. The forest
structure doesn't matter, only what root each vertex, so can take them as
all immediately under their root and so forests height <= 1.
Similar with cycles is k+1 since also no-root when in or below a cycle.
C(n) = sum(k=0,n, binomial(n,k)*(k+1)^(n-k))
= 1, 2, 6, 23, 104, 537, ... (A080108)
The root identifies the component containing v, when not under a cycle.
See vpar_compnum_vector() for similar identity by component number (and
including for cyclics there).");
}
\\ GP-Test vector(6,n,n--; sum(k=0,n, binomial(n,k)*k^(n-k))) == \
\\ GP-Test [1, 1, 3, 10, 41, 196]
\\ GP-Test vector(6,n,n--; sum(k=0,n, binomial(n,k)*(k+1)^(n-k))) == \
\\ GP-Test [1, 2, 6, 23, 104, 537]
vpar_is_root_minmax(vpar,func) =
{
\\ For func=min must have v=1 a root, otherwise whatever is the root of 1
\\ will not be the min of its tree. For func=max conversely v=#vpar must
\\ be a root.
\\
\\ For other vertices, search upwards to find their root.
\\ roots[v] = root of v, or 0 if not yet known.
\\ m is min/max of vertices v up to the root or two somewhere root[] is
\\ known. Must have root r the min/max, not one of the m below it.
\\
\\ For func=min the search is run up 2 -> n since a small v might have
\\ more chance of being smaller than its root for early exit.
\\ For func=max conversely downwards n-1 -> 1.
\\ #vpar==0 would give start/end looping i=0 to 1 which is no good.
\\ GP-Test my(func=min,vpar=[], start=func(1,#vpar), step=-func(1,-1)); \
\\ GP-Test start==0 && step==1 && #vpar-start+1==1
if(#vpar,
my(start=func(1,#vpar)); \\ 1 for min, #vpar for max
if(vpar[start], return(0)); \\ start must be a root
my(roots=vectorsmall(#vpar),
step=-func(1,-1)); \\ 1 for min, -1 for max
forstep(i=start+step, #vpar-start+1, step, \\ 2 to n min, #vpar-1 to 1 max
if(!roots[i],
my(v=i, m=v, r); \\ m = min/max vertex seen under r
while((v=vpar[r=v]) && !(r=roots[v]), m=func(m,v));
if(func(m,r)!=r,return(0)); \\ m smaller/bigger than root
v=i;
until(!(v=vpar[v]) || roots[v], roots[v]=r))));
1;
}
{
addhelp(vpar_is_root_minmax,
"bool = vpar_is_root_minmax(vpar,func)
Return 1 if vpar has all roots as the minimum or maximum vertex in their
component tree. \"func\" parameter is min or max,
bool = vpar_is_root_minmax(vpar,min)
bool = vpar_is_root_minmax(vpar,max)
The number of forests with this property is the unrooted forests count
vpar_count_unrooted(n,1). See vpar_reroot_minmax() to reroot a vpar into
this form.");
}
\\ For func=min, among the various re-rootings only
\\ the one with smallest component vertex as root counts. Similarly for
\\ func=max only biggest component vertex as root.
\\ The numTrees with this property are simply those root=1 for min, or root=n for max,
\\ so vpar_count(n,-1) many of them.
\\-----------------------------------------------------------------------------
\\ Children
vpar_children_of_vertex(vpar,v) = Vec(select(p->p==v,vpar,1));
{
addhelp(vpar_children_of_vertex,
"vec = vpar_children_of_vertex(vpar,v)
Return a vector of the child vertices of v, in ascending order (so a Set()).
This is all vertices c which have vpar[c]==v.
Can give v=0 to get all c with vpar[c]==0, which means c are the root
vertices, the same as vpar_roots().");
}
vpar_children_vector(vpar) =
{
my(num_children=vpar_INTERNAL_num_children_vecsmall(vpar),
ret=vector(#vpar,v, vector(num_children[v])));
forstep(v=#vpar,1,-1,
my(p=vpar[v]);
if(p, ret[p][num_children[p]] = v; num_children[p]--));
ret;
}
{
addhelp(vpar_children_vector,
"vec = vpar_children_vector(vpar)
Return a vector where each vec[v] is a vector of the child vertices of v.
This is the same as vec[v] = vpar_children_of_vertex(vpar,v) but calculated
all together.
Each vector has children sorted in increasing order (the same as
vpar_children_of_vertex()).");
}
\\ Specific labellings can do this slightly faster.
\\ Downwards labelled would mean only look v+1 .. n.
\\ Upwards labelled would mean only look 1 .. v-1.
\\ Pre-order min child is v+1, after checking its parent isn't higher up.
\\ Post-order max child is v-1, after checking its parent isn't higher up.
\\ Pre-order max child can stop looking when vpar[i]=0 || !#vec || vec[1],
for(v=1,#vec, if(vec[v]<0 || (edges+=vec[v])>#vec, return(0))));
forest>0 || edges==#vec;
}
{
addhelp(vpar_is_num_children_vector,
"bool = vpar_is_num_children_vector(vec,forest=0)
vec is a vector of integers. Return 1 if it is a num children vector of a
tree, so vec == vpar_num_children_vector(vpar) for some vpar tree.
Optional parameter \"forest\" can be 1 to test for vec from a forest, or can
be -1 to test from a tree of root=1.
The test is all vec[i]>=0, then vecsum(vec)==#vec-1 for a tree or <=#vec-1
for a forest, that being number of edges (vpar_num_edges()). For root=1,
must have vec[1]>0 if #vec>=2 since the root must not be childless when any
other vertices exist. Empty vec==[] is num children of the empty tree.
All such vec occur for some vpar by constructing with vertices by ascending
num children in the manner of vpar_make_num_children().
See vpar_num_children_vectors_count() for how many vec occur.");
}
vpar_num_children_vectors_count(n,forest=0) =
{
\\ forest=-1 binomial(2*n-3,n-1) or 1 if n==0
\\ forest=0 binomial(2*n-2,n-1) or 1 if n==0
\\ forest=1 binomial(2*n-1,n)
if(n, binomial((n<<1)-2+forest, n-(forest<=0)), 1);
}
{
addhelp(vpar_num_children_vectors_count,
"count = vpar_num_children_vectors_count(n,forest=0)
Return the number of different vpar_num_children_vector() which occur for
trees of n vertices, so how many vectors are vpar_is_num_children_vector().
Optional parameter \"forest\" can be 1 for number occurring from forests, or
can be -1 for number occurring from root=1 trees.
For a tree, the return is the central binomial numbers
if(n==0,1,binomial(2*n-2,n-1)) = 1,1,2,6,20,70,... (A000984).
A num children vector is a composition (partition with order) of n elements
summing to n-1, with zeros allowed. The binomial is by listing 2*n-2
positions and choosing n-1 of them to be boundaries to make n runs of the
n-1 unchosens, including possibly empty runs. Those run lengths are the
composition.
For a forest, the return is binomial(2*n-1,n) = 1,1,3,10,35,126,... (A001700).
This follows similarly by n boundaries making n+1 runs of the n-1 unchosen.
Dropping the last run is total <= n-1. Root=1 tree is the same as forest of
1 fewer vertices by removing the root so if(n==0,1,binomial(2*n-3,n-1)).");
}
\\ GP-Test vector(6,n,n--; if(n==0,1,binomial(2*n-2,n-1))) == [1,1,2,6,20,70]
\\ GP-Test vector(6,n,n--; if(n==0,1,binomial(2*n-3,n-1))) == [1,1,1,3,10,35]
\\ GP-Test vector(6,n,n--; binomial(2*n-1,n)) == [1,1,3,10,35,126]
\\ GP-Test binomial(0,-1) == 0
\\ GP-Test binomial(-1,-1) == 0
\\ GP-Test binomial(-1,0) == 1
\\ For a root=1 tree, the return is if(n==0,1,binomial(2*n-3,n-1)) =
\\ 1,1,1,3,10,35,... (A001700) which is choice from 2*n-3 positions. The first
\\ position is excluded so first run >=1 which is root=1 having >=1 children,
\\ unless n<=1 so there are no more vertices at all.
vpar_make_num_children(vec) =
{
my(seq=vpar_INTERNAL_seq_bucket_sort(vec),
vpar=vector(#vec),
c=0); \\ child index in seq[]
for(i=1,#seq,
my(p=seq[i]);
for(j=1,vec[p], vpar[seq[c++]]=p)); \\ put vec[p] many children under p
vpar;
}
{
addhelp(vpar_make_num_children,
"vpar = vpar_make_num_children(vec)
Create and return a vpar where each vertex v has vec[v] many children, so
that vpar_num_children_vector(vpar) == vec. vec must be a valid num
children vector (vpar_is_num_children_vector(vec)).
There can be various vpar with the same num children vector. The one
returned here considers vertices in ascending order of num children, and
ascending vertex number among equal num children. Vertices to be children
are taken in that order and parents are taken in that order too. Enough
children are put under successive parents to reach its vec[p] number of
children.
If vec is ascending, so childless first, then single child, etc, then this
procedure gives a vpar_is_upwards(vpar) tree (or forest). This is because
the child vertex numbers don't catch up to the parents they are under.
Some other vecs can give upwards too.");
}
\\------------------------------------------------------------------------------
\\ Sequence by Number of Children
vpar_seq_num_children(vpar,down=0) =
{
\\ must full vector for vpar_INTERNAL_seq_bucket_sort() negate when down=1
vpar_INTERNAL_seq_bucket_sort(vpar_num_children_vector(vpar), down);
}
{
addhelp(vpar_seq_num_children,
"seq = vpar_seq_num_children(vpar,down=0)
Return a Vecsmall of the vertex numbers 1..#vpar in sequence of ascending
number of children, and among equal number of children by ascending vertex
number. The return type is Vecsmall since it is a permutation of the
integers 1..#vpar.
Childless vertices are first, then single-child vertices (if any), etc. In
general this is not an upwards or downwards sequence since vertices of some
number of children can occur high or low in the tree.
Optional parameter \"down\" can be 1 to sort by descending number of
children, and within equal by ascending vertex number.");
}
vpar_from_num_children(vec,down=0) = \
vecextract(vec, vpar_seq_num_children(vec,down)^-1);
{
addhelp(vpar_from_num_children,
"vpar = vpar_from_num_children(vec,down=0)
Return the vpar from a vector of parents in order of their number of
children, as from vec = vecextract(vpar, vpar_seq_num_children(vpar,down)).
Optional parameter \"down\" should be the same as the seq from which vec was
created.
Num children sequence is \"self-from\", in the sense that the sequence
seq=vpar_seq_num_children(vpar,down) is recovered from its vec by the same
vpar_seq_num_children(vec,down) == seq, and from which can inverse permute
vec to recover the original vpar. This is since vec is a permutation of
vpar and vpar_num_children_vector() doesn't care about order, it just counts
occurrences of vertices as parents.");
}
\\ vpar_seq_num_children() is its own seqfrom:
\\ vpar_INTERNAL_seqfrom_num_children(vec,down=0) = \
\\ vpar_INTERNAL_seq_bucket_sort(vpar_num_children_vector(vec),down);
\\ For interest, sequence sorting by degree rather than num_children is not
\\ reversible in general because it doesn't distinguish the root. Trees
\\ root=1 are reversible though, fixing the root.
\\-----------------------------------------------------------------------------
\\ Childful / Childless Vertices
vpar_is_childful(vpar,v) = for(c=1,#vpar, if(vpar[c]==v, return(1))); 0;
{
addhelp(vpar_is_childful,
"bool = vpar_is_childful(vpar,v)
Return 1 if v in vpar has 1 or more children.
This means v occurs somewhere in the vpar vector, so parent of something.
vpar can contain cycles.
v=0 tests whether there are any roots, ie. whether 0 occurs somewhere in
vpar, so at least one component not cyclic. This is the same as
vpar_num_roots(vpar)!=0.
The opposite, no children, is vpar_is_childless().");
}
vpar_is_childless(vpar,v) = for(c=1,#vpar, if(vpar[c]==v, return(0))); 1;
{
addhelp(vpar_is_childless,
"bool = vpar_is_childless(vpar,v)
Return 1 if v has no children, or 0 if not (has children).
vpar can contain cycles.
The test is simply whether v occurs in the vpar vector. If it does then it
has children, if not then it is childless. This is the same as
vpar_num_children_of_vertex(vpar,v)==0, but the code can stop looking when
any child is seen.
v=0 tests for no roots, ie. 0 does not occur in vpar, so empty or all
components are cyclic. This is the same as vpar_num_roots(vpar)==0.
The opposite, has one or more child, is vpar_is_childful().");
}
vpar_is_childful_vector(vpar) =
{
my(vec=vector(#vpar));
for(v=1,#vpar, if(vpar[v], vec[vpar[v]]=1));
vec;
}
{
addhelp(vpar_is_childful_vector,
"vec = vpar_is_childful_vector(vpar)
Return a vector where each vec[v]=1 if v has children or 0 if not.
This is vec[v]=vpar_is_childful(vpar,v) but calculated all together.
Knuth calls this the \"footprint\" of a forest, and it corresponds to the
\"canopy\" of a binary tree. See examples/footprints-preorder.gp for
counting preorder forests by their footprint.");
}
\\ All possible vec occur from vpar, just by putting each non-leaf above any
\\ leaf. Even just among preorder forests, the same by successive chains
\\ down. For root=1 trees (including preorder trees), the first vec[1] is
\\ always non-leaf.
vpar_is_childless_vector(vpar) =
{
my(vec=vector(#vpar,i,1));
for(v=1,#vpar, if(vpar[v], vec[vpar[v]]=0));
vec;
}
{
addhelp(vpar_is_childless_vector,
"vec = vpar_is_childless_vector(vpar)
Return a vector where each vec[v]=1 if v is childless or 0 if v has children.
This is vec[v]=vpar_is_childless(vpar,v) but calculated all together.
vpar can contain cycles.");
}
vpar_childful_vertices(vpar) =
{
my(ret=Set(vpar));
if(#ret && !ret[1], ret[^1], ret); \\ exclude ret[1]==0
}
{
addhelp(vpar_childful_vertices,
"vec = vpar_childful_vertices(vpar)
Return a vector of all vertex numbers which have 1 or more children, so
vpar_is_childful(), in ascending order (so a Set()).
vpar can contain cycles.
The vertices are all distinct non-0 values which occur in the vpar vector.
The number of these is #vec == vpar_num_childful(vpar).
The opposite, vertices with no children, is vpar_childless_vertices().");
}
vpar_childless_vertices(vpar) = \
vpar_set_complement(vpar, vpar_childful_vertices(vpar));
{
addhelp(vpar_childless_vertices,
"vec = vpar_childless_vertices(vpar)
Return a vector of all vertex numbers which have no children, so
vpar_is_childless(), in ascending order (so a Set()).
vpar can contain cycles.
This is integers among 1..#vpar which do not appear in the vpar vector.
The number of these is #vec == vpar_num_childless(vpar).
vpar can contain cycles.
The opposite, vertices with one or more child, is vpar_childful_vertices().");
}
vpar_num_childful(vpar) =
{
\\ like vpar_num_siblings_sets(), but there 0 as parent in vpar is a set
\\ of siblings (the roots), whereas here 0 as a parent is not a vertex
\\ number
if(#vpar,
my(vec=vpar_INTERNAL_histogram(vpar,0));
vec[1]=0; \\ ignore 0s occurring in vpar
hammingweight(vec);
,
0);
}
{
addhelp(vpar_num_childful,
"num = vpar_num_childful(vpar)
Return the number of vertices in vpar which have one or more children, so
vpar_is_childful(). This is how many different non-0 values occur in the
vpar vector. vpar can contain cycles.
The opposite, vertices with no children, is vpar_num_childless().");
}
vpar_num_childless(vpar) = #vpar - vpar_num_childful(vpar);
{
addhelp(vpar_num_childless,
"num = vpar_num_childless(vpar)
Return the number of childless vertices in vpar, so how many
vpar_is_childless(). This is how many of 1..#vpar don't appear in the vpar
vector. vpar can contain cycles.
vpar can contain cycles.");
}
\\------------------------------------------------------------------------------
\\ Siblings
vpar_siblings_of_vertex(vpar,v) =
{
my(a=vpar[v]);
Vec(select(p->p==a, vpar, 1)); \\ those u with vpar[u] == vpar[v]
}
{
addhelp(vpar_siblings_of_vertex,
"vec = vpar_siblings_of_vertex(vpar,v)
Return a vector of the siblings of vertex v, in ascending order (so a Set()).
vec includes v itself. Roots of a forest are reckoned siblings of each other.
vpar can contain cycles.
Vertex u is a sibling of v when vpar[u]==vpar[v], hence the rule for roots
as siblings (both parents are 0), and for v itself included. There's no
\"are_siblings()\" function since a test vpar[u]==vpar[v] is simple enough.
See vpar_num_siblings_of_vertex() for number of siblings. It doesn't count
v itself so here #vec == vpar_num_siblings_of_vertex(vpar,v) + 1.");
}
vpar_siblings_sets(vpar) =
{
my(pos=vpar_siblings_setnum_vector(vpar),
sizes=vpar_INTERNAL_histogram(pos),
ret=vector(#sizes,i, vector(sizes[i])));
forstep(v=#vpar,1,-1,
my(p=pos[v]);
ret[p][sizes[p]]=v;
sizes[p]--);
ret;
}
{
addhelp(vpar_siblings_sets,
"vec = vpar_siblings_sets(vpar)
Return a vector of the sets of siblings in vpar. Each vec[i] is a vector of
sibling vertex numbers and in ascending order (so a Set()). vec is
vecsort() too, so ordered by smallest sibling of each set (each first
element vec[i][1]).
Roots are reckoned siblings of each other. A vertex which is an only-child
is alone in its siblings set. vpar can contain cycles.
The number of sets is #vec == vpar_num_siblings_sets(vpar). Each vertex is
in exactly one set so total lengths sum(i=1,#vec,#vec[i]) == #vpar. See
vpar_seq_siblings() for sequence of siblings, which is concat(vec).
The number of different vec occurring is the same as
vpar_next_sibling_vectors_count(), since next sibling determines sets, and
vice versa.
See vpar_siblings_of_vertex() for the set containing a particular vertex.
See examples/non-crossing-partitions.gp on sets which occur from ordered
forests.");
}
vpar_num_siblings_sets(vpar) = hammingweight(vpar_INTERNAL_histogram(vpar,0));
{
addhelp(vpar_num_siblings_sets,
"num = vpar_num_siblings_sets(vpar)
Return the number of sets of siblings in vpar.
Roots are reckoned siblings of each other. vpar can contain cycles.
This is how many different parent values occur in the vpar vector. Each
value is the parent of a set of siblings, including 0 as \"no parent\" of
the roots.
Number of sets of siblings is related to number of childful vertices
(vpar_num_childful()). Siblings sets are how many among 0..#vpar occur in
vpar, childful are how many of 1..#vpar occur,
num_siblings_sets == num_childful + (1 if any roots)");
}
vpar_siblings_setnum_vector(vpar) =
{
my(child=vectorsmall(#vpar+1),
ret=vector(#vpar),
upto=0);
for(v=1,#vpar,
my(pos=child[vpar[v]+1]);
ret[v] = if(pos, pos, child[vpar[v]+1]=upto++));
ret;
}
{
addhelp(vpar_siblings_setnum_vector,
"vec = vpar_siblings_setnum_vector(vpar)
Return a vector where vec[v] is the siblings set number containing v.
vpar can contain cycles.
Siblings sets are numbered 1 to vpar_num_siblings_sets(vpar) in order of
their smallest member. Set 1 contains vertex 1, set 2 contains the next
smallest (not in set 1), and so on. This is the order of siblings sets from
vpar_siblings_sets(), so vec[v] is an index into those.
The effect is also like vpar_minmax_sibling_vector(vpar,min) but gaps
between minimum sibling numbers collapsed so values 1..num_siblings_sets.");
}
vpar_seq_siblings(vpar) =
{
my(perm=vpar_INTERNAL_next_sibling_vector(vpar,vpar_INTERNAL_identity_perm,0),
seq=vectorsmall(#vpar),
upto=0);
for(i=1,#vpar,
if(perm[i],
my(v=i, t);
until((v=t)==i, seq[upto++]=v; t=perm[v]; perm[v]=0)));
seq;
}
{
addhelp(vpar_seq_siblings,
"seq = vpar_seq_siblings(vpar)
Return a Vecsmall of the vertex numbers 1..#vpar in order of successive
siblings. The return is Vecsmall since it is a permutation of the integers
1..#vpar. vpar can contain cycles.
The sequence begins with 1 and its siblings in ascending order. Then the
next smallest unseen vertex and its siblings, and so on. This is
vpar_siblings_sets() concatenated. An equivalent definition is to sort by
minimum sibling (per vpar_minmax_sibling_vector()), and by vertex number
among those of same minimum sibling.
Siblings are in ascending order so the sequence is vpar_seq_is_ordered().
A vpar already labelled in this sequence, so seq==[1..#vpar], means
ascending minimum sibling. See vpar_minmax_sibling_vector() on such vpar.");
}
\\------------------------------------------------------------------------------
\\ Num Siblings
vpar_num_siblings_of_vertex(vpar,v) = \
vpar_num_children_of_vertex(vpar,vpar[v])-1;
{
addhelp(vpar_num_siblings_of_vertex,
"num = vpar_num_siblings_of_vertex(vpar,v)
Return the number of siblings of vertex v, not including itself.
In a forest, root vertices are reckoned siblings of each other.
vpar can contain cycles.");
}
vpar_num_siblings_vector(vpar) =
{
my(num_roots=vpar_num_roots(vpar),
num_children=vpar_INTERNAL_num_children_vecsmall(vpar));
vector(#vpar,v, if(vpar[v],num_children[vpar[v]]-1,num_roots-1));
}
{
addhelp(vpar_num_siblings_vector,
"vec = vpar_num_siblings_vector(vpar)
Return a vector where vec[v] is the number of siblings of vertex v, in each
case not including v itself. vpar can contain cycles.
For a forest, root vertices are reckoned siblings of each other.
This is vec[v] = vpar_num_siblings_of_vertex(vpar,v) but calculated all
together.");
}
vpar_is_num_siblings_vector(vec,forest=0) =
{
if(#vec,
\\ root=1 must have no siblings
if(forest<0 && vec[1], return(0));
my(m=vecmax(vec)); \\ largest num siblings
if(m<0 || m >= #vec, return(0));
my(seen=vectorsmall(m+1));
for(v=1,#vec,
if(vec[v]<0, return(0));
seen[vec[v]+1]++);
\\ tree must have a root somewhere with no siblings
forest || seen[1] || return(0);
for(i=1,#seen, if(seen[i] % i, return(0))));
1;
}
{
addhelp(vpar_is_num_siblings_vector,
"bool = vpar_is_num_siblings_vector(vec,forest=0)
vec is a vector of integers. Return 1 if it is a num siblings vector of a
tree, so vec == vpar_num_siblings_vector(vpar) for some vpar tree.
Optional parameter \"forest\" can be 1 to test for vec from a forest, or can
be -1 for vec from a root=1 tree.
A vertex with vec[v]=k many siblings has those k many other siblings as the
same vec[s]=k. So number of entries vec[]==k must be a multiple of k+1.
For a tree, the root has no siblings so there must be some vec[r]==0.
For a root=1 tree that must be vec[1]==0. Such a num siblings vector is
from a vpar by setting successive sets of k+1 siblings as children of one of
the preceding set, and for a tree starting root as one of the k=0.");
}
vpar_num_siblings_vectors_count(n,forest=0) =
{
\\ a[] is C() in the recurrence of the addhelp.
\\ a[1] is C for n=0.
\\ This index start means a[i-m*k] doesn't need to watch for i-m*k==0.
\\ Initial a[] is for k=1 which is all 1s.
\\ Then loop stepping up to C(n,k) derived from C(n,k-1) terms.
\\ The loop goes high to low since term n depends on various =0 || !n, n++); \\ no inc for root=1, except when n==0
forest=1+!forest; \\ 2 for tree, 1 for forest and root=1
my(a=vector(n,i,1));
for(k=2,n,
forstep(i=n,1,-1,
a[i] += sum(m=1,(i-forest)\k, binomial(i-1,m*k) * a[i-m*k])));
a[n];
}
{
addhelp(vpar_num_siblings_vectors_count,
"count = vpar_num_siblings_vectors_count(n,forest=0)
Return the number of different vpar_num_siblings_vector() which occur for
trees of n vertices, so how many vectors vpar_is_num_siblings_vector().
Optional parameter \"forest\" can be 1 for number occurring from forests, or
can be -1 for number from root=1 trees.
As described in vpar_is_num_siblings_vector(), sets of k many siblings must
occur in multiples of k. So a recurrence on multiple m, starting from k=n,
C(n,k) = if(n==0 || k==1, 1,
sum(m=0,floor((n-!forest)/k), binomial(n,m*k) * C(n-m*k,k-1)))
F(n) = forest=1; C(n,n) \\\\ forests
= 1, 1, 2, 5, 13, 42, 150, 576, ... (A178682)
T(n) = forest=0; C(n,n) \\\\ trees
= 1, 1, 1, 4, 11, 31, 132, 484, ...
Root=1 trees are F(n-1) since the root is a fixed 0 siblings and forest
below. The trees case runs m up to only floor((n-1)/k) so as to reach k=1
with n>=1 so there is at least one set of 1 sibling.");
}
\\ GP-DEFINE C(n,k) = if(n==0 || k==1, 1, \
\\ GP-DEFINE sum(m=0,(n-!forest)\k, binomial(n,m*k) * C(n-m*k,k-1)));
\\ GP-DEFINE F(n) = local(forest=1); C(n,n);
\\ GP-DEFINE T(n) = local(forest=0); C(n,n);
\\ GP-Test vector(8,n,n--; F(n)) == [1, 1, 2, 5, 13, 42, 150, 576]
\\ GP-Test vector(8,n,n--; T(n)) == [1, 1, 1, 4, 11, 31, 132, 484]
\\ not in OEIS: 4, 11, 31, 132, 484
\\------------------------------------------------------------------------------
\\ Sibling Position
vpar_siblingpos_of_vertex(vpar,v,down=0) =
{
my(p=vpar[v]);
sum(i=if(down,v+1,1),if(down,#vpar,v-1), vpar[i]==p);
}
{
addhelp(vpar_siblingpos_of_vertex,
"pos = vpar_siblingpos_of_vertex(vpar,v,down=0)
Return the position of vertex v among its siblings. Position 0 is the
smallest vertex number, 1 the next bigger, etc, so position is the number of
siblings smaller than v. vpar can contain cycles.
Optional parameter \"down\" can be 1 to number positions downwards so 0 is
the biggest sibling, 1 the second biggest, etc. This is the number of
siblings bigger than v.
For a forest, roots are reckoned siblings of each other and their position
accordingly. In a tree the sole root is position 0.
Sum of position up and down is total number of siblings of v (and not
including v itself),
sum(down=0,1, vpar_siblingpos_of_vertex(vpar,v,down))
== vpar_num_siblings_of_vertex(vpar,v)");
}
vpar_siblingpos_vector(vpar,down=0) =
{
my(upto=vectorsmall(#vpar+1,v,-1), \\ ready for pre-increment to 0 first
vec=vector(#vpar));
forstep(v=if(down,#vpar,1),if(down,1,#vpar),if(down,-1,1),
vec[v]=upto[vpar[v]+1]++);
vec;
}
{
addhelp(vpar_siblingpos_vector,
"vec = vpar_siblingpos_vector(vpar,down=0)
Return a vector where vec[v] is the position of vertex v among its siblings.
This is vec[v] = vpar_siblingpos_of_vertex(vpar,v) but calculated
all together. vpar can contain cycles.
See vpar_is_siblingpos_vector() to test for such a vec, or
vpar_siblingpos_vectors_count() for how many occur.");
}
vpar_is_siblingpos_vector(vec,down=0,forest=0) =
{
\\ seen[p] is how many of vec[i]+1 have been seen so far.
\\ On incrementing seen[p] check it does not exceed seen[p-1].
\\
\\ For root=1 the initial check is vec[2]==0 for up or vec[1]==0 for down.
\\ The up case checks vec[1]==0 too in the main loop. When #vec==1 the
\\ index 1 + (#vec>=2 && !down) makes it check vec[1] there anyway.
\\ The down case could loop just #vec to 2 since vec[1] is already
\\ checked, but not worth extra code to shorten that.
\\
\\ m = largest pos is at most #vec-1 for a forest (all singletons), or
\\ #vec-2 for a tree (star-n) except singleton #vec-1=1. So too big is
\\ m>=#vec or >=#vec-1.
\\ print("vpar_is_siblingpos_vector() "vec" down="down" forest="forest);
if(#vec,
\\ root=1 conditions
if(forest<0 && vec[1 + (#vec>=2 && !down)], return(0));
my(m=vecmax(vec)); \\ largest pos
if(m<0 || m >= #vec-(forest<=0 && #vec>=2), return(0));
my(seen=vectorsmall(m+1));
forstep(i=if(down,#vec,1),if(down,1,#vec),if(down,-1,1),
my(p=vec[i]);
if(p<0, return(0));
seen[p+1]++;
if(p>0 && seen[p+1] > seen[p], return(0)));
forest>0 || #seen<2 || seen[1]>seen[2];
,
1);
}
{
addhelp(vpar_is_siblingpos_vector,
"bool = vpar_is_siblingpos_vector(vec,down=0,forest=0)
vec is a vector of integers. Return 1 if it is a sibling positions vector
of a tree, so vec == vpar_siblingpos_vector(vpar,down) for some vpar tree.
Optional parameter \"down\" can be 1 to test for vec from positions
downwards as per the down parameter of vpar_siblingpos_vector().
Optional parameter \"forest\" can be 1 to test for vec from a forest, or can
be -1 for vec from a root=1 tree.
The test is all pos=vec[i] >= 0, and then a \"ballot sequence\". Proceeding
1 to #vec, the number of pos seen must be >= the number of pos+1 seen at all
times so that there are enough open pos positions for the vertices of pos+1.
For a tree, the final number of pos=0 must be > number of pos=1. This is
the root as a pos=0 with no further sibling. For root=1, further must have
vec[2]==0 so that 2 not a sibling of 1.
For down=1, the ballot sequence goes from #vec down to 1 with the same rules
except that root=1 must have vec[1]==0 so it is not a sibling of anything
else.
See vpar_siblingpos_vectors_count() on how many vec are siblingpos vectors.");
}
\\ vec is a vector of row lengths of a Young tableau, so descending terms
\\ vec[i]>=vec[i+1]. Total squares in the tableau is n=vecsum(vec).
\\ Return the number of ways this shape can be filled with integers 1..n in
\\ the usual way rows increasing and columns increasing, calculated by
\\ n! divided by all the "hook lengths".
\\
vpar_INTERNAL_tableau_shape_count(vec) =
{
vecsum(vec)!
/ prod(i=1,#vec, \\ row
prod(j=1,vec[i], \\ column
1 + vec[i]-j + sum(k=i+1,#vec, vec[k]>=j)));
}
vpar_siblingpos_vectors_count(n,forest=0) =
{
if(n<=1, 1,
forest==0,
my(ret=0);
forpart(p=n-1,
my(vec=Vecrev(p)); vec[1]++;
ret += vpar_INTERNAL_tableau_shape_count(vec));
ret;
,
if((n+=(forest>0)-2) <= 1, 2-!n,
my(a=2,b=4);
for(i=3,n, my(c=b + i*a); a=b;b=c);
b));
}
{
addhelp(vpar_siblingpos_vectors_count,
"count = vpar_siblingpos_vectors_count(n,forest=0)
Return the number of different vpar_siblingpos_vector() which occur for
trees of n vertices, so how many vpar_is_siblingpos_vector(), with parameter
down=0 there. By symmetry the number of down=1 is the same.
Optional parameter \"forest\" can be 1 for number occurring from forests, or
can be -1 for number from a root=1 tree.
For forests, the count is the number of ballot sequences which is the number
of involutions (self-inverse permutations, swaps and fixed only). This has
many formulas. The current code uses the recurrence by Rothe
F(n) = F(n-1) + (n-1)*F(n-2) starting F(0)=F(1)=1
= 1, 1, 2, 4, 10, 26, 76, ... (A000085)
This is involutions as first permutation element fixed and rest F(n-1), or
first paired with one of the n-1 others and rest F(n-2). The count for
root=1 trees is F(n-1) since fixed vertex 1 pos=0 and below it a forest.
For trees, the count is number of ballot sequences where the first candidate
(pos=0) is an outright winner (not a tie with the second). This is the root
as a pos=0 with no pos=1 sibling.
T(n) = 1, 1, 1, 3, 7, 20, 56, ... (A238124)
Is there a good recurrence etc for this? The current code loops over
partitions (forpart()) with biggest incremented and sum number of Young
tableau of that shape using the hook length formula. The number of
partitions grows quickly with n so this is suitable only for medium size
n.");
}
\\ GP-DEFINE F(n) = if(n<=1,1, F(n-1) + (n-1)*F(n-2));
\\ GP-Test vector(7,n,n--; F(n)) == [1, 1, 2, 4, 10, 26, 76]
\\ vector(7,n,n--; vpar_siblingpos_vectors_count(n))
\\-----------------------------------------------------------------------------
\\ Next Sibling and Previous Sibling
vpar_next_sibling_of_vertex(vpar,v,down=0) =
{
\\ Search for next after or before v which has same parent as v.
\\ upwards: v+1 to #vpar by 1
\\ downwards: v-1 to 1 by -1
my(p=vpar[v]);
forstep(i=v+1-(down<<1), if(down,1,#vpar), if(down,-1,1),
if(vpar[i]==p, return(i)));
0;
}
{
addhelp(vpar_next_sibling_of_vertex,
"s = vpar_next_sibling_of_vertex(vpar,v,down=0)
Return the next greater sibling of v, or return 0 if v has no greater
siblings. vpar can be a forest and roots are reckoned as siblings of each
other. vpar can contain cycles.
Optional parameter \"down\" can be 1 to return next smaller sibling or
return 0 if v has no smaller sibling.");
}
\\ func is a closure, either
\\ vector for vpar_next_sibling_vector(),
\\ vpar_INTERNAL_identity_perm for vpar_siblings_cycles()
\\ This starts the return as an all 0s vector or a perm of all loops.
\\ In both cases appending second and subsequent children to the first
\\ makes chain or cycles.
\\
vpar_INTERNAL_next_sibling_vector(vpar,func,down) =
{
\\ prev_child[p+1] is the previously seen child of parent p, including
\\ p==0 for previously seen root.
\\ forstep() increment made from "down" arithmetically so as to catch
\\ mistake passing a symbol (unbound variable) instead of 0 or 1.
\\
my(ret=func(#vpar),
prev_child=vectorsmall(#vpar+1));
forstep(v=if(down,#vpar,1),
if(down,1,#vpar),
1-(down<<1), \\ down=0 step=1, down=1 step=-1
my(p=vpar[v]+1);
if(prev_child[p],
ret[v] = ret[prev_child[p]];
ret[prev_child[p]] = v);
prev_child[p] = v);
ret;
}
vpar_next_sibling_vector(vpar,down=0) = \
vpar_INTERNAL_next_sibling_vector(vpar,vector,down);
{
addhelp(vpar_next_sibling_vector,
"vec = vpar_next_sibling_vector(vpar,down=0)
Return a vector of the next greater sibling of v, or 0 if v has no greater
sibling. This is vec[v] = vpar_next_sibling_of_vertex(vpar,v,down) but
calculated all together. vpar can contain cycles.
vpar can be a forest and the roots are reckoned siblings of each other.
Optional parameter \"down\" can be 1 to get the next smaller sibling.
The returned vec can be treated as a new vpar. It is a forest comprising
paths of siblings going upwards smallest to biggest, or instead downwards
for down=1.
See vpar_is_next_sibling_vector() to characterize the vectors returned.");
}
vpar_is_next_sibling_vector(vec,down=0,forest=0) =
{
\\ end1 is 2 when down and root=1, so that s==1 is disallowed (root=1 must
\\ not have a sibling).
\\ forstep() increment made from "down" arithmetically so as to catch
\\ mistake passing a symbol (unbound variable) instead of 0 or 1.
if(#vec,
if(forest<0 && vec[1], return(0)); \\ root=1 must have v=1 no sibling
my(seen=vectorsmall(#vec),
singles=0,
step=1-(down<<1), \\ down=0 step=1, down=1 step=-1
end =if(down,1,#vec),
end1=if(down&&forest<0, 2, end));
forstep(v=if(down,#vec,1), end, step,
my(s=vec[v]);
if(s,
if( lex(s,v)!=step \\ want s>v up, s=1or2 down
|| seen[s]++>1, \\ not previously seen
return(0)),
seen[v] || singles++)); \\ s==0 and unseen v is single
forest || singles; \\ tree must have >=1 singles
,
1);
}
{
addhelp(vpar_is_next_sibling_vector,
"bool = vpar_is_next_sibling_vector(vec,down=0,forest=0)
vec is a vector of integers. Return 1 if it is a next siblings vector of a
tree, so vec == vpar_next_sibling_vector(vpar,down) for some vpar tree.
Optional parameter \"down\" can be 1 to test for vec of next sibling
downwards, per the down parameter of vpar_siblingpos_vector().
Optional parameter \"forest\" can be 1 to test for vec from a forest, or can
be -1 for vec from a root=1 tree.
The test is each sibling s=vec[v] must be bigger then v, so v=1 single-element
sets. This is an alternating sum of the Bell numbers,
T(n) = B(n) - B(n-1) + B(n-2) - ... trees
= 1,1,1,4,11,41,162,... (A000296)
T(n) is implemented by working up through a vector of Stirling numbers of
the second kind since their row sum is Bell number B(i). OEIS A000296 is
defined as n+1 into sets all size>=2. That corresponds to n into sets at
least one size=1 by taking the set containing n+1 and making its others into
singles, or vice versa collecting singles among n into a set with new extra
n+1. The alternating sum is then T(n) = B(n) - T(n-1) which is B all
partitions less those all size>=2.");
}
\\ GP-DEFINE B(n) = sum(k=0,n, stirling(n,k,2));
\\ GP-DEFINE T(n) = if(n==0,1, sum(i=1,n, (-1)^(n-i)*B(i)));
\\ GP-Test vector(7,n,n--; B(n)) == [1,1,2,5,15,52,203]
\\ GP-Test vector(7,n,n--; T(n)) == [1,1,1,4,11,41,162]
vpar_minmax_sibling_vector(vpar,func) =
{
\\ child[p+1] is min or max child of p, including p==0 for min or max root.
\\ This is like vpar_minmax_child_vector() but includes the roots.
\\ The return is a vecextract() of childp[ by vparp[, but +1 in each of vpar.
my(child=vectorsmall(#vpar+1));
if(#vpar,
my(end=func(1,#vpar)); \\ 1 for min, #vpar for max
forstep(v=#vpar+1-end, end, func(1,-1), \\ step 1 for max, -1 for min
child[vpar[v]+1]=v));
vector(#vpar,v, child[vpar[v]+1]);
}
{
addhelp(vpar_minmax_sibling_vector,
"vec = vpar_minmax_sibling_vector(vpar,func)
Return a vector where vec[v] is the minimum or maximum sibling of v, or v
itself it is respective minimum or maximum of its siblings.
\"func\" parameter is min for smallest sibling or max for biggest sibling,
vec = vpar_minmax_sibling_vector(vpar,min)
vec = vpar_minmax_sibling_vector(vpar,max)
For min, vec[v] is location in vpar of first occurrence of value vpar[v].
For max, vec[v] is location in vpar of last occurrence of value vpar[v].
In both cases that occurrence might be at v itself. vpar can contain cycles.
The number of different vec occurring is vpar_next_sibling_vectors_count(),
since next, min, or max, can all be derived from each other.
vpar has vec increasing with vertex number when all siblings are consecutive
vertex numbers. The number of such trees and forests is
T(n) = if(n==0,1, sum(k=1,n, Narayana(n-1,k-1)*k!)) trees
= 1, 1, 2, 8, 44, 302, ...
F(n) = sum(k=0,n, Narayana(n,k)*k!) forests
= 1, 1, 3, 13, 73, 501, ... (A000262)
where
Narayana(n,k) = if(k==0, (n==0),
binomial(n,k-1)*binomial(n-1,k-1)/k) \\\\ A001263
Narayana numbers have several interpretations. One of them is count ordered
forests of n vertices of which k childless. This is also how many with s
many sets of siblings, since s+k==n+1 (per vpar_num_siblings_sets()) and
reverse Narayana(n,n-k+1)==Narayana(n,k). The order the sets are taken for
labelling is then k! ways. Trees are a root above an n-1 forest.");
}
\\ GP-DEFINE Narayana(n,k) = if(k==0,(n==0), \
\\ GP-DEFINE binomial(n,k-1)*binomial(n-1,k-1)/k);
\\ GP-DEFINE F(n) = sum(k=0,n, Narayana(n,k)*k!);
\\ GP-DEFINE T(n) = if(n==0,1, sum(k=1,n, Narayana(n-1,k-1)*k!));
\\ GP-Test vector(6,n,n--; F(n)) == [1, 1, 3, 13, 73, 501]
\\ GP-Test vector(6,n,n--; T(n)) == [1, 1, 2, 8, 44, 302]
\\-----------------------------------------------------------------------------
\\ Horizpos, Diagdepths
\\ cumul = vpar_INTERNAL_ancestor_cumulative(vpar,vec,d)
\\ vec[] is a vector of length #vpar with each vec[v]>=0.
\\ Return a vector where cumul[v] = sum vec[v], vec[parent of v], etc, for
\\ all ancestors of v, including v itself.
\\ d>=0 is an extra quantity add when going up each depth level.
\\
\\ For example, vpar_depths_vector() would be vec=all zeros and d=1.
\\ Or instead vec=all ones and d=1 would be each cumul[v] = depth+1.
\\
vpar_INTERNAL_ancestor_cumulative(vpar,vec,d) =
{
\\ ret[v]=-1 when unset. At such a v, sum upwards (in t) until reaching
\\ the root or a ret[] already set.
\\ While working upwards, set ret[]=-2 for in-progress.
\\ If find a ret[] == -2 then this is a cycle.
\\ Re-traverse upwards to fill, with ret[v]=t and then t decreasing.
\\ For cyclic, no decreases in the cycle, all of it is cycle total.
\\ The special -1 and -2 mean cannot have negatives in vec[].
\\ Could go to other values or private symbols or something if ever wanted
\\ negatives.
\\ print("vpar_INTERNAL_ancestor_cumulative() "vpar" "vec" d="d);
my(ret=vector(#vpar,i,-1));
for(i=1,#vpar,
if(ret[i]<0,
\\ print(" i="i);
my(v=i,
t=0, \\ total
c=0);
while(1,
ret[v] = -2;
t += vec[v];
(v=vpar[v]) || break;
if(ret[v]==-2,
\\ print(" stop for cycle");
c=v; break);
t += d;
if(ret[v]>=0,
\\ print(" stop for found above");
c=v;
t += ret[v]; break);
);
\\ print(" t="t" at v="v" ret="if(v,ret[v])" c="c);
v=i;
while(v!=c,
\\ print(" set v="v" t="t); ret[v]==-2 || error();
ret[v] = t;
t -= vec[v] + d;
v=vpar[v]);
if(v,
while(ret[v]<0,
\\ print(" set cyclic v="v" t="t); ret[v]==-2 || error();
ret[v] = t;
v=vpar[v]));
));
ret;
}
vpar_horizpos_vector(vpar,down=0) = \
vpar_INTERNAL_ancestor_cumulative(vpar, vpar_siblingpos_vector(vpar,down), 0);
{
addhelp(vpar_horizpos_vector,
"vec = vpar_horizpos_vector(vpar,down=0)
Return a vector where vec[v] = vertex v horizontal position.
Horizontal position is total siblingpos (vpar_siblingpos_of_vertex()) of v
and its ancestors.
With the tree imagined drawn first child vertical and each sibling
horizontal to the right, this is the horizontal position of each v.
In the natural correspondence to binary trees, horizontal position is how
many binary tree right edges are on the path down to v.
Optional parameter \"down\" can be 1 to count each siblingpos from last
sibling, the same as \"down\" in vpar_siblingpos_of_vertex(). The effect is
horizontal position measured leftward from the last root.
vpar can contain cycles. Ancestors of v are everything reachable upwards,
per vpar_is_ancestor(). Horizpos is total siblingpos of them. For vertices
in a cycle, this means all have the same total siblingpos of that cycle.");
}
\\ Test code counts various increasing,decreasing:
\\ Some vpar have horizpos[] in ascending order. The number of such forests
\\ is vpar_count_min_descendant_increasing().
vpar_diagdepths_vector(vpar,down=0) = \
vpar_INTERNAL_ancestor_cumulative(vpar, vpar_siblingpos_vector(vpar,down), 1);
{
addhelp(vpar_diagdepths_vector,
"vec = vpar_diagdepths_vector(vpar,down=0)
Return a vector where vec[v] = vertex v diagonal depth.
Diagonal depth is depth plus horizontal position (vpar_horizpos_vector()),
so vec = vpar_depths_vector(vpar) + vpar_horizpos_vector(vpar).
With the tree imagined drawn first child vertical and each sibling
horizontal, this is the diagonal containing each v.
In the natural correspondence to binary trees, diagonal depth is total
binary tree depth (left edges are depth, right edges are horizpos).
Optional parameter \"down\" can be 1 to count horizpos with the down
parameter of vpar_horizpos_vector(). The effect is to measure down the
opposite diagonal.
vpar can contain cycles per vpar_depths_vector() and vpar_horizpos_vector().
For vertices in a cycle, all have the same depth+horizpos of that cycle.");
}
vpar_is_horizpos_vector(vec,forest=0) =
{
\\ For root=1, that root is skipped by starting the loop at 2. This means
\\ it is not a small pos=0 allowing big pos=1, but instead will need some
\\ later vec[i]=0.
\\ Note root=1 doesn't demand vec[2]==0. Child of the root can be later,
\\ 1
\\ horizpos = [0,1,0,1] | \
\\ 3 .. 4
\\ |
\\ 2
\\ print("vpar_is_horizpos_vector() "vec" forest="forest);
if(#vec,
forest>=0 || vec[1]==0 || return(0); \\ root=1 must have v=1 pos=0
my(len=vecmax(vec));
\\ print(" len="len);
\\ maximum pos range 0 to n-1 forest, 0 to n-2 tree
len < max(1,#vec-(forest<=0)) || return(0);
\\ seen[pos+2] is how many vec[i]==pos have been seen
\\ good[pos+1] != 0 when there is good overlap between pos and pos+1
\\ meaning some vec[i]==pos and vec[j]==pos+1 with j>i
my(seen=vectorsmall(len+2),
good=vectorsmall(len+1));
for(i=2-(forest>=0), #vec,
vec[i]>=0 || return(0); \\ must pos=vec[i] >= 0
if(seen[vec[i]+1], \\ test pos - 1
good[vec[i]+1]=1);
seen[vec[i]+2]++);
if(forest<0, seen[2]++); \\ vec[1]=0 for root=1 tree
\\ print(" seen="Vec(seen)" "hammingweight(seen)" want "len+1);
\\ print(" good="Vec(good)" "hammingweight(good)" want "len);
\\ tree #vec>=2 must have >=2 many vec[i]==0, since one is the root
\\ which cannot have siblings
forest || seen[2]>(#vec>=2) || return(0);
\\ want seen all pos = 0 to len inclusive
\\ want good all pos = 0 to len-1 inclusive
hammingweight(seen)==len+1 && hammingweight(good)==len;
,
1
);
}
{
addhelp(vpar_is_horizpos_vector,
"bool = vpar_is_horizpos_vector(vec,forest=0)
vec is a vector of integers. Return 1 if it is a horizontal positions
vector of a tree, so vec == vpar_horizpos_vector(vpar) for some vpar tree.
Optional parameter \"forest\" can be 1 to test for vec from a forest, or can
be -1 to test vec from a root=1 tree.
Each horizpos in 1 <= pos <= vecmax(vec) must occur as sibling of a smaller
vertex number. So must have some vec[i]=pos-1 and vec[j]=pos with ii
then the others can be a path down under j. A forest can be made in this
form by a chain down of all the pos=0 from biggest down to smallest v. Then
sibling of that v is biggest of pos=1 and chain down to smallest of pos=1,
etc.
For trees >=2 vertices, there must be >=2 vertices pos=0 since one is the
root and cannot have a sibling after. For root=1 must have vec[1]==0 as the
root with no sibling, which means only i>=2 available to satisfy the i perm[2] to have >=2 vertices of pos=0. All perms are in
pairs of first entries big/small or small/big, hence half n!/2.");
}
\\ GP-Test vector(6,n,n--; n!) == [1,1,2,6,24,120]
\\ GP-Test vector(6,n,n--; if(n==0,1,(n-1)!)) == [1,1,1,2,6,24]
\\ GP-Test vector(6,n,n--; if(n<=1,1,n!/2)) == [1,1,1,3,12, 60]
vpar_is_preorder_horizpos_vector(vec,forest=0) =
{
\\ print("vpar_is_preorder_horizpos_vector() "vec" forest="forest);
if(#vec,
vec[1]==0 || return(0);
my(len=vecmax(vec));
\\ print(" len="len);
\\ maximum pos range 0 to n-1 forest, 0 to n-2 tree
len < max(1,#vec-!forest) || return(0);
\\ pos[k+2] = next allowed pos of a child of the vertex at depth k
my(pos=vectorsmall(#vec+1),
d=1);
for(i=2-forest,#vec,
\\ print(" i="i" pos "vec[i]" onto "Vec(pos[1..d]));
vec[i]>=0 || return(0);
forstep(j=d,1,-1,
\\ print(" cf j="j" pos "pos[j]);
if(vec[i]==pos[j],
\\ print(" add sibling");
pos[d=j]=vec[i]+1;
pos[d++]=vec[i];
next(2)));
\\ print(" no predecessor");
return(0));
);
\\ print(" yes");
1;
}
{
addhelp(vpar_is_preorder_horizpos_vector,
"bool = vpar_is_preorder_horizpos_vector(vec,forest=0)
vec is a vector of integers. Return 1 if it is a horizontal positions
vector of a preorder labelled tree, so vec == vpar_horizpos_vector(vpar)
for some tree vpar_is_preorder(vpar).
Optional parameter \"forest\" can be 1 to test for vec from a preorder
forest.
The test is as follows: When vec[v]==vec[v-1], v is child of v-1. Otherwise
vec[v] must be the next child of some ancestor, so its last child pos + 1.
A stack of last child pos at each depth is maintained and the deepest one
satisfying vec[v] is used.
In the full vpar_is_horizpos_vector(), any preceding vertex can be the
parent of v (where the next child pos suits), but here the preorder
restriction means only the last of each higher depth level is permitted.");
}
\\-----------------------------------------------------------------------------
\\ Neighbours
vpar_is_neighbour(vpar,u,v) = u==vpar[v] || v==vpar[u];
{
addhelp(vpar_is_neighbour,
"bool = vpar_is_neighbour(vpar,u,v)
Return 1 if u and v are neighbours, or 0 if not.
This means an edge between u and v, so u parent of v, or v parent of u.
vpar can contain cycles.
A self-loop vertex is its own neighbour.");
}
vpar_neighbours_of_vertex(vpar,v) = \
concat(if(vpar[v],[vpar[v]],[]),vpar_children_of_vertex(vpar,v));
{
addhelp(vpar_neighbours_of_vertex,
"vec = vpar_neighbours_of_vertex(vpar,v)
Return a vector of neighbours of v in vpar as [parent,child1,child2,...].
The parent is first if v has a parent, otherwise omitted.
Children, if any, are in ascending order the same as
vpar_children_of_vertex().
Length #vec == vpar_degree_of_vertex(vpar,v), how many edges at v.
vpar can contain cycles. If v is a self-loop or in a 2-cycle then parent is
the same as one of the children. vec still has parent first and children
after. This means a duplicate if self-loop or 2-cycle, but ensures
#vec==degree in all cases.");
}
vpar_neighbours_vector(vpar) =
{
my(vecs=vpar_children_vector(vpar));
vector(#vpar,v, if(vpar[v], concat(vpar[v],vecs[v]), vecs[v]));
}
{
addhelp(vpar_neighbours_vector,
"vec = vpar_neighbours_vector(vpar)
Return a vector where vec[v] is a vector of the neighbour vertices of v.
Each vec[v] has parent first, if v has a parent, then the children in
increasing order.
This is vec[v] = vpar_neighbours_of_vertex(vpar,v) calculated all together.");
}
\\-----------------------------------------------------------------------------
\\ Degrees (Number of Neighbours)
vpar_degree_of_vertex(vpar,v) = \
vpar_num_children_of_vertex(vpar,v) + sign(vpar[v]);
{
addhelp(vpar_degree_of_vertex,
"degree = vpar_degree_of_vertex(vpar,v)
Return the degree of vertex v in vpar.
This is the number of neighbours of v, ie. how many children, and its parent
if it has a parent.
vpar can contain cycles. A self-loop a parent and child so degree 2,
likewise each vertex of a 2-cycle. In these cases the parent and child are
the same vertex, but reckoning degree 2 ensures sum(degrees) = 2*num_edges
always. This can be thought of as each edge having 2 ends and degree of a
vertex is how many edges end at it.");
}
vpar_degrees_vector(vpar) =
{
my(vec=vector(#vpar), p);
for(v=1,#vpar, if(p=vpar[v], vec[v]++; vec[p]++));
vec;
}
{
addhelp(vpar_degrees_vector,
"vec = vpar_degrees_vector(vpar)
Return a vector where vec[v] is the degree of vertex v.
This is the number of neighbours of v, ie. its children and its parent if it
has a parent.
This is vec[v] = vpar_degree_of_vertex(v), but calculated all together.
vpar can contain cycles. See vpar_degree_of_vertex() on the treatment of
self-loops and 2-cycles.");
}
\\ 2*n-2 == vecsum for tree lex=0 lex=-1 bad
\\ 2*n-2 >= vecsum for forest lex=0 or 1 lex=-1 bad
\\
vpar_is_degrees_vector(vec,forest=0) =
{
\\ In a full Vec, vector [0]==0 and []==0 which would suit the #vec<=1
\\ case. But Vecsmall [] and [0] are not ==0. So write the tests with
\\ vec[1]==0 so as not to silently and subtly give the wrong answer if
\\ vec is a Vecsmall.
\\
\\ However, vec=Vecsmall errors out for the rest anyway, since vecsum()
\\ circa GP 2.13.1 doesn't accept Vecsmall. Taking the vecsum() is fairly
\\ fundamental to the test, so for now don't really want to try to
\\ workaround that. (Even though a degrees vector as a partition means
\\ that Vecsmall from partitions() or forpart() would be reasonable ...)
\\
if(#vec<=1, #vec==0 || vec[1]==0, \\ vec==[] or vec==[0]
vecmin(vec) < (forest<1), 0, \\ must min 1 for tree, 0 for forest
forest<=0, vecsum(vec) == (#vec-1)<<1, \\ tree, vecsum equality
\\ forest
my(m=hammingweight(vec), s);
m==0 || (bitnegimply(1,s=vecsum(vec)) && s <= (m-1)<<1));
}
{
addhelp(vpar_is_degrees_vector,
"bool = vpar_is_degrees_vector(vec,forest=0)
vec is a vector of integers. Return 1 if it is a degrees vector of a tree,
so vec == vpar_degrees_vector(vpar) for some vpar tree.
Optional parameter \"forest\" can be 1 to test for degrees from a forest.
Or it can be -1 for vector from a root=1 tree (which is the same as the tree
case since degrees don't depend on the root).
For a tree, the test is vec==[] empty tree, or vec==[0] singleton, or
otherwise all vec[i]>=1 and vecsum(vec) == 2*(#vec-1) for the usual
sum(degrees) = 2*num_edges.
For a forest, the test is all vec[i]>=0, and vecsum(vec) even and <= 2*(m-1)
where m is the number of non-zero vec entries. A forest gives this form
since non-zero entries are from one or more component trees and within each
sum(degrees) = 2*(size - 1) as above. Conversely, a vec of these conditions
is a forest made by a chain of the degree>=2 entries leaving sum(deg-2)+2
many endpoints. The degree=1 entries are those endpoints and their
deg-2 = -1 each. Net endpoints left is, taking out the -2 each as -2*m,
then vecsum(vec) - 2*m + 2 which the condition ensures <=0. When <0 there
are extra degree=1s which can be paired up. vecsum even ensures there is an
even number of them. Notice the test uses the number of non-zeros m not
all n. If it used 2*(n-1) then that would treat the degree=0 vertices as
degree - 2 = -2 endpoints, which is not so.");
}
vpar_degrees_vectors_count(n,forest=0) =
{
\\ "forest" parameter tested by <1 so as to provoke an error if mistakenly
\\ a symbol instead of flag -1,0,1.
\\
\\ 1 + sum(m=2,n, binomial(n,m) * sum(c=0,m\2-1, binomial(2*m-3-2*c, m-1)));
if(forest<1,
if(n, binomial((n<<1)-3,n-1), 1); \\ trees, and root=1 trees the same
,
\\ forests
1 + sum(m=2,n,
binomial(n,m) * sum(c=1,m>>1, binomial(((m-c)<<1)-1, m-1))));
}
{
addhelp(vpar_degrees_vectors_count,
"count = vpar_degrees_vectors_count(n,forest=0)
Return the number of different vpar_degrees_vector() which occur for
trees of n vertices, so how many vectors are vpar_is_degrees_vector().
Optional parameter \"forest\" can be 1 for number occurring from forests.
Or can be -1 for number from root=1 trees (which is the same as all trees
since degrees don't depend on the root).
For a tree, the return is
T(n) = if(n==0,1, binomial(2*n-3,n-1))
= 1, 1, 1, 3, 10, 35,... (A001700)
As described in vpar_is_degrees_vector(), a tree degrees vector is a
composition (partition with order) of n elements each >=1 summing to 2*n-2.
Subtracting 1 is each >=0 sum n-2 and that is counted by binomial of 2*n-3
positions choose n-1 separators leaving n runs total length n-2, including
possibly empty runs.
For a forest, the return is
F(n) = 1 + sum(m=2,n, binomial(n,m) * C(m))
= 1, 1, 2, 7, 30, 136, ...
where C(m) = sum(c=0,floor(m/2)-1, binomial(2*m-3-2*c, m-1))
= 0, 0, 1, 3, 11, 40, 148, ... (A014301)
F follows from the degrees form described in vpar_is_degrees_vector().
Firstly 1 all singletons. Then m many degree!=0 vertices and binomial to
choose which they are. C(m) is the number of degrees vectors with all
degrees non-zero. For it, a single component c=0 is a tree sum 2*m-3 the
same as above. Two components c=1 is one less edge so sum 2*m-5 by a
corresponding binomial, and so on down while 2*m-3-2*c >= m-1. The binomial
top is odd and runs down to m or m-1 whichever is odd.");
}
\\ GP-DEFINE T(n) = if(n==0,1, binomial(2*n-3,n-1));
\\ GP-Test vector(6,n,n--; T(n)) == [1,1,1,3,10,35]
\\ GP-DEFINE F(n) = 1 + sum(m=2,n, binomial(n,m) * C(m));
\\ GP-DEFINE C(m) = sum(c=0,floor(m/2)-1, binomial(2*m-3-2*c, m-1));
\\ GP-Test vector(6,n,n--; F(n)) == [1, 1, 2, 7, 30, 136]
\\ GP-Test vector(7,m,m--; C(m)) == [0, 0, 1, 3, 11, 40, 148]
\\
\\ vector(8,n,n++; F(n))
\\ not in OEIS: 2, 7, 30, 136, 629, 2941, 13858, 65698
\\------------------------------------------------------------------------------
\\ Leaf Vertices (Degree=1)
vpar_is_leaf(vpar,v) =
{
my(neighbours=sign(vpar[v])); \\ 0 or 1 for parent
for(i=1,#vpar, vpar[i]!=v || neighbours++<=1 || return(0)); \\ pre-incr
1;
}
{
addhelp(vpar_is_leaf,
"bool = vpar_is_leaf(vpar,v)
Return 1 if v is a leaf vertex, or 0 if not.
A leaf vertex has 0 or 1 neighbours.
This is the same as vpar_degree_of_vertex(vpar,v)<=1, but a non-leaf can be
detected earlier, once degree>1 seen.
vpar can contain cycles. A self-loop is degree 2 so is not a leaf.");
}
vpar_leaves(vpar) = Vec(select(n->n<=1, vpar_degrees_vector(vpar), 1));
{
addhelp(vpar_leaves,
"vec = vpar_leaves(vpar)
Return a vector of the leaf vertex numbers of vpar.
These are vertices vpar_is_leaf() so 0 or 1 neighbours, degree<=1.
vpar can contain cycles. A self-loop is degree 2 so is not a leaf.");
}
vpar_num_leaves(vpar) =
{
my(degrees=vpar_degrees_vector(vpar));
sum(v=1,#vpar, degrees[v]<=1);
}
{
addhelp(vpar_num_leaves,
"num = vpar_num_leaves(vpar)
Return the number of leaf vertices in vpar, so how many vpar_is_leaf(),
those being vertices 0 or 1 neighbours, degree<=1.
vpar can contain cycles. A self-loop is degree 2 so is not a leaf.");
}
\\------------------------------------------------------------------------------
\\ Singletons
vpar_is_singleton(vpar,v) =
{
if(vpar[v], 0,
for(c=1,#vpar, if(vpar[c]==v, return(0))); 1);
}
{
addhelp(vpar_is_singleton,
"bool = vpar_is_singleton(vpar,v)
Return 1 if vertex v is a singleton in vpar, or return 0 if not.
A singleton is degree=0, so no parent and no children.
vpar can contain cycles. All cyclic vertices (vpar_is_cyclic_vertex()),
including self-loops, have degree >= 2 so are not singletons.");
}
\\ (vpar[v]==0 and vpar_vertex_is_childless())
vpar_singletons(vpar) =
{
my(seen=Vec(vpar));
for(v=1,#vpar, if(vpar[v], seen[vpar[v]] = 1));
Vec(select(vpar_INTERNAL_gnot, seen, 1));
}
{
addhelp(vpar_singletons,
"vec = vpar_singletons(vpar)
Return a vector of the vertex numbers which are the singletons in vpar
(vpar_is_singleton()). The vertices are in ascending order (so a Set()).
The number of them is #vec == vpar_num_singletons(vpar).
A singleton has no parent and no children so vec is the set intersection
(setintersect()) of vpar_roots() and vpar_childless_vertices().
vpar can contain cycles. All cyclic vertices (vpar_cyclic_vertices()),
including self-loops, have degree >= 2 so are not singletons.");
}
vpar_num_singletons(vpar) =
{
\\ seen[v] = 1 if it has been seen as either a parent of something or a
\\ child of something.
my(seen=vectorsmall(#vpar));
for(v=1,#vpar, if(vpar[v], seen[v] = seen[vpar[v]] = 1));
#vpar - hammingweight(seen);
}
{
addhelp(vpar_num_singletons,
"num = vpar_num_singletons(vpar)
Return the number of singleton vertices in vpar, so how many
vpar_is_singleton(). A singleton is degree=0 so no parent and no children.
vpar can contain cycles. Cyclic vertices, including self-loops, are not
singletons.
For interest, the total number of singletons over all forests of n vertices
is
TS(n) = if(n==0,0, vpar_count(n)) total singletons
= 0,1,2,9,64,625,... (A152917)
This follows by taking each tree and removing edges at its root so as to
make a forest. The root and any leaf children become singletons. All
forests which have a singleton arise with that singleton as the now
disconnected root and everything below it as rest of the forest. Forests
with multiple singletons are each singleton as the tree root.
Algebraically, the same follows by choosing s singletons, binomial their
positions, and rest forest of no singletons,
TS(n) = sum(s=1,n, s*binomial(n,s)*vpar_count_no_singletons(n-s))
Factor \"s\" is the usual exponential generating function x*exp(x) and the
binomial is product of it with the no singletons count exp(gT(x)-x)*exp(-x).
Cancellation gives s* forests count, which is the trees count.");
}
\\ GP-DEFINE TotS(n) = if(n==0,0, vpar_count(n)); \\ the trees count
\\ GP-Test vector(10,n,n--; TotS(n)) == \
\\ GP-Test vector(10,n,n--; sum(s=1,n, \
\\ GP-Test s*binomial(n,s)*vpar_count_no_singletons(n-s)))
vpar_count_no_singletons(n) = sum(k=0,n, (-1)^(n-k)*binomial(n,k)*(k+1)^(k-1));
{
addhelp(vpar_count_no_singletons,
"count = vpar_count_no_singletons(n)
Return the number of labelled forests of n vertices with no singletons
(no degree=0 vertices).
NS(n) = sum(k=0,n, (-1)^(n-k)*binomial(n,k)*vpar_count(k,1))
= 1, 0, 2, 9, 76, 805, 10626, ... (A105785)
This formula (by Vladeta Jovovic in OEIS A105785) follows by some usual
exponential generating function manipulations. Forests built from trees is
exp(gT(x)) = gF(x) but here want to exclude the singleton tree so
gNS(x) = exp(gT(x)-x) = exp(gT(x))*exp(-x)
which is exp(-x) gf of alternating signs, and multiplying forests gF is sum
with binomial above.
The interpretation as a sum is k=n all forests, but that includes some with
singletons so subtract 1 vertex as singleton and k=n-1 others chosen by
binomial. But that singleton choice means forests of >=2 singletons are
excluded twice. So k=n-2 adds back those >=2 singletons, then re-subtract
those >=3 singletons, etc.
A direct recurrence per Alois P. Heinz again in OEIS A105785 is
NS(n) = if(n==0,1, sum(t=2,n, binomial(n-1,t-1) * vpar_count(t) * NS(n-t)))
This is t vertices in the tree containing v=1, binomial to choose its t-1
others, and the rest NS(n-t) forest of no singletons.");
}
\\ GP-DEFINE NS(n) = sum(k=0,n, (-1)^(n-k)*binomial(n,k)*vpar_count(k,1));
\\ GP-Test vector(7,n,n--; NS(n)) == [1, 0, 2, 9, 76, 805, 10626]
\\
\\ GP-DEFINE NS(n) = if(n==0,1, sum(t=2,n, binomial(n-1,t-1) * vpar_count(t) * NS(n-t)));
\\ GP-Test vector(7,n,n--; NS(n)) == [1, 0, 2, 9, 76, 805, 10626]
\\
\\ A105785 without isolated vertices
\\ Sum_{k=0..n} (-1)^(n-k)*binomial(n, k)*(k+1)^(k-1)
\\ k=n (k+1)^(k-1) all forests 0..n singletons
\\ k=n-1 binomial(n,k)=k position rest forest
\\ double count when >=2 singletons
\\
\\ Sum_{j=1..n-1} binomial(n-1,j) (j+1)^j a(n-1-j)
\\ v=1 in a component tree j+1 >= 2 not singleton, choose its other vertices
\\-----------------------------------------------------------------------------
\\ Parents, Children, Neighbours of Set
vpar_parents_of_set(vpar,set) =
{
my(ret=Set(vecextract(vpar,set))); \\ parent of each
setminus(if(#ret && !ret[1], ret[^1], ret), \\ exclude 0 parent
set); \\ exclude those in set
}
{
addhelp(vpar_parents_of_set,
"set = vpar_parents_of_set(vpar,set)
\"set\" is a Set() of vertex numbers.
Return a set of the parent vertices of that set.
These are vertices not in set but which are the parent of something in the
set. The return has size vpar_num_parents_of_set(vpar,set).
vpar can contain cycles.");
}
vpar_num_parents_of_set(vpar,set) =
{
\\ seen[p+1] is when parent p has been seen.
\\ seen[1] is for p=0, which is not counted.
my(seen=vectorsmall(#vpar+1),
ret=0);
seen[1]=1; \\ not a parent when vpar[v]==0
for(i=1,#set, seen[set[i]+1]=1); \\ not those in the set
for(i=1,#set, if(seen[vpar[set[i]]+1]++==1, ret++));
ret;
}
{
addhelp(vpar_num_parents_of_set,
"num = vpar_num_parents_of_set(vpar,set)
\"set\" is a Set() of vertex numbers.
Return the number of parent vertices of that set.
This is how many vertices not in set but which are the parent of something
in the set. See vpar_parents_of_set() for the parent vertex numbers.
vpar can contain cycles.");
}
vpar_children_of_set(vpar,set) =
{
\\ MAYBE: Could turn set into flags to test instead of setsearch() it.
\\ Expect probably setsearch() faster than flags setup until big sets.
setminus(Vec(select(p->setsearch(set,p),vpar,1)), set);
}
{
addhelp(vpar_children_of_set,
"set = vpar_children_of_set(vpar,set)
\"set\" is a Set() of vertex numbers.
Return the set of the children of that set.
These are vertices not in set but which are a child of something in the set.
The return set has size vpar_num_children_of_set(vpar,set).
vpar can contain cycles.");
}
vpar_num_children_of_set(vpar,set) =
{
my(seen=vectorsmall(#vpar),
ret=0);
for(i=1,#set, seen[set[i]]=1); \\ those in set
for(v=1,#vpar, if(!seen[v] && vpar[v] && seen[vpar[v]], ret++));
ret;
}
{
addhelp(vpar_num_children_of_set,
"num = vpar_num_children_of_set(vpar,set)
\"set\" is a Set() of vertex numbers.
Return the number of children of that set.
This is how many vertices not in set but which are a child of something in
the set. See vpar_children_of_set() for the child vertex numbers.
vpar can contain cycles.");
}
vpar_neighbours_of_set(vpar,set) =
{
setunion(vpar_parents_of_set(vpar,set),
vpar_children_of_set(vpar,set));
}
{
addhelp(vpar_neighbours_of_set,
"set = vpar_neighbours_of_set(vpar,set)
\"set\" is a Set() of vertex numbers.
Return the set of neighbouring vertices of that set.
This is all vertices not in the set but which are a neighbour of something
in the set. It can be either parent or child of something in the set, so
setunion(vpar_parents_of_set(vpar,set),
vpar_children_of_set(vpar,set))
This is setunion() because when the set has separated vertices it's possible
for a neighbour to be both a child and a parent. vpar can contain cycles.");
}
vpar_num_neighbours_of_set(vpar,set) =
{
my(seen=vectorsmall(#vpar));
for(i=1,#set, seen[set[i]]=1);
for(v=1,#vpar,
if(!seen[v], my(p=vpar[v]); if(p && seen[p]==1, seen[v]=2))); \\ children
for(i=1,#set,
my(p=vpar[set[i]]); if(p, seen[p]=2)); \\ parents
hammingweight(seen) - #set;
}
{
addhelp(vpar_num_neighbours_of_set,
"num = vpar_num_neighbours_of_set(vpar,set)
\"set\" is a set of vertex numbers.
Return the number of neighbouring vertices of that set.
This is how many vertices not in the set but which are a neighbour of
something in the set. See vpar_neighbours_of_set() for the child vertex
numbers. vpar can contain cycles.");
}
\\-----------------------------------------------------------------------------
\\ Depths
vpar_depth_of_vertex(vpar,v) = vpar_depth_and_root_of_vertex(vpar,v)[1];
{
addhelp(vpar_depth_of_vertex,
"depth = vpar_depth_of_vertex(vpar,v)
Return the depth of vertex v, meaning distance up to its root.
The root is depth 0, its children are depth 1, etc.
vpar can be a forest. Depth is distance up to the root of the component
tree containing v.
vpar can contain cycles. Depth is the number of ancestors reachable above
v, and not including v itself. A self loop has none, the same as a root has
none. Depth of a vertex in a cycle is its cycle length - 1.");
}
\\ depth sometimes "inner distance", number of edges path to root
\\ subtree height sometimes "outer distance", number of edges path to root
vpar_depth_and_root_of_vertex(vpar,v) =
{
my(vec=vpar_INTERNAL_top_depth_length(vpar,v));
return(if(vec[3],
[vec[2]+vec[3]-1, 0], \\ cyclic, ancestors and no root
[vec[2], vec[1]])); \\ acyclic, distance and root
}
{
addhelp(vpar_depth_and_root_of_vertex,
"[depth,root] = vpar_depth_and_root_of_vertex(vpar,v)
Return a vector of two elements, the depth of v and the root vertex of v.
This is [vpar_depth_of_vertex(vpar,v), vpar_root_of_vertex(vpar,v)]
but calculated together.
vpar can contain cycles. Depth and root are treated as per
vpar_depth_of_vertex() and vpar_root_of_vertex(), so depth is number of
proper ancestors and root is 0 when a cycle rather than a root above.");
}
\\ vec = vpar_INTERNAL_depths_vector(vpar,start,end)
\\ Determine the depths of vertices v=start to v=end inclusive.
\\ Return vec with each such vec[v] = depth of v.
\\ The rest of vec, outside vec[start..end], is unspecified.
\\
vpar_INTERNAL_depths_vector(vpar,start,end) =
{
\\ At each v=i search upwards for a depths[v] already known, or a root, or
\\ a cycle.
\\ depths[v] = special value none (= -#vpar-1) when not yet known.
\\ depths[v] = -1, -2, -3, ... while working upwards, will be overwritten.
\\
\\ Finding depths[v]>=0 means an already determined depth. The d (which
\\ is negative) counted to there says how far below is the desired i.
\\ A re-traversal from i upwards fills depths i to v accordingly.
\\
\\ Finding depths[v]<0 means a cycle in this search upwards.
\\ Cycle length is difference of d and that depths[v].
\\ The cyclics are depth = len-1 filled by re-traversing around.
\\ Then that len-1 is the same as a found depth for re-traversal up from i.
\\
\\ If vpar was assumed to be acyclic, then there'd be no need to store
\\ depths[v]=-1,-2,-3,... used for cycle detection. Working upwards would
\\ eventually reach either root or previously decided depth. But even if
\\ depths of cyclics not interesting then might still want cycle detection
\\ to guard against infinite loop.
\\ print("vpar_depths_vector() "vpar);
my(none=-(#vpar+1),
depths=vector(#vpar,v,none));
for(i=start,end,
\\ print(" i="i" "depths[i]" in "depths);
if(depths[i]<0, \\ depths[i] not yet known
my(v=i, d=-1);
while(depths[v]=d; v=vpar[v],
d--;
if(depths[v]!=none,
\\ print(" v="v" "depths" d="d" depths[v]="depths[v]);
if(depths[v]>=0,
d -= depths[v];
,
my(c=depths[v]-(d++));
\\ print(" cycle depth "c);
until(depths[v]>=0, depths[v]=c; v=vpar[v]);
\\ print(" filled "depths);
);
break));
\\ For reaching root, d = negative distance i to root.
\\ An already depth[] or cycle found is adjusted ready for -d here.
d = -d;
\\ print(" re-traverse i="i" d="d" before pre-decrement");
v=i;
until(!(v=vpar[v]) || depths[v]>=0, depths[v] = d--));
);
depths;
}
vpar_depths_vector(vpar) = vpar_INTERNAL_depths_vector(vpar,1,#vpar);
{
addhelp(vpar_depths_vector,
"vec = vpar_depths_vector(vpar)
Return the depths vector of vpar. Each vec[v] is the depth of vertex v,
meaning number of proper ancestors above, which is distance up to root.
This is vec[v] = vpar_depth_of_vertex(vpar,v), but calculated all together.
vpar can contain cycles. See vpar_depth_of_vertex() on depth = number of
proper ancestors above.
See vpar_depths_vectors_count() for how many different depths vectors occur.
See vpar_is_equaldepths() for vpar vector equal to its depths vector.
Among pre-order trees (or forests), lex() ordering of depths vectors is the
same as lex() ordering of their vpar vectors. This is since the last vertex
number at each depth is an increasing function of depth.");
}
vpar_is_depths_vector(vec,forest=0) =
{
\\ vec[v]<0 is not a depth.
\\ vec[v]==0 must occur only once, or be a forest.
\\ got[depth+1] is when seen some vec[v]==depth.
\\ forest=-1 is a root=1 must vec[1]==0.
if(#vec,
my(height=vecmax(vec)+1); \\ height = max depth
if(height<1 || height>#vec || (forest<0 && vec[1]), return(0));
my(got=vectorsmall(height));
for(v=1,#vec,
if(vec[v]<0 || !(forest>0||vec[v]||!got[1]), return(0));
got[vec[v]+1]=1);
hammingweight(got)==#got;
,
1); \\ vec==[] is depths of the empty tree
}
{
addhelp(vpar_is_depths_vector,
"bool = vpar_is_depths_vector(vec,forest=0)
vec is a vector of integers. Return 1 if it is a depths vector of a tree,
so vec == vpar_depths_vector(vpar) for some tree vpar.
Optional parameter \"forest\" can be 1 to test for depths vector of a
forest, or can be -1 for vector from a root=1 tree.
The test is vec entries >=0 and all of 0 to vecmax(vec)=height inclusive
occurring somewhere in vec. For a tree, there is a single root so must have
only one entry vec[i]==0, and for root=1 tree that entry must be vec[1]==0.
For a forest, there can be any number of 0s. Empty vec==[] is depths of the
empty tree.
See vpar_depths_vectors_count() for the number of possible depths vectors.");
}
vpar_depths_vectors_count(n,forest=0) =
{
if(n,
my(m=n-1+(forest>0)); \\ n-1 when tree, n when forest
if(forest,1,n) * sum(k=0,m, k!*stirling(m,k,2));
,
1); \\ n==0
}
{
addhelp(vpar_depths_vectors_count,
"count = vpar_depths_vectors_count(n,forest=0)
Return the number of different vpar_depths_vector() which occur for
trees of n vertices, so how many vpar_is_depths_vector().
Optional parameter \"forest\" can be 1 for number from forests, or can be -1
for number from root=1 trees.
For forests, the return is the ordered Bell numbers
OBell(n) = sum(k=0,n,k!*stirling(n,k,2)) = 1,1,3,13,75,541, ... (A000670).
The sum is over k many depth levels. Stirling number of the second kind is
ways to distribute vertices 1..n into k non-empty depths, then k! to order
those depths.
Root=1 trees are forests n-1 by removing the root if(n==0,1, OBell(n-1)).
All trees are multiply n for choice of root (the sole depth==0) so
if(n==0,1, n*OBell(n-1)) = 1,1,2,9,52,375, ... (A052882).
Single-component functional digraphs (trees plus one-component cyclics) are
the same count as forests, since the cyclic vertices can be reckoned
corresponding to forest roots.
Multi-component function digraphs count is trickier. When there is a gap in
the depths values occurring, the new depth d is one or more cycles length
d+1. Some recurrence over where the top gap, how many copies of its cycle,
choosing which vertices above/below (and limited depth of below) gives
1,1,4,17,103,747,..., but no code for it here.");
}
\\ not in OEIS: 1,4,17,103,747
\\
\\ GP-DEFINE OBell(n) = sum(k=0,n,k!*stirling(n,k,2));
\\ GP-Test vector(6,n,n--; OBell(n)) == [1,1,3,13,75,541]
\\
\\ Want trees n=0 count=1 but n*OBell(n-1) is 0.
\\ GP-Test my(n=0); n*OBell(n-1) == 0
\\ GP-Test vector(6,n,n--; if(n==0,1, n*OBell(n-1))) == [1,1,2,9,52,375]
\\------------------------------------------------------------------------------
\\ Preorder Depths
vpar_is_preorder_depths_vector(vec,forest=0) =
{
\\ bitxor() on "forest" parameter so as to catch a mistake like an unbound
\\ variable giving symbol 'forest instead of flag 0,1.
if(#vec,
if(vec[1], return(0));
forest=bitxor(1,forest); \\ 1 for tree, 0 for forest
for(v=2,#vec,
(forest <= vec[v] && vec[v] <= vec[v-1]+1) || return(0)));
1;
}
{
addhelp(vpar_is_preorder_depths_vector,
"bool = vpar_is_preorder_depths_vector(vec,forest=0)
vec is a vector of integers. Return 1 if it is the depths vector from a
pre-order labelled tree, so vec == vpar_depths_vector(vpar) for some vpar
which is vpar_is_preorder(vpar).
Optional parameter \"forest\" can be 1 to test for depths from a preorder
forest.
The test is vec[1]==0, then 1 <= vec[v] <= vec[v-1]+1 for all v>=2 since
each v can be at most 1 deeper than the preceding v-1. For a forest,
further roots are allowed so the permitted range expands to 0 <= vec[v].
Each preorder depths vector corresponds to an ordered tree so the number of
distinct vec is vpar_count_ordered(). See vpar_make_preorder_depths() to
make a preorder vpar with depths vec.");
}
\\ This condition appearing for example in
\\ Joe Sawada, "Generating Rooted and Free Plane Trees", 2005, theorem 1.
\\ http://www.socs.uoguelph.ca/~sawada/papers/tree.pdf
vpar_make_preorder_depths(depths) =
{
\\ When depths[v] = depths[v-1]+1 the parent of v is v-1.
\\ When depths[v] is smaller, it is up to parent or grandparent etc of v-1.
\\
\\ The total steps up this way is at most n-1 forest or n-2 tree.
\\ Each vertex goes down 1 step, and then up by as many as depths[v] implies.
\\ The total is therefore as many ups as needed to go to the last depths,
\\ #depths - depths[#depths] - 1.
\\
\\ test.gp has a couple of other approaches.
\\ Could keep the last vertex at depth in a separate vector, but creating
\\ and maintaining that is likely about the same work as the up steps.
\\ Similarly high to low keeping chains to fill in with a parent.
my(vpar=vector(#depths));
for(v=2,#vpar,
my(p=v-1);
for(i=depths[v],depths[v-1], p=vpar[p]);
vpar[v]=p);
vpar;
}
{
addhelp(vpar_make_preorder_depths,
"vpar = vpar_make_preorder_depths(depths)
Return the tree (or forest) labelled in preorder and having the given depths
vector. So the return is
vpar_is_preorder(vpar) == 1
vpar_depths_vector(vpar) == depths \\\\ the given depths
depths must be a valid pre-order depths vector per
vpar_is_preorder_depths_vector().
A pre-order depths (or \"level sequence\") is a common representation for an
ordered tree and this function turns that into a vpar. Remember depths here
start at 0 for the root or roots.
vpar is constructed by setting vpar[v] = p where p is the closest preceding
vertex with depths[p] == depths[v]-1, or set vpar[v]=0 if depths[v]==0.");
}
\\-----------------------------------------------------------------------------
\\ Height
vpar_height(vpar) =
{
\\ Don't think there's much to gain by own code for the height, since a
\\ full set of depths would be built to allow search up from a vertex to
\\ stop at a depth already seen.
if(#vpar, vecmax(vpar_depths_vector(vpar)), -1);
}
{
addhelp(vpar_height,
"height = vpar_height(vpar)
Return the height of vpar.
This is the maximum depth of any vertex, vecmax(vpar_depths_vector(vpar)),
and is the eccentricity of the tree root.
vpar can be a forest, in which case the effect is maximum height of all its
component trees.
The empty tree [] is reckoned height -1 (similar to vpar_diameter()).");
}
vpar_height_vertices(vpar) =
{
my(depths=vpar_depths_vector(vpar),
height=if(#vpar,vecmax(depths)));
Vec(select(d->d==height, depths, 1));
}
{
addhelp(vpar_height_vertices,
"vec = vpar_height_vertices(vpar)
Return a vector of the vertex numbers which are at the height of vpar (its
maximum depth), so those vpar_depth_of_vertex(vpar,v) == vpar_height(vpar).
vpar can be a forest and in that case the vertices are those at the whole
forest height. If some component trees are shorter than others then they
contribute nothing to the return.
The number of vertices is #vec == vpar_height_num_vertices(vpar).");
}
vpar_height_num_vertices(vpar) =
{
\\ Another possibility would be the code of vpar_depths_vector() and in
\\ the loop watch for new high or equal high d. Doesn't seem worth
\\ duplicating the code.
if(#vpar,
my(depths=vpar_depths_vector(vpar),
height=vecmax(depths));
sum(v=1,#vpar, depths[v]==height);
,
0);
}
{
addhelp(vpar_height_num_vertices,
"num = vpar_height_num_vertices(vpar)
Return the number of vertices which are at the height of vpar, so how many
have vpar_depth_of_vertex(vpar,v) == vpar_height(vpar).
vpar can be a forest and in that case the count is vertices at the whole
forest height. If some component trees are shorter than others then they
contribute nothing to the return.
See vpar_height_vertices() for the vertex numbers.");
}
vpar_component_heights(vpar) = \
vecextract(vpar_subtree_heights_vector(vpar), vpar_roots(vpar));
{
addhelp(vpar_component_heights,
"vec = vpar_component_heights(vpar)
Return a vector of the heights of the component trees in vpar.
Components are numbered 1 upwards in order of their smallest contained
vertex. vec length is vpar_num_roots(vpar), that being the number of
components.
The number of different vec occur from forests of n vertices is 2^n-n =
1,1,2,5,12,27,58,... (A000325). The connection to that power can be seen by
interpreting n bits of binary as runs like 100..00 for h+1 bits which is h+1
vertices making height h of a component, and high 0 bits as extra vertices
not extending the heights. For example 001000001000 would be component
heights [5,3], and 2 extra vertices. Extra vertices can only be present
when some component has height >=1 so they can at least go under the root
there without changing the height. So bit patterns like 00111 are excluded
(three components height 0 and nowhere for the 2 high extras to go). Bits
patterns 011..11 through 00..001 and 00..000 are n forms excluded for net
2^n-n.");
}
\\ GP-Test vector(7,n,n--; 2^n-n) == [1, 1, 2, 5, 12, 27, 58]
vpar_is_component_heights(vec,n) =
{
my(s);
(!n || #vec) \\ vec not empty, unless n=0
&& (!#vec || vecmin(vec)>=0) \\ vec entries >= 0
&& if(s=vecsum(vec),
s+#vec <= n, \\ enough n to make paths for vec
#vec == n); \\ all singletons equal only
}
{
addhelp(vpar_is_component_heights,
"bool = vpar_is_component_heights(vec,n)
vec is a vector of integers. Return 1 if vec is a component heights vector
for some forest of n vertices, so vec == vpar_component_heights(vpar) for
some forest of size #vpar==n.
The test is all vec entries >=0, and a component of height h has >= h+1
vertices so n must be big enough for that many in all of vec. If vec all 0s
then must have #vec==n since there is nowhere to put extra vertices not
extending that height=0.");
}
vpar_height_num_components(vpar) =
{
my(vec=vpar_component_heights(vpar),
height=if(#vec,vecmax(vec)));
sum(i=1,#vec, vec[i]==height);
}
{
addhelp(vpar_height_num_components,
"num = vpar_height_num_components(vpar)
Return the number of component trees of vpar which have height equal to the
whole forest height vpar_height().
If vpar is a single tree then the return is 1.
If vpar==[] empty then the return is 0 since it has no components at all.");
}
\\------------------------------------------------------------------------------
\\ 2-Height
vpar_2height(vpar) =
{
\\ second greatest entry in depths[]
my(depths=vpar_depths_vector(vpar),
height=-1,
h2=-2);
for(v=1,#vpar,
if(depths[v]>=height, h2=max(h2,height); height=depths[v],
h2=max(h2, depths[v])));
h2;
}
{
addhelp(vpar_2height,
"height2 = vpar_2height(vpar)
Return the 2-height of vpar.
This is the depth of the second-deepest vertex in vpar, in the manner of
Helmut Prodinger, \"The Average Height of the Second Highest Leaf of a
Planted Plane Tree\", European Journal of Combinatorics, volume 5, 1984,
pages 351-357
If vpar has 1 vertex at vpar_height(vpar) then the second highest is one
above and the return is height-1. If vpar has 2 or more vertices at its
height then the second highest is also that height.
For a single vertex vpar==[0] the return is -1.
For the empty tree vpar==[] the return is -2.
These cases don't have a second vertex to be second highest but the returns
are based on having 2height of a path-n equal to n-2 in all cases (similar
to vpar_height() having path always n-1).");
}
\\-----------------------------------------------------------------------------
\\ Subtree Heights
vpar_subtree_height(vpar,v) =
{
my(seq=vpar_INTERNAL_seq_upwards(vpar),
heights=vectorsmall(#vpar));
for(i=1,#seq,
my(u=seq[i]);
if(u==v,break);
my(p=vpar[u]);
if(p, heights[p]=max(heights[p],heights[u]+1)));
heights[v];
}
{
addhelp(vpar_subtree_height,
"height = vpar_subtree_height(vpar,v)
Return the height of the subtree at and below vertex v.
This is the maximum distance down to any vertex in the subtree below v.
If v has no children then its subtree height is 0.
If v has some children but nothing deeper then subtree height 1, etc.
vpar can be forest.");
}
vpar_subtree_heights_vector(vpar) =
{
my(seq=vpar_INTERNAL_seq_upwards(vpar),
heights=vector(#vpar));
for(i=1,#seq,
my(v=seq[i], p=vpar[v]);
if(p, heights[p]=max(heights[p],heights[v]+1)));
heights;
}
{
addhelp(vpar_subtree_heights_vector,
"vec = vpar_subtree_heights_vector(vpar)
Return a vector where vec[v] is the height of the subtree at vertex v.
This is vec[v] = vpar_subtree_height(vpar,v) but calculated all together.
See vpar_subtree_heights_vectors_count() for how many heights vectors occur.
Or see vpar_count_subtree_heights_increasing() for trees or forests with
height increasing with vertex number.");
}
vpar_is_subtree_heights_vector(vec,forest=0) =
{
if(#vec,
my(m=vecmax(vec));
if(m<0 || m>=#vec || (forest<0 && vec[1]!=m), return(0));
my(count=vectorsmall(m+1));
for(v=1,#vec,
if(vec[v]<0, return(0));
count[vec[v]+1]++);
for(i=1,m,
if(count[i]0 || count[m+1]==1; \\ trees must 1 only at height=m
,
1); \\ empty vec==[] from empty tree
}
{
addhelp(vpar_is_subtree_heights_vector,
"bool = vpar_is_subtree_heights_vector(vec,forest=0)
vec is a vector of integers. Return 1 if it is a subtree heights vector, so
vec == vpar_subtree_heights_vector(vpar) for some vpar tree.
Optional parameter \"forest\" can be 1 to test for vector from a forest, or
can be -1 to test from a root=1 tree.
The test counts how many entries in vec are a given height h. Must have
decreasing count[h+1]<=count[h] otherwise there are not enough count[h+1]
children to give height h to those of count[h]. For a tree must have a
single vertex at the top so count[max_h]==1. For root=1 tree this must be
at vec[1]==max_h. Empty vec==[] is heights of the empty tree.
See vpar_subtree_heights_vectors_count() for the number of vectors occurring.");
}
\\ A182926 counts:
\\ Partition 1..n into a list of sets all the same size.
\\ The list is permuted except one element fixed position (say the smallest
\\ fixed first in list where two sets compared by smallest element in each).
\\
\\ The number of these is sumdiv(n,d, n! / (d!)^q / q) where d the set size
\\ and q=n/d many sets. List permuted all ways would be n! / (d!)^q.
\\ Fixing the first set means divide /q no choice there.
\\
\\ Exponential transform exp(A182926) = A005651. Exp collects lists (of all
\\ list lengths) without order between lists. Take these lists in
\\ descending order of the first set in the list (sets compared again by
\\ smallest element). Concatenating the lists is then all orderings of the
\\ sets. A single list has smallest set first. Two lists never have
\\ smallest set first because the list with the bigger first set is taken
\\ first. Etc.
\\
\\ Can reverse ordering if preferred, so A182926 is lists with biggest set
\\ first and exp order those lists by increasing first set. Can compare
\\ sets any way too, eg. by their biggest element.
\\
\\ vec = vpar_INTERNAL_A182926_vector(n)
\\ Return vector length n with vec[i] = A182926(i).
vpar_INTERNAL_A182926_vector(n) =
{
\\ Lightly massaged sumdiv form by Peter Luschny in A182926:
\\ sumdiv(n,d, n! / (d!)^q / q) where q=n/d.
\\
\\ /q is *d/n. The /n is by dropping the factorial to (n-1)!. The *d is
\\ by dropping one d! to (d-1)!. f[] is with offset 1 so f[n]=(n-1)!.
\\ Case d==n is skipped in the sumdiv() since it is an unnecessary
\\ division f[n]/f[n]==1. It is counted by 1+ outside. Maybe calculating
\\ prime powers from the factorials would be better instead.
my(f=vpar_INTERNAL_factorials_vector(n+1,1));
vector(n,n, 1 + sumdiv(n,d, if(dn!/prod(i=1,#p,p[i]!), partitions(n)))
= 1, 1, 3, 10, 47, 246, ... (A005651)
The partition is how many vertices at successive subtree heights h. These
are in non-decreasing order since there must be enough h-1 to have h
subtrees. The multinomial is how many ways to assign vertices 1..n to those
heights. The current implementation is some exponential transforms and
sumdivs (done in vectors), so is suitable n larger than where partitions()
(for forpart() for that matter) would be practical.
Root=1 trees are the same by the root having the unique maximum height then
forest below is 1 vertex smaller, so B(n) = F(n-1). For all trees, the root
can be any of n, so
T(n) = if(n==0,1,n*M(n-1))
= 1, 1, 2, 9, 40, 235, ... (A327827)");
}
\\ GP-DEFINE F(n) = vecsum(apply(p->n!/prod(i=1,#p,p[i]!), partitions(n)));
\\ GP-Test vector(6,n,n--; F(n)) == [1,1,3,10,47,246]
\\ GP-DEFINE T(n) = if(n==0,1,n*F(n-1));
\\ GP-Test vector(6,n,n--; T(n)) == [1,1,2,9,40,235]
\\ No forest=-1 root=1 case here since root=1 means it is the max height so
\\ no subtree heights increasing. Heights decreasing would be same as
\\ forest of n-1 by forest below the root=1.
\\
vpar_count_subtree_heights_increasing(n,forest=0) =
{
\\ MAYBE: could return whole vector or poly since it is all generated
if(n,
n+=forest;
my(a=vector(n,l, vector(l,i,1)));
for(n=2,n,
for(l=1,n,
\\ print("entry n="n" l="l" diff n-l="n-l);
a[n][l] = sum(f=1,n-l,
a[n-l][f] * sum(s=0,l-f,
binomial(l,l-s)
* f!*stirling(l-s,f,2)
* (n-l-f)^s))));
vecsum(a[n]);
,
1); \\ n==0
}
{
addhelp(vpar_count_subtree_heights_increasing,
"count = vpar_count_subtree_heights_increasing(n,forest=0)
Return the number of vpar trees which have subtree height increasing with
vertex number, so its vpar_subtree_heights_vector(vpar) is sorted in
ascending order.
Optional parameter \"forest\" can be 1 to count for forests.
Trees can be counted by considering a tree with l many height=0 childless
vertices first in vpar, and f many height=1 above them next in vpar. l can
have any of the remaining n-l as parent, provided each f has at least one
child to give them height=1.
W(n,f,e) = if(n==0,(f==0),
f*W(n-1,f-1,e+1) + e*W(n-1,f,e));
C(n,l) = if(l==0,(n==0), l==1,1,
sum(f=0,n-l, C(n-l,f) * W(l,f,n-l-f)));
Trees(n) = sum(l=0,n, C(n,l));
= 1, 1, 1, 2, 5, 16, 61, 268, ...
Forests(n) = Trees(n-1);
W(n,f,e) is how many ways n vertices can be put under f non-empty locations
and e extra locations which can be empty or not. In terms of the r-Stirling
numbers of the second kind this is
W(n,f,e) = f! * rStirling2(n+e,f+e,e))
rStirling2(n,m,r) is how many ways to put 1..n into m non-empty buckets with
1..r all in different buckets. For W, r=e and the different buckets which
1..e go into are the e extra locations. They have an element each so
putting the other n to make all f+e non-empty means the n go to f non-empty
and e possibly empty. The f locations are undistinguished by r-Stirling so
f! orders them. Case e=0 is plain ordered Stirling numbers of the second
kind W(n,f,0)==f!*stirling(n,f,2). All W can be expressed in terms of
stirling(,,2) if desired by sum choose s>=f many under f and power e^(n-s)
for where others under e. Broder gives that sort of identity for
rStirling2.
A tree of increasing subtree heights has root as the last vertex n, and
below it a forest. So count of a forest is a tree of n+1. Or if preferred
in C(n,l) above put W(l,f,n-l-f+1) which is a further extra location for an
l going under no parent at all.
The split of vertices l,f,etc by height is a partition of n with decreasing
terms. The product terms depend on an adjacent pair and total remaining.
For a forest, and since partitions() gives terms increasing,
Forests(n) = vecsum(apply(p->prod(i=2,#p,
W(p[i],p[i-1],1+vecsum(Vec(p)[1..i-2]))),
partitions(n)));
The \"1+\" is extra location no parent in a forest. For trees would omit
that, but would restrict to partitions with first (smallest) entry 1 since
that entry is how many roots.");
}
\\ GP-DEFINE W(n,f,e) = if(n==0,(f==0), \
\\ GP-DEFINE f*W(n-1,f-1,e+1) + e*W(n-1,f,e));
\\ GP-DEFINE C(n,l) = if(l==0,(n==0), l==1,1, \
\\ GP-DEFINE sum(f=0,n-l, C(n-l,f) * W(l,f,n-l-f)));
\\ GP-DEFINE Trees(n) = sum(l=0,n, C(n,l));
\\ GP-Test vector(8,n,n--; Trees(n)) == [1, 1, 1, 2, 5, 16, 61, 268]
\\
\\ GP-DEFINE Forests(n) = vecsum(apply(p->prod(i=2,#p, \
\\ GP-DEFINE W(p[i],p[i-1],1+vecsum(Vec(p)[1..i-2]))), \
\\ GP-DEFINE partitions(n)));
\\ GP-Test vector(10,n, Trees(n+1)) == vector(10,n, Forests(n))
\\
\\ GP-DEFINE TreesP(n) = vecsum(apply(p-> if(p[1]!=1,return(0)); \
\\ GP-DEFINE prod(i=2,#p, \
\\ GP-DEFINE W(p[i],p[i-1],vecsum(Vec(p)[1..i-2]))), \
\\ GP-DEFINE partitions(n)));
\\ GP-Test vector(10,n, Trees(n)) == vector(10,n, TreesP(n))
\\ R2(n,m,x) = sum_k binomial(n,k) * stirling2(n-k,m) * x^k
\\ R2(n,m,r) = rStirling2(n+r,m+r,r)
\\
\\ Nielsen, Traite Elementaire des Nombres de Bernoulli (Gauthier-Villars,
\\ Paris, 1923).
\\ A^n_m(x) = m! R2(n,m,x)
\\ s is how many vertices going to non-f locations. So binomial to choose
\\ which s, then l-s into f buckets Stirling of the second kind, f! to order
\\ those buckets, and power for the s to any non-f. Total forests are sum over
\\ possible l.
\\ C(n,l) = sum(f=1,n-l, C(n-l,f) * sum(s=0,l-f,
\\ binomial(l,l-s) * f!*stirling(l-s,f,2) * (n-l-f)^s ))
\\ or C(n,n)=1
\\ CC = sum(l=1,n, C(n,l)) = 1,1,1,2,5,16,61,268,...
\\------------------------------------------------------------------------------
\\ Subtree Height Sequence
vpar_seq_subtree_height(vpar,down=0) =
{
\\ Bucket sort by subtree height, and within same subtree height by vertex
\\ number, per remark in (called "radix sort" there)
\\
\\ Paulius Micikevicius, Saverio Caminiti, Narsingh Deo, "Linear-Time
\\ Algorithms for Encoding Trees as Sequences of Node Labels",
\\ Congressus Numerantium, volume 183, 2006, pages 65-75.
\\
\\ http://www.researchgate.net/publication/228964155_Linear-time_algorithms_for_encoding_trees_as_sequences_of_node_labels
\\ http://www.researchgate.net/profile/N_Deo/publication/228964155_Linear-time_algorithms_for_encoding_trees_as_sequences_of_node_labels/links/0a85e53ac70b117b42000000/Linear-time-algorithms-for-encoding-trees-as-sequences-of-node-labels.pdf
\\ (the html page includes a text form of the whole)
vpar_INTERNAL_seq_bucket_sort(vpar_subtree_heights_vector(vpar),down);
}
{
addhelp(vpar_seq_subtree_height,
"seq = vpar_seq_subtree_height(vpar,down=0)
Return a Vecsmall of the vertex numbers 1..#vpar in sequence of ascending
subtree height and among equal height by ascending vertex number. The
return type is Vecsmall since it is a permutation of the integers 1..#vpar.
This is the sequence of Neville's second canonical tree representation.
Neville's conception is to take all childless vertices, in ascending order,
consider them removed from the tree, and repeat. The effect is all vertices
with subtree height 0, then subtree height 1, etc. Caminiti notes the
equivalence to sorting by subtree height.
This is upwards per vpar_seq_is_upwards() since children precede their
parent. The tree root is last, or for a forest all roots of the maximum
height are last.
Optional parameter \"down\" can be 1 to sort instead by descending subtree
height, and among equal height by ascending vertex number.");
}
\\ vec is a an extract per vecextract(vpar, vpar_seq_subtree_height(vpar)).
\\ Return the heights of subtrees as per vpar_subtree_heights_vector(vpar).
\\ ENHANCE-ME: want this public?
vpar_INTERNAL_exto_subtree_heights_vector(vec,down=0) =
{
\\ Algorithm roughly in the manner outlined in
\\
\\ Saverio Caminiti, "On Coding Labelled Trees", Ph.D. thesis, section
\\ 3.3.3 "Neville Code".
\\ http://wwwusers.di.uniroma1.it/~caminiti/bib/C07th.pdf
\\
\\ and a little more detail in
\\
\\ Paulius Micikevicius, Saverio Caminiti, Narsingh Deo, "Linear-Time
\\ Algorithms for Encoding Trees as Sequences of Node Labels",
\\ Congressus Numerantium, volume 183, 2006, pages 65-75.
\\ (URLs with vpar_seq_subtree_height() code.)
\\
\\ work[v]=i is the last occurrence of vec[i]=v.
\\ This i position is the last child of v, though the vertex number of that
\\ child is unknown.
\\
\\ v which don't occur at all in vec[] are the leaf (childless) vertices L.
\\ L all have subtree height 0. The code starts with len many of them.
\\ Their parents are vec[1..len].
\\
\\ The vertices F with subtree height 1 are those parents vec[1..len].
\\ When an F has multiple children it appears in vec[1..len] multiple times.
\\ Counting how many of p=vec[1..len] are the last occurrence of p gives
\\ the number of F, when becomes new len.
\\
\\ The next len many of vec[] are then parents of F, which are subtree
\\ height 2 vertices. They can be counted and new len again, etc.
\\
\\ h is maintained as the subtree height of the vertices vec[1..len] will
\\ get. h increments with each block of len processed.
\\
\\ Subtree height of a vertex v is recorded as work[v] = h. This re-uses
\\ the work[] vector for subtree heights. if the last occurrence of some
\\ v has been seen in vec[] then it doesn't occur again to compare
\\ vec[]==last_occence[v].
\\
\\ The result is to have established work[] = vpar_subtree_heights_vector()
\\ of the original vpar (and using only that array and some fixed
\\ variables). The vertices can then be bucket sorted the same as in
\\ vpar_seq_subtree_height().
\\
\\ For down=1 the blocks are reversed, so that the leaves L are at the end
\\ of vec[#vec-L+1..#vec]. This is handled by stepping upto from the end
\\ #vec down to the beginning 1. The "last" occurrence is reckoned from
\\ the end downwards too. This is necessary because don't want to store
\\ to work[] until the last occurrence in the direction of upto.
\\ print("vpar_INTERNAL_exto_subtree_heights_vector(),down="down);
\\ print(" vec "vec);
my(work=vector(#vec),
start=if(down,#vec,1),
step=if(down,-1,1)); \\ step for upto
forstep(i=start,if(down,1,#vec),step,
if(vec[i],
work[vec[i]]=i)); \\ work[v]=i position of last vec[i]=v
\\ print("work "Vec(work));
my(len=#work-hammingweight(work), \\ how many 0s, is how many leaves
h=1, \\ height
upto=start);
while(len,
my(new_len=0);
\\ print("h="h" upto="upto" len="len);
for(j=1,len,
my(p=vec[upto]);
\\ print(" upto="upto" p="p" last occurrence "if(p,work[p],""));
if(p && work[p]==upto,
\\ print(" set "p" subtree height "h);
work[p]=h;
new_len++);
upto+=step);
len=new_len;
h++);
work;
}
\\ vec is a an extract vec = vecextract(vpar, vpar_seq_subtree_height(vpar)).
\\ Return its vertex sequence per vpar_seq_subtree_height(vpar).
\\ ENHANCE-ME: want this public?
vpar_INTERNAL_seqfrom_subtree_height(vec,down=0) =
{
vpar_INTERNAL_seq_bucket_sort
(vpar_INTERNAL_exto_subtree_heights_vector(vec,down), down);
}
vpar_from_subtree_height(vec,down=0) = \
vecextract(vec,vpar_INTERNAL_seqfrom_subtree_height(vec,down)^-1);
{
addhelp(vpar_from_subtree_height,
"vpar = vpar_from_subtree_height(vec,down=0)
Return vpar from a vector of parents in subtree height order, as from
vec = vecextract(vpar, vpar_seq_subtree_height(vpar,down)).
Optional parameter \"down\" should be the same as the seq from which vec was
created.");
}
\\------------------------------------------------------------------------------
\\ Subtree Childless
vpar_INTERNAL_subtree_childless(vpar,v,flags) =
{
\\ seen[u] = 0 unseen
\\ 1 seen as non-childless below v
\\ 2 seen as childless below v
\\ 4 seen as not below v
\\
\\ At i unseen, search u upwards for ancestor which has been seen.
\\ If seen[u]==1 then i is a new extra childless below v.
\\ If seen[u]==2 then u was counted as a childless before, but turns out
\\ to have descendant i. Will now reckon i as the childless and no change
\\ to number of childless so far.
\\ If seen[u]==4 then i is not under v.
\\ For seen[u]==1,2, fill upwards i to u as 2,1,1,1,1.
\\ For seen[u]==4, fill upwards 4,4,4,4,4. This is filled while working
\\ upwards, in anticipation of most of the tree not under v.
\\ Setting seen[u]=4 also means cycles not under v are noticed.
\\
\\ flags=0 return the count.
\\ flags=1 return the seen[]==2 vertices.
\\
\\ Could instead build seen[]=0 meaning childless under v, then
\\ hammingweight() for count or select() gnot for vertices. Would be a
\\ touch cleaner, though maybe more copying to return the seen[] vector.
\\ print("vpar_INTERNAL_subtree_childless() "vpar" v="v);
my(seen=vectorsmall(#vpar),
ret=1);
seen[v]=2;
for(i=1,#vpar,
\\ print(" i="i" seen "seen[i]" ret="ret" "Vec(seen));
if(!seen[i],
my(u=i);
while(seen[u]=4; u=vpar[u],
if(seen[u],
if(seen[u]<4,
ret += bitand(seen[u],1);
\\ print(" found u="u" seen "seen[u]);
seen[i]=2;
my(t=i); until(t==u, seen[t=vpar[t]]=1);
\\ print(" to ret="ret" seen="Vec(seen));
);
break))));
\\ If v is in a cycle then its parent will be reckoned childless under v.
\\ Adjust down for that.
if(vpar[v] && seen[vpar[v]]==2,
ret--; seen[vpar[v]]=1);
\\ print(" end loop ret="ret" "Vec(seen));
if(flags,
ret=vector(ret);
my(upto=0);
for(v=1,#vpar, if(seen[v]==2, ret[upto++]=v)));
\\ print(" final ret="ret);
ret;
}
vpar_subtree_num_childless(vpar,v) = vpar_INTERNAL_subtree_childless(vpar,v,0);
{
addhelp(vpar_subtree_num_childless,
"num = vpar_subtree_num_childless(vpar,v)
Return the number of childless vertices in the subtree at and below v.
v itself is included if childless.
vpar can contain cycles. If v is part of a cycle then everything below all
of that cycle is counted. If there's nothing but the cycle then num==0.
This is equivalent to vpar_num_childless(vpar_subtree(vpar,v)).");
}
vpar_subtree_num_childless_vector(vpar) =
{
my(num_children=vpar_INTERNAL_num_children_vecsmall(vpar),
ret=vector(#vpar));
for(i=1,#vpar,
my(v=i);
while(v<=i && !num_children[v],
if(!ret[v], ret[v]=1);
if(vpar[v], ret[vpar[v]] += ret[v]);
(v=vpar[v]) || break; \\ to parent or stop at root
num_children[v]--));
\\ cycles are left with 1 child each
\\ sum ret[v] in the cycle for total num childless under the cycle
for(i=1,#vpar,
if(num_children[i]>0,
my(v=i, c=0);
until((v=vpar[v])==i, c+=ret[v]);
until((v=vpar[v])==i, ret[v]=c; num_children[v]=-1)));
ret;
}
{
addhelp(vpar_subtree_num_childless_vector,
"vec = vpar_subtree_num_childless_vector(vpar)
Return a vector where each vec[v] is the number of childless vertices in the
subtree at and below v, as per vec[v] = vpar_subtree_num_childless(vpar,v)
but calculated all together. vpar can contain cycles.");
}
vpar_subtree_childless_vertices(vpar,v) = \
vpar_INTERNAL_subtree_childless(vpar,v,1);
{
addhelp(vpar_subtree_childless_vertices,
"vec = vpar_subtree_childless_vertices(vpar,v)
Return a vector of the childless vertex numbers in the subtree at and below v.
vpar can contain cycles.
This is equivalent to setintersect(vpar_childless_vertices(vpar),
Set(vpar_subtree_vertices(vpar,v)))");
}
\\-----------------------------------------------------------------------------
\\ Rows
vpar_row_width_of_vertex(vpar,v) =
{
my(depths=vpar_depths_vector(vpar),
d=depths[v]);
sum(i=1,#vpar, depths[i]==d);
}
{
addhelp(vpar_row_width_of_vertex,
"width = vpar_row_width_of_vertex(vpar,v)
Return the number of vertices in the row containing vertex v.
This is how many with the same depth as v, per vpar_depth_of_vertex().
v itself is included so always width >= 1.
vpar can contain cycles. See vpar_depth_of_vertex() on depth as number of
proper ancestors.");
}
vpar_row_widths_vector(vpar) = \
Vec(vpar_INTERNAL_histogram(vpar_depths_vector(vpar),0));
{
addhelp(vpar_row_widths_vector,
"vec = vpar_row_widths_vector(vpar)
Return a vector where vec[d] is the number of vertices at depth d-1 in vpar.
vec[1] is the number of roots, the same as vpar_num_roots(vpar).
vec[2] is the number of all their children.
And so on down to vec[height+1], where height=vpar_height(vpar).
If vpar is the empty tree [] then the return is an empty vec.
vpar can contain cycles. See vpar_depth_of_vertex() on depth as number of
proper ancestors. For a forest, all of vec is non-zero since each parent is
at depth-1. For cyclics there can be 0s in vec due to cycle lengths making
all vertices have bigger depths.
Various different vpar can give the same vec. The number of distinct vec
occurring from forests of n vertices is 2^(n-1). This is since successive
row widths can be any number >=1 vertices of total n. Interpreting widths
as bit run lengths in a binary number, starting from highest run 1s, is then
2^(n-1) ways. For a tree, the top row is 1 vertex then the rest as a forest
so 2^(n-2).");
}
\\ MAYBE: Could have an up=1 parameter for rows upwards like vpar_seq_rows().
\\ That'd be as simple as Vecrev(vec) though.
\\ Could have vertices downwards within rows, though again that would be
\\ just apply(Vecrev,vec).
\\
vpar_row_vertices_vector(vpar) =
{
my(depths=vpar_depths_vector(vpar),
widths=vpar_INTERNAL_histogram(depths,0),
ret=apply(vector, Vec(widths)));
forstep(v=#vpar,1,-1,
my(d=depths[v]+1);
ret[d][widths[d]] = v;
widths[d]--);
ret;
}
{
addhelp(vpar_row_vertices_vector,
"vec = vpar_row_vertices_vector(vpar)
Return a vector where each vec[d] is a vector of vertices at depth d-1 in vpar.
vec[1] is a vector of the roots, the same as vpar_roots(vpar).
vec[2] is a vector of all their children, and so on.
Each vec[i] vector has vertices in increasing order (so are Set()s).
vec extends down to the deepest row, so 1 .. vpar_height(vpar)+1.
If vpar is the empty tree [] then the return is an empty vec.
vpar can contain cycles. See vpar_depth_of_vertex() on depth as number of
proper ancestors. Vertices in a row follow from these depths.");
}
\\------------------------------------------------------------------------------
\\ Row Position
vpar_rowpos_of_vertex(vpar,v,down=0) =
{
\\ Had some specific search up code here previously. It was nearly the
\\ same as the depths vector calculation, hence now sharing with that.
\\ Rowpos only needs depths of 1..v.
\\ Working upwards from 1 etc might make excursions into other later
\\ vertices. Going through them is necessary to match depths between
\\ different components of a forest.
\\ down=1 is likewise, but v..#vpar.
my(start=if(down,v,1),
end=if(down,#vpar,v),
depths=vpar_INTERNAL_depths_vector(vpar,start,end));
v=depths[v];
sum(i=start,end, depths[i]==v) - 1;
}
{
addhelp(vpar_rowpos_of_vertex,
"pos = vpar_rowpos_of_vertex(vpar,v,down=0)
Return the position of vertex v in its row. A row is vertices at the same
depth (vpar_depth_of_vertex()). Row position 0 is smallest vertex number in
the row, position 1 the next bigger, etc, hence number of vertices smaller
than v in its row.
Optional parameter \"down\" can be 1 to number row positions downwards so 0
is the biggest in the row, 1 the second biggest, etc. This is the number of
vertices bigger than v in its row.
vpar can contain cycles. Depths in and under cycles are as described in
vpar_depth_of_vertex(). Rows are then vertices at same depths.
Sum of positions as up and down is vertices other than v in the row,
sum(down=0,1, vpar_rowpos_of_vertex(vpar,v,down)) + 1
== vpar_row_width_of_vertex(vpar,v)");
}
vpar_rowpos_vector(vpar,down=0) =
{
my(ret=vector(#vpar));
if(#vpar,
my(depths=vpar_depths_vector(vpar),
upto=vectorsmall(vecmax(depths)+1,i,-1));
for(v=1,#vpar,
ret[v] = upto[depths[v]+1]++);
if(down,
for(v=1,#ret,
ret[v] = upto[depths[v]+1] - ret[v])));
ret;
}
{
addhelp(vpar_rowpos_vector,
"vec = vpar_rowpos_vector(vpar,down=0)
Return a vector where vec[v] is the position of vertex v in its row.
A row is vertices at the same depth (vpar_depth_of_vertex()).
The return is vec[v] = vpar_rowpos_of_vertex(vpar,v) but calculated all
together.
vpar can contain cycles. Depths in and under cycles are as described in
vpar_depth_of_vertex(). Rows are then vertices at same depths.
Possible vec occurring are the same as from vpar_siblingpos_vector().
Row positions are like sibling positions in that there must be enough
vertices at pos-1 to have those at pos after them.
So vpar_is_siblingpos_vector() tests for a rowpos vector, and number of
different vec occurring is vpar_siblingpos_vectors_count().");
}
\\-----------------------------------------------------------------------------
\\ Rows Sequence (Breadth-First)
vpar_seq_rows(vpar,up=0) = \
vpar_INTERNAL_seq_bucket_sort(vpar_depths_vector(vpar), up);
{
addhelp(vpar_seq_rows,
"seq = vpar_seq_rows(vpar,up=0)
Return a Vecsmall of the vertex numbers 1..#vpar by rows down the tree,
ie. breadth-first. The return type is Vecsmall since it is a permutation of
the integers 1..#vpar.
seq is roots first, then depth=1 vertices, then depth=2 vertices, and so on.
Vertices are by ascending vertex number within a row. This is downwards
(vpar_seq_is_downwards()) since each parent precedes its children.
Optional parameter \"up\" can be 1 for rows upwards from the deepest, and
within each row still by ascending vertex. This is an upwards sequence
(vpar_seq_is_upwards()) since children precede their parent.
vpar can be a forest. The sequence is still by depth and across rows,
paying no attention to which component tree contains the vertex. vpar can
contain cycles. Depths of cyclic vertices, and hence their rows, are per
vpar_depth_of_vertex().
For reference, vec=vecextract(vpar,vpar_seq_rows(vpar)) is not a reversible
code since some different vpars give the same extract. Such vpars may be
isomorphic or may not be (isomorphism either as free or rooted).
See vpar_row_vertices_vector() for separate rows. The downwards sequence is
their concatenation, Vec(seq) == concat(vpar_row_vertices_vector(vpar)).");
}
\\ MAYBE: And cf vpar_row_vertices_vector() maybe similar.
\\ vpar_seq_rows()
\\ up=0 \ down tree rows
\\ up=1 / up tree rows
\\ could flip the order within rows too by a bit
\\ up=0 \ ascending vertices in row
\\ up=2 / descending vertices in row
\\ Maybe same sort of option in vpar_INTERNAL_seq_bucket_sort, becoming the
\\ vecsort() flag=8 Vecrev (and flip when to negate keys).
\\-----------------------------------------------------------------------------
\\ Path Length, Vertices, Common Ancestor
\\ [t,d,c] = vpar_INTERNAL_top_depth_length(vpar,v) =
\\ Return a vector [t,d,c] where
\\ t = Root or cyclic vertex number closest at or above v.
\\ Can be t=v if v is itself a root or in a cycle.
\\ d = Distance from v to t. Can be 0 when t=v.
\\ c = Cycle length of the cycle at t, or 0 if t is a root not a cycle.
\\
vpar_INTERNAL_top_depth_length(vpar,v) =
{
\\ u is a search up from v looking for a root, ie. vpar[u]==0.
\\ h is a half-speed search up.
\\ If find u==h then they are part of a cycle.
\\ Cycle length is by working up around until u==h again.
\\
\\ Non-cycle "tail" length up from v is then by re-traversing upwards with
\\ u ahead by the cycle length, so that u==v at the first cyclic t at or
\\ above v.
\\
\\ Time taken is linear in the returned depth d and the cycle length c (if
\\ cyclic). The first loop has two stopping conditions, either u==h or u
\\ through #vpar many vertices. The former catches small d and c, the
\\ latter caps at #vpar if big c (instead of 2 cycles around big c).
\\
\\ Keeping distance up info in a vector would eliminate re-traversals, but
\\ initializing that vector would make time linear in #vpar rather than
\\ d+c.
\\
\\ Distance d where u==h found does not say anything about the desired
\\ depth v to t. Different depths reach u==h with same d. For example
\\
\\ 5 <-- 4
\\ \ | from u=1 reach u=4,h=4 at d=6, desiring depth=2
\\ -> 3
\\ | from u=2 reach u=5,h=5 at d=6, desiring depth=1
\\ 2
\\ |
\\ 1
\\
\\ The d with u==h is a multiple of the cycle length c though. If the
\\ desired depth is x, and du=2*dh steps up by u and h, then have
\\ dh-x == du-x mod c so that u==h, and hence dh==2*dh mod c and
\\ dh==0 mod c. What multiple of c is du or dh depends on x, so doesn't
\\ help to find c.
\\ print("vpar_INTERNAL_top_depth_length() v="v);
my(u=v, h=v);
for(d=0,#vpar,
vpar[u] || return([u,d,0]);
u=vpar[u];
d++;
vpar[u] || return([u,d,0]);
u=vpar[u];
if((h=vpar[h])==u, break));
\\ print(" cyclic at u="u" h="h);
my(c=1);
h=u; while((u=vpar[u])!=h, c++);
\\ print(" cycle length c="c);
u=v; for(i=1,c, u=vpar[u]); \\ ahead by c steps
for(d=0,#vpar,
if(u==v, return([v,d,c])); \\ equal at first cyclic
u=vpar[u]; v=vpar[v]);
[0,0,0];
}
\\ [c,u_dist,v_dist] = vpar_INTERNAL_common_ancestor_and_distances(vpar,u,v)
\\ Return a vector [c, u_dist, v_dist] where
\\ c = common ancestor of u,v, or 0 if no common ancestor.
\\ u_dist = distance from u to c, possibly 0 if u==c.
\\ v_dist = distance from v to c similarly.
\\ If u,v are in or under a cycle then common ancestor per rule of
\\ vpar_common_ancestor().
vpar_INTERNAL_common_ancestor_and_distances(vpar,u,v) =
{
\\ print("vpar_INTERNAL_common_ancestor_and_distances() "vpar" u="u" v="v);
my(u_vec=vpar_INTERNAL_top_depth_length(vpar,u),
v_vec=vpar_INTERNAL_top_depth_length(vpar,v),
c=u_vec[3]);
\\ print(" "u_vec" "v_vec);
if(c == v_vec[3] && (c || u_vec[1]==v_vec[1]),
\\ same both are root, or both are cycle and cycle lengths same,
\\ and if roots then same roots
my(m=min(u_vec[2],v_vec[2]),
ud=u_vec[2]-m,
vd=v_vec[2]-m);
\\ print(" balance m="m" ud="ud" vd="vd);
for(i=1,ud, u=vpar[u]);
for(i=1,vd, v=vpar[v]);
\\ print(" at u="u" v="v);
for(i=0,m,
if(u==v,return([u, ud+i, vd+i]));
u=vpar[u]; v=vpar[v]);
\\ (u==vpar[u_vec[1]] && v==vpar[v_vec[1]]) || error();
\\ print(" cycles");
my(t=u_vec[1]);
for(i=0,c-1,
if(t==v_vec[1],
\\ print(" utop to vtop i="i);
return(if(i < ((c+1)>>1),
\\ print(" take vtop");
[v_vec[1], u_vec[2]+i, v_vec[2]],
\\ print(" take utop");
[u_vec[1], u_vec[2], v_vec[2]+if(i,c-i)])));
t=vpar[t]));
[0,0,0];
}
vpar_path_length(vpar,u,v) =
{
my(vec=vpar_INTERNAL_common_ancestor_and_distances(vpar,u,v));
if(vec[1], vec[2]+vec[3], -1);
}
{
addhelp(vpar_path_length,
"num = vpar_path_length(vpar,u,v)
Return the path length from u to v within vpar.
If u==v then the return is 0.
If u and v are in different components of a forest then return -1.
vpar can contain cycles. Path length is by the shorter way across a cycle.");
}
vpar_path_lengths_vector(vpar,v) =
{
\\ print("vpar_path_lengths_vector() "vpar" v="v);
\\ ret[u]=-2 undetermined
\\ ret[u]=-1 known to be not under the target v
\\ ret[u]>=0 distance to v, by shortest path
\\
\\ Upwards from v set distance h away from v.
\\ Cycle noticed by ret[] already set.
\\ Re-fill the second half of the cycle with the shorter distance to target v.
\\
my(ret=vector(#vpar,v,-2),
h=-1); \\ distance above v
while(ret[v]=h++; (v=vpar[v]), \\ upwards from v, set ret[v] for ancestors of v
\\ print(" up v="v);
if(ret[v]>=0, \\ cycle
\\ print(" cycle h="h" prev "ret[v]", length="h-ret[v]+1);
my(c = (h-ret[v]+1)>>1); \\ half cycle length
for(i=1,c, v=vpar[v]); \\ half way around cycle
h -= c-1; \\ distance to v ready for pre-decr
\\ print(" half around to v="v" h="h", by c="c);
for(i=1,c, ret[v=vpar[v]]=h--);
break));
\\ print(" above "ret);
\\ Upwards from each vertex.
\\ Fill ret[v]=-1 on the way up. This is right setting if stop at a root,
\\ since that means target v is not reachable.
\\ If find ret[v]==-1 then already known the target v is not reachable.
\\ This includes a cyclic component not containing v, since ret[v]=-1 is
\\ stored along the way and is revisited by around the cycle.
\\ If ret[v]>=0 found then re-traverse and fill with that plus h.
for(i=1,#vpar,
if(ret[i]==-2,
\\ print(" i="i);
v=i; h=1;
while(h++; ret[v]=-1; (v=vpar[v]),
if(ret[v]!=-2,
if(ret[v]>=0,
\\ print(" found v="v" ret="ret[v]" h="h);
h += ret[v];
v=i;
until(ret[v=vpar[v]]>=0, ret[v]=h--);
\\ print(" filled "ret);
);
break))));
\\ print(" return "ret);
ret;
}
{
addhelp(vpar_path_lengths_vector,
"vec = vpar_path_lengths_vector(vpar,v)
Return a vector where each vec[u] is the distance from u to given vertex v.
If u and v are in different component trees of a forest then vec[u]=-1.
This is vec[u] = vpar_path_length(vpar,u,v) but calculated all together.
vpar can contain cycles. Each path length is by its shorter way around a
cycle.");
}
vpar_path_lengths_matrix(vpar) =
{
\\ Had tried some direct code here, working up to a root or cycle and
\\ around the cycle. The root or cycle vertices get distances to
\\ everything else, so can work back down so for example the child of a
\\ root takes the root row and +1, but not overwriting distances already
\\ known for the child, since they are below it. Doing this works, but
\\ it's still O(n^2) and is a chunk of complicated code for what would be
\\ expected only modest speedup over individual vpar_path_lengths_vector()
\\ rows.
Mat(vectorv(#vpar,v, vpar_path_lengths_vector(vpar,v)));
}
{
addhelp(vpar_path_lengths_matrix,
"m = vpar_path_lengths_matrix(vpar)
Return a matrix where m[u,v] = path length between vertices u,v.
If u and v are in different component trees of a forest then m[u,v]=-1.
This is m[u,v] = vpar_path_length(vpar,u,v) but calculated all together.
vpar can contain cycles. Each path length is by its shorter way around a
cycle.
Properties of the path lengths matrix (also called distance matrix) of a
tree include
matdet(m) == (-1)^(n-1) * (n-1) * 2^(n-2) where n=#vpar
(Graham and Pollak)
See examples/path-lengths-matrix.gp for an example of this matrix
determinant, and a form with lengths are squared.
Entries m[u,v]=1 are neighbouring vertices (vpar_is_neighbour()).
See vpar_adjacency_matrix() for those (and everything else 0s).
Like the adjacency matrix, vpar is effectively treated as labelled but
unrooted. The number of distinct path lengths matrices which occur is
therefore vpar_count_unrooted().
Entries m[u,v]>=1 are reachable vertices u to v (some non-empty path).
See vpar_reachability_matrix() for booleans indicating that.");
}
\\ R.L. Graham and H.O. Pollak, "On the Addressing Problem for Loop
\\ Switching", Bell Systems Tech J, volume 50, 1971, pages 2495-2519.
\\ http://www.math.ucsd.edu/~ronspubs/71_05_loop_switching.pdf
vpar_path_vertices(vpar,u,v) =
{
my(vec=vpar_INTERNAL_common_ancestor_and_distances(vpar,u,v));
if(vec[1], \\ common ancestor c
my(ret=vector(vec[2]+vec[3]+1));
for(i=1,vec[2], \\ u (inclusive) to c (exclusive)
ret[i]=u; u=vpar[u]);
forstep(i=#ret,#ret-vec[3],-1, \\ v (inclusive) to c (inclusive)
ret[i]=v; v=vpar[v]);
ret;
,
[]);
}
{
addhelp(vpar_path_vertices,
"vec = vpar_path_vertices(vpar,u,v)
Return a vector of the vertices comprising the path from u to v in vpar,
in sequence from u to v and including those endpoints.
The path is u up to the common ancestor of u,v, then down again to v.
If u==v then return that single vertex [u].
If u and v are in different components of a forest then return empty [].
In all cases length #vec == vpar_path_length(vpar,u,v) + 1.
vpar can contain cycles. The path is by the shorter way around a cycle, or
if two equal ways then up from u to the common ancestor by the rule in
vpar_common_ancestor().");
}
vpar_common_ancestor(vpar,u,v) = \
vpar_INTERNAL_common_ancestor_and_distances(vpar,u,v)[1];
{
addhelp(vpar_common_ancestor,
"c = vpar_common_ancestor(vpar,u,v)
Return the closest common ancestor vertex of u and v.
u and v themselves are included so if u==v then return that u = v.
If vpar is a forest and u,v are in different component trees then return 0
as they have no common ancestors at all.
vpar can contain cycles. When u,v meet in a cycle, c is chosen so u upto c
and v upto c are together the shorter way around the cycle (giving
vpar_path_length(vpar,u,v)). Suppose u,v reach the cycle at vertices uc,vc.
The rule is c=uc if dist(uc upto vc) <= dist(uc upto vc), or c=vc otherwise.
Equal dists is when uc,vc are opposite each other around an even length
cycle. In that case uc is preferred,");
}
\\-----------------------------------------------------------------------------
\\ Subtree Sizes
vpar_subtree_size(vpar,v) =
{
\\ The strategy is to search upwards from each i looking for the root, or
\\ the target vertex v, or a vertex marked seen[] by a previous search up.
\\
\\ seen[p] = 0 if not yet seen
\\ = 1 if seen and not under v
\\ = 2 if seen and under v, including seen[v]=2 itself
\\
\\ seen[v]=2 initially, since it is under itself.
\\ During traversal up, set successive seen[p]=1 anticipating not under v.
\\
\\ If find seen[p]==1 then not under v so stop. This includes going
\\ around a cycle and seeing seen[p]=1 set during this traverse up.
\\
\\ If find seen[p]==2 so i is under v then re-traverse i upwards to mark
\\ seen[] = 2, and count them towards ret.
\\
\\ Jenner, Langey, McKenziez, "Tree Isomorphism and Some Other Complete
\\ Problems for Deterministic Log-Space", note in the context of parallel
\\ algorithms that subtree size can be formed by a tour through the
\\ forest, from which count how much is below v. Is that a tree
\\ representation with ordered neighbours, so know where going and coming
\\ back?
\\ print("vpar_subtree_size() v="v" "vpar);
my(seen=vectorsmall(#vpar),
ret=1);
seen[v]=2; \\ gives an error if v out of range
for(i=1,#vpar,
\\ print("i="i" seen[i]="seen[i]" ret="ret);
my(p=i);
until(!(p=vpar[p]), \\ stop at root if not below v
if(seen[p],
\\ print(" stop p="p" seen "seen[p]);
if(seen[p]==2, \\ at or below v, set to 2s below
p=i;
while(seen[p]!=2,
\\ print(" set p="p" seen 2");
seen[p]=2; ret++; p=vpar[p]));
break);
seen[p]=1));
ret;
}
{
addhelp(vpar_subtree_size,
"num = vpar_subtree_size(vpar,v)
Return the number of vertices in the subtree at and below v in vpar.
Vertex v itself is included in the size, so always >= 1.
vpar can contain cycles. Subtree below v means vertices which can reach v
by going upwards. If v is in a cycle then this is all of the cycle and
everything under the cycle, so the size of the component containing v.");
}
vpar_subtree_sizes_vector(vpar) =
{
\\ Up from childless, to set sizes under the cyclics.
\\ Then around each cycle to set all to the total in the cycle.
\\ print("vpar_subtree_sizes_vector() "vpar);
\\ print(" table "Vec(table));
my(num_children=vpar_INTERNAL_num_children_vecsmall(vpar),
ret=vector(#vpar,v,1),
v, p);
for(i=1,#vpar,
v=i;
while(!num_children[v] && v<=i,
if(p=vpar[v], ret[p]+=ret[v]; num_children[v=p]--, break)));
\\ any cycles
for(i=1,#vpar,
if(num_children[i],
v=i;
my(total=0);
until(v==i, total += ret[v=vpar[v]]); \\ total sizes in the cycle
v=i;
until(v==i, num_children[v]=0; ret[v=vpar[v]]=total)));
ret;
}
{
addhelp(vpar_subtree_sizes_vector,
"vec = vpar_subtree_sizes_vector(vpar)
Return a vector where vec[v] is the number of vertices in the subtree at v.
Each size includes v itself, so vec[v] >= 1.
This is vec[v] = vpar_subtree_size(vpar,v), but calculated all together.
vpar can contain cycles. See vpar_subtree_size() on cyclic sizes.");
}
vpar_subtree_vertices(vpar,v) =
{
\\ print("vpar_subtree_vertices() v="v" "vpar);
\\ chain[i] = next vertex in chain
\\ -1 if end of chain under v
\\ -2 if i not under v
\\ 0 if not yet seen
\\ chain[] is a linked list starting at v, initially empty chain[v]=-1.
\\ "size" is how many vertices in the chain starting at v, inclusive of v.
\\
\\ The loop works upwards from successive i looking for chain[p]!=0 which
\\ is something seen before, or the root. Each chain[p] is set -2
\\ anticipating "not under v". Cycles not under v are noticed by seeing
\\ that -2.
\\ If find chain[p] >= -1 then i is under v and re-traverse i upwards
\\ inserting at the head of the chain[v] linked list.
my(chain = vectorsmall(#vpar),
size = 1);
chain[v] = -1; \\ and gives an error if bad v
for(i=1,#vpar,
\\ print(" i="i" chain "chain);
my(p=i);
if(!chain[p],
while(p=vpar[p],
if(chain[p],
if(chain[p]>=-1,
p=i;
until(chain[p]>=-1,
\\ print(" set chain p="p" to prev="prev);
size++; chain[p]=chain[v]; chain[v]=p; p=vpar[p]));
break);
chain[p]=-2)));
\\ print(" v="v" size "size" chain "chain);
my(ret=vector(size));
size=0; \\ now used as position in ret[]
until(v==-1, ret[size++]=v; v=chain[v]); \\ pre-increment
\\ print(" ret "ret);
ret;
}
{
addhelp(vpar_subtree_vertices,
"vec = vpar_subtree_vertices(vpar,v)
Return a vector of all vertex numbers which are in the subtree at and below v.
The return includes v itself. The vertices are in an unspecified order.
vpar can contain cycles. Subtree below v means vertices which can reach v
by going upwards. If v is in a cycle then this is its entire component.");
}
vpar_subtree(vpar,v) = vpar_keep_vertices(vpar,vpar_subtree_vertices(vpar,v));
{
addhelp(vpar_subtree,
"vpar = vpar_subtree(vpar,v)
Return the subtree of vpar at and below vertex v.
This is keep vpar_subtree_vertices() and delete everything else.
Vertices are in the same order as in vpar, but renumbered down to collapse
out everything except the subtree kept. The return is a single component.
Its root is v renumbered down by the collapsing.
vpar can contain cycles. If v is in a cycle then its subtree is its entire
component (renumbered down).");
}
vpar_delete_subtree(vpar,v) = \
vpar_delete_vertices(vpar,vpar_subtree_vertices(vpar,v));
{
addhelp(vpar_delete_subtree,
"vpar = vpar_delete_subtree(vpar,v)
Return vpar with the subtree at and below vertex v deleted.
The return has vertices are in the same order as vpar, but renumbered down
to collapse out the v subtree.
vpar can contain cycles. If v is in a cycle then its subtree is its entire
component and that is deleted.");
}
\\-----------------------------------------------------------------------------
\\ Descendants
vpar_minmax_descendant_of_vertex(vpar,v,func) =
{
\\ For min, take vertices i = 1..v and search upwards from each to see if
\\ reach v. If so then i is the minimum descendant.
\\ seen[u] is when a vertex has already been seen on a search up so can stop
\\ there knowing not under v.
\\ seen[] avoids n^2 time repeatedly going up a long path not under v.
\\ If nothing smaller than v reaches v, then v itself must be the min.
\\ For max the same, but vertices high to low i = #vpar..v.
\\ print("vpar_minmax_descendant_of_vertex() "vpar" v="v" "func);
my(seen=vectorsmall(#vpar));
seen[v]=0; \\ provoke an error if v does not exist
forstep(i=func(1,#vpar), v, -func(1,-1), \\ step 1 for min, -1 for max
\\ print("i="i);
my(u=i);
while(u && !seen[u],
\\ print(" at u="u);
if(u==v, return(i));
seen[u]=1;
u=vpar[u]));
\\ print("reached v="v);
v;
}
{
addhelp(vpar_minmax_descendant_of_vertex,
"descendant = vpar_minmax_descendant_of_vertex(vpar,v,func)
Return the minimum or maximum descendant of vertex v.
\"func\" parameter is min for smallest descendant or max for biggest,
descendant = vpar_minmax_descendant_of_vertex(vpar,v,min)
descendant = vpar_minmax_descendant_of_vertex(vpar,v,max)
Descendants are reckoned inclusive of v itself. So the return can be v if
it is the min or max, including when it has no children at all.
vpar can contain cycles. A descendant is any vertex which can go upwards to
reach v (as per vpar_is_ancestor()). If v is in a cycle then this means all
of its component.
The return is equivalent to vecmin(vpar_subtree_vertices(vpar,v)), or
vecmax, but implemented with less intermediate work.
See vpar_minmax_descendants_vector() to calculate for all vertices.");
}
vpar_minmax_descendants_vector(vpar,func) =
{
\\ v=1 upwards sets 1 as minimum of itself and all above.
\\ v=2 upwards sets 2 likewise, etc.
\\ print("vpar_minmax_descendants_vector() "vpar);
my(vec=vector(#vpar));
if(#vpar, \\ #vpar==0 would get start wrong
my(start=func(1,#vpar)); \\ 1 for min, #vpar for max
forstep(i=start, #vpar+1-start, -func(1,-1), \\ step 1 for min, -1 for max
my(v=i);
while(!vec[v],
\\ print(" v="v" set "i);
vec[v]=i;
(v=vpar[v]) || break)));
vec;
}
{
addhelp(vpar_minmax_descendants_vector,
"vec = vpar_minmax_descendants_vector(vpar,func)
Return a vector where each vec[v] is the minimum or maximum descendant of
vertex v. Descendants are reckoned inclusive of v itself.
\"func\" parameter is min for smallest descendant or max for biggest,
vec = vpar_minmax_descendants_vector(vpar,min)
vec = vpar_minmax_descendants_vector(vpar,max)
This is vec[v] = vpar_minmax_descendant_of_vertex(vpar,v,func) but
calculated all together. vpar can contain cycles.
See vpar_count_min_descendant_increasing() counting trees or forests with
min vec increasing with v.");
}
\\ vpar_INTERNAL_attach_transform_result() takes vector a[] and returns a
\\ result b[n] where b[] is calculated as an "attach transform" of a.
\\ The transform is
\\
\\ b[n] = a[n] + sum(t=1,i-1, b[n-t]*(n-t)*a[t])
\\
\\ This is a sub-tree formed from vertex numbers 1..t and attached
\\ underneath one of the n-t vertices of the remainder. A sub-tree of size
\\ t has a[t] different forms. The remainder is built by the same
\\ attachments method so it has b[n-t] forms, then factor *(n-t) for where
\\ the sub-tree attaches under.
\\
\\ Trees are parameter forest=0. Forests are forest=1. For forests, the
\\ factor is *(n-t+1) places to attach. The extra 1 allows subtree 1..t to
\\ be a new root (separate component) in the forest rather than attached
\\ under the remainder.
\\
\\ In the code, vector b holds quantity b[n]*n since that allows
\\ b[n-t]*(n-t) to becomes just b[n-t] with factor already rather than
\\ multiplying the same each time b is re-used. The final b[n] is returned
\\ before this *n.
\\
vpar_INTERNAL_attach_transform_result(a,forest) =
{
my(b=vector(#a));
for(n=1,#a,
b[n] = a[n] + sum(t=1,n-1, b[n-t]*a[t]);
if(n==#a,return(b[n]));
b[n] *= (n+forest));
}
\\ vec = vpar_INTERNAL_factorials_vector(n,offset=0)
\\ Return vector of n many factorials.
\\ offset=0 for a[n]=n!
\\ offset=1 for a[n]=(n-1)!, including a[1] = 0! = 1 (as usual).
vpar_INTERNAL_factorials_vector(n,offset=0) =
{
my(a=vector(n));
a[1]=1;
for(i=2,n, a[i] = a[i-1]*(i-offset));
a;
}
vpar_count_min_descendant_increasing(n,forest=0) =
{
\\ if(forest<0, n--;forest=1);
if(n<=0, 1,
vpar_INTERNAL_attach_transform_result(vpar_INTERNAL_factorials_vector(n,1),
forest));
}
\\ print(vector(10,n, vpar_count_min_descendant_increasing(n,-1)));
\\ [1, 1, 3, 13, 71, 461, 3447, 29093, 273343, 2829325]
{
addhelp(vpar_count_min_descendant_increasing,
"count = vpar_count_min_descendant_increasing(n,forest=0)
Return the number of vpar trees which have minimum descendant increasing
with vertex number, so vpar_minmax_descendants_vector(vpar,min) is already
sorted in ascending order.
Optional parameter \"forest\" can be 1 to count forests, or can be -1 to
count root=1 trees.
The count is a recurrence,
C(n) = if(n==0,1, (n-1)! + sum(t=1,n-1, C(n-t)*(n-t+forest)*(t-1)!));
= 1,1,2,7,33,191,1297,... forest=0 (A104981)
= 1,1,3,13,71,461,3447,... forest=1 (A003319)
= 1,1,1,3,13,71,461,... forest=-1 one later
This follows by considering runs of equal min descendant. Suppose the last
run is t vertices n-t+1..n. They are all min descendant n-t+1 so a path
with n-t+1 at the bottom. The others in the path permute (t-1)! ways. If
t=n then this is the entire tree. Otherwise the remaining preceding n-t are
to be increasing min descendant, and the path can attach under any of n-t,
or for a forest also a separate component so n-t+1. For forests, the sum
can run to t=n to include the (n-1)!, but not for trees.
Root=1 trees are a vertex above an n-1 forest, or in the above construction
(n-2)! single path since the root is fixed, then sum to t=n-2 and factor n-t
like all trees. But in fact the formula shown works with forest=-1 since
t=n-1 factor n-t-1=0 eliminates that term and in the rest it can be verified
by induction that -1 shortfall sum(t=1,n-1,C(n-t_*(t-1)!) = (n-2)*(n-2)! is
accounted for by the bigger (n-1)! rather than (n-2)!.
The sequence of parts t taken is a composition of n (partition with order),
and path permutations (t-1)! for each term, but also factor n-t which is
total of the composition after t.
Forests is the number of indecomposable permutations. The recurrence here
is the same as Andrew King gives for that count (with indices adjusted).
Andrew King, \"Generating Indecomposable Permutations\", Discrete
Mathematics, volume 306, number 5, 2006, pages 508-518, theorem 4.
Max descendant increasing (instead of min) has the same recurrence
construction as above but working from the start vertices 1..t, except the
root=1 case has max_descendant[1]=n so all [n,n,...,n] which is a path with
n at the bottom and (n-2)! permutations within.
Min descendant decreasing (instead of increasing), is only [1,1,...,1] since
min_descendant[1]=1 always, so a path with v=1 at the bottom and (n-1)! of
the rest. For root=1 can only have v=1 as the top, so any n>=2 is none.
Max descendant decreasing is [n,n,...,n] since always have
max_descendant[n]=n, so a path with n at the bottom and (n-1)! for the rest,
or (n-2)! for root=1 fixed 1 at top.
See vpar_count_min_ancestor_increasing() for similar but ancestors.");
}
\\ GP-DEFINE C(n) = if(n==0,1, (n-1)! + sum(t=1,n-1, C(n-t)*(n-t)*(t-1)!));
\\ GP-Test vector(7,n,n--; C(n)) == [1,1,2,7,33,191,1297]
\\
\\ GP-DEFINE C(n) = if(n==0,1, (n-1)! + sum(t=1,n-1, C(n-t)*(n-t+1)*(t-1)!));
\\ GP-Test vector(7,n,n--; C(n)) == [1,1,3,13,71,461,3447]
\\
\\ GP-DEFINE C(n,forest) = if(n==0,1, (n-1)! + sum(t=1,n-1, C(n-t,forest)*(n-t+forest)*(t-1)!));
\\ GP-Test my(t=n-1); n-t-1 == 0
\\ GP-Test vector(7,n,n--; C(n,0)) == [1,1,2,7,33,191,1297]
\\ GP-Test vector(7,n,n--; C(n,1)) == [1,1,3,13,71,461,3447]
\\ GP-Test vector(7,n,n--; C(n,-1)) == [1,1,1,3,13,71,461]
\\ GP-Test vector(10,n,n++; sum(t=1,n-2,C(n-t,-1)*(t-1)!)) == \
\\ GP-Test vector(10,n,n++; (n-2)*(n-2)!)
\\ GP-DEFINE B(n) = if(n==0,1, (n-2)! + sum(t=1,n-2, C(n-t,-1)*(n-t)*(t-1)!));
\\ GP-Test vector(10,n,n++; B(n)) == \
\\ GP-Test vector(10,n,n++; C(n,-1))
\\-----------------------------------------------------------------------------
\\ Ancestors
\\ Includes self as ancestor same as vpar_common_ancestor().
vpar_is_ancestor(vpar,v,ancestor) =
{
\\ Cyclics are handled by running up s at half speed of v.
\\ If find s==v then have gone into a cycle. This catches v close below a
\\ small cycle.
\\ Never need to look at more than #vpar steps of v. This catches a big
\\ cycle without v going around it twice.
\\
\\ If tail length t and cycle length c then this takes t steps for s to
\\ reach the cycle, then up to c steps for v to reach s (closing the gap
\\ by 1 each time).
vpar[v]; vpar[ancestor]; \\ validate v and ancestor
my(s=v);
for(i=0,#vpar>>1,
if(v==ancestor,return(1));
(v=vpar[v]) || break;
if(v==ancestor,return(1));
((v=vpar[v]) && (s=vpar[s])!=v) || break);
0;
}
{
addhelp(vpar_is_ancestor,
"bool = vpar_is_ancestor(vpar,v,ancestor)
Return 1 if v has \"ancestor\" as an ancestor, or return 0 if not.
Ancestors are reckoned inclusive of v itself, so if v==ancestor then return 1.
vpar can contain cycles. The test is whether going up v, vpar[v],
vpar[vpar[v]], etc, eventually reaches the target \"ancestor\". Vertices in
a cycle go around so all are ancestors of each other, and all are ancestors
of non-cyclics below all of them too.");
}
vpar_ancestors(vpar,v) =
{
my(ret=vector(vpar_depth_of_vertex(vpar,v)+1));
for(i=1,#ret, ret[i]=v; v=vpar[v]);
ret;
}
{
addhelp(vpar_ancestors,
"vec = vpar_ancestors(vpar,v)
Return a vector of v and its ancestors up to the root of its component tree,
[v, parent, grandparent, ..., root].
vpar can contain cycles. If v is in a cycle or has a cycle above then the
ancestor list stops at the vertex before repeat (already in the list).
In all cases vec is all vertices w which are vpar_is_ancestor(vpar,v,w), in
path-up order; and length #vec == vpar_depth_of_vertex(vpar,v) + 1.");
}
vpar_minmax_ancestor_of_vertex(vpar,v,func) =
{
vpar[v]; \\ validate v
my(s=v, m=v);
for(i=0,#vpar>>1,
m=func(m,v);
(v=vpar[v]) || break;
m=func(m,v);
((v=vpar[v]) && (s=vpar[s])!=v) || break);
m;
}
{
addhelp(vpar_minmax_ancestor_of_vertex,
"ancestor = vpar_minmax_ancestor_of_vertex(vpar,v,func)
Return the minimum or maximum vertex number at and above v.
\"func\" parameter is min for minimum or max for maximum,
ancestor = vpar_minmax_ancestor_of_vertex(vpar,v,min)
ancestor = vpar_minmax_ancestor_of_vertex(vpar,v,max)
Ancestors are reckoned inclusive of v itself, so the return can be v if it
is the minimum or maximum or if v is a root so nothing above.
vpar can contain cycles. In a cycle, all are ancestors of each other, so
all have the same minimum or maximum. Vertices below the cycle have
additional candidates for minimum or maximum.
See vpar_minmax_ancestors_vector() to calculate for all vertices.");
}
vpar_minmax_ancestors_vector(vpar,func) =
{
\\ The stragegy is to start at a vertex v and search upwards until an
\\ already established above[] value, or the root. The root has nothing
\\ above so treated as an established "above" of 0.
\\
\\ The vertices seen on the way up are pushed onto a linked list which is
\\ followed down again to set successive above=max(v,above). The linked
\\ list is recorded in the above[] vector using negative vertex numbers.
\\
\\ If the search above reaches a negative above[] then that is a cycle in
\\ the upwards search. In that case a pass down the cyclic part finds the
\\ maximum. That is treated as the "established" above to propagate down.
\\ That propagation puts the same value into all of the cycle, then any
\\ non-cyclic part might increase or decrease it.
\\ print(vpar);
my(above=vector(#vpar));
for(i=1,#vpar,
if(above[i], next());
\\ print("i="i);
my(v=i,prev=0,m=0);
while(1,
\\ print(" stack "v" -> ",-prev);
above[v]=-prev; prev=v;
v=vpar[v];
if(!v, m=prev; break); \\ at root
if((m=above[v]), \\ print(" found existing or cycle");
if(m<0,
my(s=m=prev); \\ print(" cycle at seen v="v", top "prev);
while(m=func(m,s); s!=v, \\ run down cycle part for maximum
\\ print(" down s="s" to m="m" next "above[s]);
s=-above[s]));
break));
\\ print(" descend m="m" v="v" "if(v,above[v]));
while(prev>0,
v=prev; prev=-above[v]; \\ pop from stack
\\ print(" v="v" store m=",func(m,v)" pop to "prev);
above[v] = m = func(m,v);
);
);
above;
}
{
addhelp(vpar_minmax_ancestors_vector,
"vec = vpar_minmax_ancestors_vector(vpar,func)
Return a vector where vec[v] is the minimum or maximum vertex number at or
above v.
\"func\" parameter is min for minimum or max for maximum,
vec = vpar_minmax_ancestors_vector(vpar,min)
vec = vpar_minmax_ancestors_vector(vpar,max)
This is vec[v] = vpar_minmax_ancestor_of_vertex(vpar,v,func) but calculated
all together.
vpar can contain cycles. In a cycle, all are ancestors of each other, so
all have the same minimum or maximum. Vertices below the cycle have
additional candidates for minimum or maximum.
See vpar_count_min_ancestor_increasing() counting trees or forests with min
ancestor increasing with v.");
}
vpar_count_min_ancestor_increasing(n,forest=0) =
{
if(forest<0, vpar_count(n,forest),
n, vpar_INTERNAL_attach_transform_result(vector(n,i,vpar_count(i,-1)),
forest),
1); \\ n==0
}
{
addhelp(vpar_count_min_ancestor_increasing,
"count = vpar_count_min_ancestor_increasing(n,forest=0)
Return the number of vpar trees which have minimum ancestor increasing with
vertex number, so vpar_minmax_ancestors_vector(vpar,min) is already sorted
in ascending order.
Optional parameter \"forest\" can be 1 to count forests, or can be -1 to
count root=1 trees.
The count for trees or forests is a recurrence
C(n) = if(n==0,1, n^(n-2) + sum(t=1,n-1, C(n-t) * (n-t+forest) * t^(t-2)))
= 1,1,2,8,47,365,... if forest=0
= 1,1,3,14,87,675,... if forest=1
This follows by working along the min ancestor vector. Suppose 1..t have
the same min ancestor. This is 1 since min_ancestor[1]=1 always. These
vertices are a root=1 sub-tree and there are t^(t-2) such. If t=n then it
is the entire C(n). If t=2 if requiring root=1.
See vpar_count_min_descendant_increasing() for similar but descendants.");
}
\\ GP-DEFINE C(n,forest) = if(n==0,1, n^(n-2) + sum(t=1,n-1, C(n-t,forest)*(n-t+forest)*t^(t-2)));
\\ GP-Test vector(6,n,n--; C(n,0)) == [1,1,2,8,47,365]
\\ GP-Test vector(6,n,n--; C(n,1)) == [1,1,3,14,87,675]
\\ vector(8,n,n++; C(n,1))
\\ not in OEIS: 2,8,47,365
\\ not in OEIS: 3, 14, 87, 675, 6343, 71176, 947341, 14878951
\\ different from A335849 = 3,14,87,675,6282
\\
\\ Putting forest=-1 into the general case doesn't give root=1 counts,
\\ unlike min descendants formula. Don't know any significance to n-t-1, it
\\ would be something like no attach subtree under the root of the rest, and
\\ rest n-t>=2 vertices, applied recursively.
\\ vector(10,n,n--; C(n,-1))
\\ not in OEIS: 5, 34, 288, 2977, 37046, 547998, 9504900
\\-----------------------------------------------------------------------------
\\ Weights and Centroid
vpar_weight_of_vertex(vpar,v) =
{
\\ For a tree, if v is not a valid vertex of vpar then will get an error
\\ from num_below[p] with p=0 at u=root.
\\ For a forest, the same might happen for reaching the root of a
\\ component not containing v, but if v is reached first then no error but
\\ a fairly useless answer.
\\ ENHANCE-ME: Could like any bad v to provoke an error, if easily done.
my(num_below=vectorsmall(#vpar,i,1),
seq=vpar_INTERNAL_seq_upwards(vpar),
weight=0);
for(i=1,#seq,
my(u=seq[i], p=vpar[u]);
if(u==v, break);
num_below[p]+=num_below[u];
if(p==v, weight=max(weight,num_below[u])));
max(weight,#vpar-num_below[v]);
}
{
addhelp(vpar_weight_of_vertex,
"weight = vpar_weight_of_vertex(vpar,v)
Return the weight of vertex v in vpar. This is the number of vertices in
the biggest neighbouring subtree, either a child subtree or the tree part
reached from the parent.
vpar must be a single tree, not a forest.");
}
vpar_weights_vector(vpar) =
{
my(subtree_sizes=vpar_subtree_sizes_vector(vpar),
n=#vpar,
weights=apply(s->n-s, subtree_sizes), \\ parent weights
p);
for(v=1,#vpar,
if((p=vpar[v]), weights[p]=max(weights[p],subtree_sizes[v])));
weights;
}
{
addhelp(vpar_weights_vector,
"vec = vpar_weights_vector(vpar)
Return a vector where vec[v] is the weight of vertex v.
Weight is the number of vertices in the biggest neighbouring subtree,
being either a child subtree or the tree part reached from the parent.
The return is the same as vec[v] = vpar_weight_of_vertex(vpar,v), but
calculated all together.
vpar must be a single tree, not a forest.");
}
vpar_centroids(vpar) = vpar_centroids_and_weight(vpar)[1];
{
addhelp(vpar_centroids,
"vec = vpar_centroids(vpar)
Return a vector of the centroid vertex numbers in vpar.
vpar must be a single tree, not a forest.
The centroid vertices have the minimum weight (per vpar_weights_vector())
of any vertices in the tree, or equivalently they are vertices where
neighbouring sub-tree sizes are all <= 1/2 total vertices.
The empty tree has no vertices so for it the return is empty [].
Otherwise a tree has either 1 centroid vertex of unique minimum weight, or 2
adjacent vertices which are equal minimum.");
}
\\ maybe cf: Mitchell, "Algorithms on Trees and Maximal Outerplanar Graphs:
\\ Design, Complexity Analysis and Data Structures Study", Ph.D. Thesis,
\\ University of Virginia, 1977.
\\
vpar_centroids_and_weight(vpar) =
{
my(seq=vpar_INTERNAL_seq_upwards(vpar),
num_below =vectorsmall(#vpar,v,1), \\ num vertices below, including self
max_weight=vectorsmall(#vpar), \\ max child subtree size under v
centroid_weight=#vpar, \\ minimum so far
centroids=vector(2)); \\ vertex number or 0
for(i=1,#seq,
my(v=seq[i],
p=vpar[v],
pweight=#vpar-num_below[v],
vmax=max(pweight,max_weight[v]));
\\ print("v="v" p="p" pweight "pweight" vmax "vmax
\\ " num_below "num_below[v]
\\ " centroid_weight "centroid_weight);
if(vmax= ecc_below[p]+1 anyway,
\\ so the prospective "b" is still correct.
\\
\\ print("vpar_eccentricities_vector_cyclic() "vpar);
my(num_children=vpar_INTERNAL_num_children_vecsmall(vpar),
ecc =vector(#vpar),
ecc_second=vectorsmall(#vpar),
ecc_above =ecc_second,
chain =ecc_second,
t = 0,
v, p);
\\ Childless upwards.
for(i=1,#vpar,
v=i;
while(!num_children[v] && v<=i && (p=vpar[v]),
chain[v]=t; t=v;
my(b=ecc[v]+1); \\ accumulate ecc[v] into ecc[p]
if(b>ecc[p],
ecc_second[p]=ecc[p];
ecc[p]=b;
\\ print(" v="v" to p="p" ecc "ecc[p]" second "ecc_second[p]);
,
ecc_second[p]=max(ecc_second[p],b);
\\ print(" v="v" to p="p" second "ecc_second[p]);
);
num_children[v=p]--));
\\ print(" ecc below "ecc);
\\ print(" second "Vec(ecc_second));
\\ Possible top cycle.
my(c=hammingweight(num_children)); \\ num cyclic vertices
if(c>1,
\\ print(" cycle length "c" at v="v);
\\ print1(" vertices ");for(i=1,c,print1(" "v);v=vpar[v]); print();
\\ print1(" below ");for(i=1,c,print1(" "ecc[v]);v=vpar[v]);print();
\\ delta = (v) ecc[1]+1, ecc[2]+2, ecc[3]+3, ecc[4]+4 (u)
\\ p
\\ ecc[5]+5 replace from u
\\ last is +h, and loop extends then first time to i+h = h+1
\\
\\ print(" after");
my(h=c>>1, \\ half cycle
delta = vectorsmall(h),
u=v);
for(i=1,h,
u=vpar[u]; delta[i] = ecc[u] + i); \\ successive after v
p=1; \\ next position in delta[] to replace
for(i=1,c,
\\ print(" at i="i" v="v" u="u" pos p="p" "Vec(delta)" is ",vecmax(delta) - i + 1);
ecc_above[v] = vecmax(delta) - i + 1;
u=vpar[u]; v=vpar[v];
delta[p] = ecc[u] + i+h;
if(p++>h, p=1));
\\ delta = ecc[-4]+3, ecc[-3]+2, ecc[-2]+1, ecc[-1]+0 (v)
\\ p
\\ ecc[0]-1 replace from v
\\ first distance back is +0 which on adding +i becomes +1
\\
\\ print(" before");
for(i=1,h,
delta[i] = ecc[v] + h-i; v=vpar[v]); \\ successive before v
p=1; \\ next position to replace
for(i=1,c,
\\ print(" at i="i" v="v" p="p" "Vec(delta)" is "vecmax(delta)+i);
ecc_above[v] = max(ecc_above[v], vecmax(delta)+i);
delta[p] = ecc[v] - i;
if(p++>h, p=1);
v=vpar[v]);
for(i=1,c,
ecc[v] = max(ecc[v],ecc_above[v]);
v=vpar[v]);
\\ print(" ecc_above "Vec(ecc_above));
);
\\ Root downwards make ecc out of ecc below and ecc above.
\\ ecc[v] is, as yet, still ecc below.
while(t,
\\ print(" t="t" p="p);
my(v=t, p=vpar[v]);
if(p,
my(b=max(ecc_above[p], if(ecc[p]==ecc[v]+1,ecc_second,ecc)[p])
+ 1);
\\ print(" above b="b" cf ecc "ecc[v]);
ecc_above[v]=b;
ecc[v]=max(ecc[v],b));
t=chain[t]);
ecc;
}
{
addhelp(vpar_eccentricities_vector,
"vec = vpar_eccentricities_vector(vpar)
Return a vector where each vec[v] is the eccentricity of vertex v.
This is vec[v] = vpar_eccentricity_of_vertex(vpar,v), but calculated all
together. vpar must be a single component, not a forest.
The smallest value in vec is radius vecmin(vec)==vpar_radius(vpar).
The biggest value in vec is diameter vecmax(vec)==vpar_diameter(vpar).
vpar can contain a cycle. Each distance is by its shorter way around the
cycle. The current implementation may take time O(c^2) in the cycle
length.");
}
vpar_is_eccentricities_vector(vec) =
{
\\ print("vpar_is_eccentricities_vector() "vec);
if(#vec,
my(diameter=vecmax(vec),
radius =vecmin(vec));
if(radius<0 || diameter>=#vec || radius != (diameter+1)>>1,
return(0));
\\ seen[i] = how many vertices have eccentricity i-1+radius.
\\ seen[1] = how many of radius (the min).
\\ seen[#seen] = how many of diameter (the max).
\\
my(seen=vectorsmall(diameter-radius+1));
for(v=1,#vec, seen[vec[v]-radius+1]++);
\\ print(" seen "Vec(seen));
\\ even diameter 0, unicentral seen[1]==1
\\ odd diameter 1, bicentral seen[1]==1
\\
\\ unicentral bicentral
\\ 6-5-4-3-4-5-6 3-2-2-3
\\ 4-3-2-3-4 5-4-3-3-4-5
\\ 2*radius+1 2*radius num vertices
\\
if(seen[1] > 2 \\ must centres<=2
|| bitand(diameter,1) != seen[1]-1, \\ odd/even central
return(0));
\\ at least 2, so vertices two directions away from centre
for(i=2,#seen, if(seen[i]<2, return(0))));
1;
}
{
addhelp(vpar_is_eccentricities_vector,
"bool = vpar_is_eccentricities_vector(vec)
vec is a vector of integers. Return 1 if it is an eccentricities vector of
a tree, so some vpar tree has vpar_eccentricities_vector(vpar) == vec.
The biggest vec entry is the diameter and smallest is the radius.
They must be 0 <= radius <= diameter < #vec and radius==ceil(diameter/2).
There must be 1 or 2 vec entries ==radius, being the centre(s) of the tree.
Then there must be >=2 entries of each radius+1 through diameter inclusive.
These are branches away from the centre with endpoints attaining the
diameter.");
}
\\ vpar_INTERNAL_ge2_vector() returns a vector of n entries where each a[i]
\\ is the number of ways to partition i labelled things into some number of
\\ labelled buckets with >=2 things in each bucket. A032032
\\
\\ The recurrence is a first bucket of k>=2 things chosen from the n, times
\\ ways to partition the rest. k=n would be all in the first bucket, a
\\ fixed 1 way. a[1]=0 as there is no way to put n=1 things with >=2 each
\\ bucket.
\\
vpar_INTERNAL_ge2_vector(n) =
{
my(a=vector(n));
for(n=2,n,
a[n] = 1 + sum(k=2,n-1, binomial(n,k)*a[n-k]));
a;
}
vpar_eccentricities_vectors_count(n) =
{
if(n<=2, 1,
my(a=vpar_INTERNAL_ge2_vector(n-1));
(n*((a[n-1]<<1) + (n-1)*a[n-2])) >> 1);
}
{
addhelp(vpar_eccentricities_vectors_count,
"count = vpar_eccentricities_vectors_count(n)
Return the number of different vpar_eccentricities_vector() which occur for
trees of n vertices, so how many vectors vpar_is_eccentricities_vector().
This is
E(n) = if(n<=2,1, n*G(n-1) + binomial(n,2)*G(n-2))
= 1, 1, 1, 3, 10, 45, 231, 1428, ...
where
G(n) = if(n==0,1, sum(k=2,n, binomial(n,k)*G(n-k)))
= 1, 0, 1, 1, 7, 21, 141, 743,... (A032032)
G is the number of ways to partition n labelled things into an ordered list
of buckets with >=2 things in each bucket. Its recurrence is choose k for
the first bucket, times ways for the rest. The self-binomial is solved to
egf gG = 1/(2+x-exp(x)) as shown in OEIS A032032.
The eccentricities vectors are then choose centre 1 or 2 vertices and >=2
all subsequent eccentricities. Its egf follows as 1 + x*(1+x/2)*gG.");
}
\\ GP-DEFINE E(n) = if(n<=2,1, n*G(n-1) + binomial(n,2)*G(n-2));
\\ GP-DEFINE G(n) = if(n==0,1, sum(k=2,n, binomial(n,k)*G(n-k)));
\\ GP-Test vector(8,n,n--; E(n)) == [1, 1, 1, 3, 10, 45, 231, 1428]
\\ GP-Test vector(8,n,n--; G(n)) == [1, 0, 1, 1, 7, 21, 141, 743]
\\-----------------------------------------------------------------------------
\\ Diameter, Centre
vpar_diameter(vpar) =
{
\\ This code is roughly in the manner of
\\
\\ S. Mitchell Hedetniemi, E. J. Cockayne and S. T. Hedetniemi, "Linear
\\ Algorithms for Finding the Jordan Centre and Path Centre of a Tree",
\\ Transportation Science, volume 15, number 2, May 1981, pages 98-114.
\\ http://www.jstor.org/stable/25768007
\\
\\ height[v] is the height of the subtree at and below v.
\\ Working upwards from childless vertices, parent
\\ height[p] = 1 + max(height[c] children)
\\ Prospective diameters are between existing height[p] and new child
\\ height[v], giving the children of maximum and second maximum height
\\ (and possibly two child of equal maximum).
\\
\\ The upwards sequence is by decrementing num_children[] in Prufer code
\\ style. If any cyclic vertices then they are left num_children[v]==1.
\\ For a cycle,
\\ c = the cycle length
\\ h = half cycle
\\
\\ Diameters across the cycle are calculated by considering vertex v and
\\ the h many vertices preceding. delta[] vector starts as heights of the
\\ vertices before v, each with +0, +1, +2, etc for how far before v they
\\ are.
\\ delta = [ ..., prev(prev(prev(v)))+2, prev(prev(v))+1, prev(v) ]
\\ Prospective diameter is then vecmax(delta) + height[v] + 1.
\\
\\ To step from v to next(v), the oldest entry in delta[] is discarded,
\\ because it is now the long way around the cycle. It is replaced by
\\ height[v], that being v immediately preceding next(v). p is the
\\ position in delta[] to do this.
\\
\\ The +1, +2, etc distances back in the rest of delta[] are unchanged.
\\ Instead the prospective diameter is +i to allow for next(v) stepping
\\ away from them. New delta[p] has a -i to allow for that. The effect
\\ is offsets ..., +2, +1, 0, -1 in delta[].
\\
\\ When c is even, the second half of the loop repeats some distances
\\ already tried. Eg. c=8 has 1 to 5, then 5 to 1. This does no harm.
\\ Could shorten delta[] at half way around the cycle, but that doesn't
\\ seem worth the trouble.
\\
\\ -------
\\ The use of vecmax(delta) at each cycle vertex means time is O(c^2).
\\ This is not very good. The worst case is a cycle and nothing else.
\\
\\ Time should be c log c by keeping the delta[] vector in a B-tree or
\\ similar so can do the remove, replace, get maximum, in log time each.
\\ Not good enough would be keeping delta[] sorted. setsearch() is log to
\\ find where to insert to stay sorted, but listdelete() and listput() are
\\ block copies of average 1/2*c hence still O(c^2).
\\
\\ Time c log c is shown in
\\
\\ Eunjin Oh and Hee-Kap Ahn, "A Near-Optimal Algorithm for Finding an
\\ Optimal Shortcut of a Tree", DOI 10.4230/LIPIcs.ISAAC.2016.59
\\
\\ They keep diameter distances going clockwise and anti-clockwise around
\\ the cycle, with offset for when stepping around. They keep complete
\\ diameters so their offset is effectively height[v] + i. Here instead
\\ just +i and apply the height[v] term in the max. They keep the
\\ diameters in a tree (but without specifying any particular tree update
\\ algorithm to maintain balance needed for log lookups -- is that right?).
\\
\\ A half-way possibility would be to keep an m which is m >= vecmax(delta),
\\ and if prospective = m + height[v] + i >= diameter so-far then recheck
\\ it with exact m=vecmax(delta). On replacing delta[p] would only need
\\ m = max(m, new value). But if the old delta[p] was causing the m size
\\ then its removal doesn't decrease m, hence needing fresh vecmax to be
\\ sure. Not sure how much this would help. It doesn't help at all in
\\ the cycle-only case, because every replaced delta[p] was the diameter
\\ and so fresh vecmax every time.
\\ print("vpar_diameter() "vpar);
my(num_children=vpar_INTERNAL_num_children_vecsmall(vpar),
height = vectorsmall(#vpar),
diameter = min(0,#vpar-1),
v, p);
for(i=1,#vpar,
\\ print(" at i="i" diameter="diameter" num_children "num_children[i]);
v=i;
while(!num_children[v] && v<=i && (p=vpar[v]),
my(b=height[v]+1,
prospective=height[p]+b);
\\ print(" v="v" prospective "prospective);
diameter=max(diameter,prospective);
height[p]=max(height[p],b);
num_children[v=p]--));
\\ print(" final num_children "Vec(num_children));
\\ print(" heights "Vec(height));
my(c=hammingweight(num_children)); \\ num cyclic vertices
if(c>1,
\\ v here is a vertex in the cycle, being wherever the loop up from
\\ childless stopped
\\ print(" cycle length "c" at v="v);
\\ print1(" vertices ");for(i=1,c,print1(" "v);v=vpar[v]); print();
\\ print1(" heights ");for(i=1,c,print1(" "height[v]);v=vpar[v]);print();
my(h=c>>1, \\ half cycle
delta = vectorsmall(h));
for(i=1,h,
delta[i]=height[v] + h-i; v=vpar[v]);
p=1; \\ next position to replace
for(i=1,c,
\\ print(" at v="v" p="p" offset="offset" d="Vec(delta)" implies "apply(value->value + height[v] + i, Vec(delta)));
diameter=max(diameter, vecmax(delta) + height[v] + i);
delta[p] = height[v] - i;
if(p++>h, p=1);
v=vpar[v]));
diameter;
}
{
addhelp(vpar_diameter,
"diam = vpar_diameter(vpar)
Return the diameter of vpar.
This is the length of the longest path between any two vertices in vpar.
vpar must be a single component, not a forest.
A single vertex #vpar==1 is diameter 0.
The empty tree [] is reckoned diameter -1, so that path-n is diameter n-1.
vpar can contain a cycle. Distances across it are by shortest way around.
The current implementation may take time O(c^2) in the cycle length.");
}
vpar_diameter_and_count(vpar) =
{
my(seq=vpar_INTERNAL_seq_upwards(vpar),
height =vectorsmall(#vpar), \\ height[v] = height below v
height_count=vectorsmall(#vpar,v,1), \\ how many endpoints attain
diameter=min(0,#vpar-1),
count =1);
for(i=1,#seq,
my(v=seq[i], p=vpar[v]);
\\ print(" v="v" p="p" v_height="height[v]" p_height=",if(p,height[p],"(none)"),
\\ " counts "height_count[v]" ",if(p,height_count[p],"(none)"),
\\ " diameter "diameter" count "count);
if(p,
my(v_height =height[v]+1, \\ with new edge up to p
v_height_count=height_count[v],
prospective_diameter = height[p] + v_height); \\ v to best at p
if(prospective_diameter>diameter,
\\ print(" new high");
diameter=prospective_diameter; count=v_height_count*height_count[p]
,
prospective_diameter==diameter,
\\ print(" equal high");
count+=v_height_count*height_count[p]);
if(v_height>height[p],
height[p]=v_height; height_count[p]=v_height_count;
,
v_height==height[p],
height_count[p]+=v_height_count);
\\ print(" prospective_diameter="prospective_diameter
\\ " giving diameter "diameter" count "count
\\ " p new height "height[p]" count "height_count[p]);
));
[diameter,count];
}
{
addhelp(vpar_diameter_and_count,
"[diam,count] = vpar_diameter_and_count(vpar)
Return the diameter of vpar and count of how many paths attain that diameter.
A path u--v is counted just once, not also its reverse v--u.
For example vpar_make_path(n) has diameter n-1 count 1.
vpar must be a single tree (not a forest).
A single vertex #vpar==1 is diameter 0 count 1 path.
The empty tree [] is reckoned diameter -1 count 1 path.
Always have count>=1 since the empty path is reckoned as a diameter in the
empty tree.");
}
vpar_diameter_num_endpoints(vpar) =
{
my(seq=vpar_INTERNAL_seq_upwards(vpar),
height =vectorsmall(#vpar), \\ height[v] = height below v
height_endpoints =vectorsmall(#vpar,v,1), \\ num endpoints attain height
above_diameter=height,
above_distance=height,
diameter =if(#vpar,0,-1),
diameter_num_endpoints=#vpar);
for(i=1,#seq,
my(v=seq[i], p=vpar[v]);
if(p,
my(new_height = height[v] + 1, \\ p down through v
new_diameter = new_height + height[p]); \\ v to best already at p
if(new_diameter>=diameter,
if(new_diameter>diameter,
diameter = new_diameter;
diameter_num_endpoints = height_endpoints[v] + height_endpoints[p];
,
new_diameter==diameter,
diameter_num_endpoints += if(above_diameter[v]==diameter,
height_endpoints[p],height_endpoints[v]));
above_distance[p]=0;
above_diameter[p]=diameter;
,
above_diameter[v]==diameter,
above_diameter[p]=diameter;
above_distance[p]=above_distance[v]+1);
if(new_height>height[p],
height[p]=new_height;
height_endpoints[p]=height_endpoints[v];
,
new_height==height[p],
height_endpoints[p] += height_endpoints[v])));
diameter_num_endpoints;
}
{
addhelp(vpar_diameter_num_endpoints,
"num = vpar_diameter_num_endpoints(vpar)
Return the number of vertices which are endpoints of some diameter path.
This is number of vertices with eccentricity (vpar_eccentricity_of_vertex())
equal to the diameter (vpar_diameter()). These endpoints are also called
the \"periphery\".
vpar must be a single tree, not a forest.
Diameter ends are always leaves (vpar_is_leaf(), degree <= 1) so the
return is num <= vpar_num_leaves(vpar).
The empty tree [] has no vertices at all and for it the return is 0.");
}
vpar_diameter_num_vertices(vpar) =
{
my(seq=vpar_INTERNAL_seq_upwards(vpar),
height =vectorsmall(#vpar), \\ height[v] = height below v
height_vertices =vectorsmall(#vpar,v,1), \\ num vertices to all height
above_diameter=height,
above_distance=height,
diameter=0,
diameter_num_vertices=#vpar);
for(i=1,#seq,
my(v=seq[i], p=vpar[v]);
if(p,
\\ print("v="v" height "height[v]" vertices "height_vertices[v]
\\ " p="p" height=",if(p,height[p],"(none)"),
\\ " vertices ",if(p,height_vertices[p],"(none)"),
\\ " diameter "diameter" vertices "diameter_num_vertices);
my(new_height = height[v] + 1, \\ p down through v
new_diameter = new_height + height[p]); \\ v to best already at p
if(new_diameter>=diameter,
if(new_diameter>diameter,
diameter = new_diameter;
diameter_num_vertices = height_vertices[v] + height_vertices[p];
\\ print(" new high diameter, to vertices "diameter_num_vertices);
,
new_diameter==diameter,
diameter_num_vertices
+= if(above_diameter[v]==diameter,
height_vertices[p] + above_distance[v],
height_vertices[v] + above_distance[p]);
\\ print(" equal high diameter, to vertices "diameter_num_vertices);
);
above_distance[p]=0;
above_diameter[p]=diameter;
,
above_diameter[v]==diameter,
above_diameter[p]=diameter;
above_distance[p]=above_distance[v]+1;
\\ print(" parent further above");
);
if(new_height>height[p],
height[p]=new_height; height_vertices[p]=height_vertices[v]+1,
new_height==height[p],
height_vertices[p] += height_vertices[v])));
diameter_num_vertices;
}
{
addhelp(vpar_diameter_num_vertices,
"num = vpar_diameter_num_vertices(vpar)
Return the number of vertices which are on some diameter path.
There can be multiple diameter paths in a tree. The return counts vertices
which are on any such path. Each vertex is counted just once irrespective
how many paths it appears on.
vpar must be a single tree, not a forest.
The empty tree has no vertices at all and the return is 0.");
}
vpar_radius(vpar) = if(#vpar, vecmin(vpar_eccentricities_vector(vpar)), 0);
{
addhelp(vpar_radius,
"radius = vpar_radius(vpar)
Return the radius of vpar.
This is the smallest eccentricity occurring in vpar.
vpar must be a single component, not a forest.
For a tree, radius = ceil(diameter / 2).
Empty vpar==[] is reckoned radius 0 so that this relation to diameter holds
even for the empty tree (diameter -1 so ceil(-1/2)=0).
That diameter behaviour is for consistency on a path-n. Here that means
vpar_radius(vpar_make_path(n)) == ceil((n-1)/2) for all n.
vpar can contain cycles. Eccentricities, and consequently radius, are by
each shorter way across a cycle.");
}
vpar_centres(vpar) =
{
if(#vpar,
my(vec=vpar_eccentricities_vector(vpar),
radius=vecmin(vec));
Vec(select(e->e==radius,vec,1));
,
[]);
}
{
addhelp(vpar_centres,
"vec = vpar_centres(vpar)
Return a vector of the vertices which are centres of vpar, in ascending
order (so a Set()). Centres are the vertices of minimum eccentricity
(ie. vpar_radius()).
vpar must be a single component, not a forest.
Empty vpar==[] has no vertices and the return is empty [].
For a tree, there are 1 or 2 adjacent centres. They are the middle vertices
of diameter paths. When diameter odd there is 1 middle. When diameter even
there are 2 middles. The diameter can be attained by multiple paths but all
of them pass through both the 1 or 2 centres.
vpar can contain cycles. A cyclic vpar might have more than 2 centres, they
might not be adjacent, and various possible diameter paths may pass through
just some of them.");
}
vpar_centres_of_forest(vpar) =
{
\\ Vertices are processed upwards in the forest, establishing
\\ height[v] = height below v
\\ height_v[v] = vertex number below v attaining height[v]
\\ diameter[v] = diameter of subtree at and below v
\\ diameter_v[v] = vertex number below v which is the end of a diameter
\\ path, and the deeper of the two ends of that path, so can go up from
\\ diameter_v[v] to reach the centre or centres
\\ print("vpar_centres_of_forest() "vpar);
my(ret=vector(vpar_num_roots(vpar)),
upto=0);
my(seq=vpar_INTERNAL_seq_upwards(vpar),
height =vectorsmall(#vpar),
height_v =vpar_INTERNAL_identity_perm(#vpar),
diameter =height,
diameter_v =height_v);
for(i=1,#seq,
my(v=seq[i], p=vpar[v]);
\\ print("at v="v" p="p);
if(p,
\\ if v diameter bigger than p then copy v to p
if(diameter[v]>diameter[p],
diameter[p]=diameter[v];
diameter_v[p]=diameter_v[v]);
\\ Possible diameter down from p and down from v (with height[p] not
\\ yet including height[v]). diameter_v gets whichever is the
\\ deeper height.
my(prospective_diameter=height[p] + height[v] + 1);
if(prospective_diameter>diameter[p],
diameter[p]=prospective_diameter;
diameter_v[p]=height_v[if(height[p]>height[v],p,v)];
\\ print(" new diameter "diameter[p]" ["diameter_v[p]"] for p="p" from heights "height[p]"["height_v[p]"] "height[v]"["height_v[v]"]")
);
\\ if height v + 1 bigger than height p then set new high in p
if(height[v]+1>height[p],
\\ print(" new height "height[v]+1" vertex "height_v[v]" for p="p);
height[p] = height[v]+1;
height_v[p] = height_v[v]);
,
\\ p==0 so v is a root. Step upwards from diameter_v[v] to the
\\ centre or centres of this component tree.
p=diameter_v[v];
\\ print(" at root v="v", diameter="diameter[v]" diameter_v="p);
forstep(i=2,diameter[v],2, p=vpar[p]);
ret[upto++] = if(bitand(diameter[v],1),[p,vpar[p]],[p]);
\\ print(" set ret "ret[upto]);
));
\\ print("final ret "ret);
ret;
}
{
addhelp(vpar_centres_of_forest,
"vec = vpar_centres_of_forest(vpar)
Return a vector of vectors [ [c1,c2], [c], [c], [c1,c2] ] which are the
centres of the component trees of vpar.
The empty tree has no vertices so the return is empty [].
Otherwise there are 1 or 2 centres in each component tree. The order of the
components in the return is unspecified, as is the order of the centres when
a component has 2 centres.
The centres of a component tree are the middle vertices of its diameter
paths. When diameter odd there is 1 centre. When diameter even there are 2
centres. The diameter can be attained by multiple paths but the centre or
centres are the same in all diameter paths.
Equivalently, the centre is those vertices of minimum eccentricity in the
component tree. There is either 1 centre of unique minimum eccentricity, or
2 of equal minimum.");
}
\\-----------------------------------------------------------------------------
\\ Wiener Index
vpar_Wiener_index(vpar,terminal=0) =
{
\\ The method is number of crossings of an edge, summed over all edges.
\\
\\ num_below[v] is the number of path endpoint vertices at and below v.
\\ For terminal=0, all vertices below and including v, so initial 1.
\\ For terminal=1,2, only the terminal vertices meaning childless,
\\ so initial value 1 if childless (like vpar_is_childless_vector()).
\\
\\ Each edge child to parent v--p is crossed by paths for num_below
\\ vertices below going to total_vertices - num_below above.
\\
\\ For terminal=0, total_vertices is #vpar all vertices.
\\ For terminal=1,2, total_vertices excludes the childful vertices, as
\\ are counted by hammingweight.
\\ For terminal=1 the root is included if degree 1, which means at
\\ vpar[r]==0 and childful with one child num_children[r]==1.
\\
\\ MAYBE: Could have an option for Wiener index total distances between
\\ non-terminals, but that should be merely full index of tree with leaves
\\ pruned.
\\
my(num_children = vpar_INTERNAL_num_children_vecsmall(vpar),
total_vertices = #vpar - if(terminal, hammingweight(num_children))
+ if(terminal==1, sum(v=1,#vpar,num_children[v]==1&&!vpar[v])),
num_below = vectorsmall(#vpar,v, !(terminal && num_children[v])),
seq = vpar_INTERNAL_seq_upwards(vpar,num_children),
W=0);
for(i=1,#seq,
my(v=seq[i], p=vpar[v]);
if(p,
W += num_below[v] * (total_vertices-num_below[v]); \\ v--p crossings
num_below[p] += num_below[v]));
W;
}
{
addhelp(vpar_Wiener_index,
"W = vpar_Wiener_index(vpar,terminal=0)
Return the Wiener index of vpar.
This is sum of distances between all pairs of vertices u,v.
Distances are counted in one direction u-->v, not also reverse v-->u.
Optional parameter \"terminal\" can be 1 to calculate the terminal Wiener
index. This is sum of distances between terminal vertices, where terminal
means a leaf vertex degree=1, per vpar_leaves().
(See examples/terminal-Wiener-maximum.gp for its maximum values.)
terminal=2 is a variation on terminal Wiener where only childless vertices
are considered terminal. The effect is to exclude a degree=1 root.
In all cases vpar must be a single tree, not a forest.
See also vpar_Laplacian_poly() which has Wiener index among its
coefficients.");
}
\\------------------------------------------------------------------------------
\\ Protection (Distance Down to Childless)
vpar_protnum(vpar) =
{
if(#vpar,
my(depths=vpar_depths_vector(vpar));
for(v=1,#vpar, if(vpar[v], depths[vpar[v]]=#vpar)); \\ zap when children
vecmin(depths);
,
0);
}
{
addhelp(vpar_protnum,
"num = vpar_protnum(vpar)
Return the protection number of vpar.
This is the shortest distance down from a root to a childless vertex.
Or thinking upwards, num is how far can go up from a vertex anywhere and
still be within the tree (or forest).
See vpar_protnum_of_vertex() for how far down from a given vertex to a
childless. Here num is the minimum of that among the roots.");
}
vpar_protnum_of_vertex(vpar,v) =
{
my(depths=vpar_depths_vector(vpar));
vecmin(vecextract(depths, vpar_subtree_childless_vertices(vpar,v)))
- depths[v];
}
{
addhelp(vpar_protnum_of_vertex,
"num = vpar_protnum_of_vertex(vpar,v)
Return the protection number of vertex v in vpar.
This is the shortest distance down from v to a childless vertex.
num=0 when v is childless; num=1 when v has a childless child; or num=2 when
v has no childless children but does have a childless grandchild; and so on.
See vpar_protnum() for protection number of the whole tree or forest.");
}
\\ Emeric Deutsch, "Rooted Tree Statistics from Matula Numbers", calls this
\\ shortest down to childless the "exit distance" of vertex v.
vpar_protnums_vector(vpar) =
{
my(num_children=vpar_INTERNAL_num_children_vecsmall(vpar),
seq=vpar_INTERNAL_seq_upwards(vpar,num_children),
ret=vector(#vpar,v, if(num_children[v], #vpar, 0)));
for(i=1,#seq,
my(v=seq[i], p=vpar[v]);
if(p, ret[p] = min(ret[p], ret[v]+1)));
ret;
}
{
addhelp(vpar_protnums_vector,
"vec = vpar_protnums_vector(vpar)
Return a vector of the protection numbers of the vertices of vpar.
This is vec[v] = vpar_protnum_of_vertex(vpar,v) but calculated all
together.
See vpar_is_protnums_vector() to characterize the possible vectors,
and see vpar_protnums_vectors_count() for how many such vectors.");
}
vpar_is_protnums_vector(vec,forest=0) =
{
if(#vec,
my(m=vecmax(vec));
if(m<0 || m>=#vec \\ out of range
|| !(forest>0 || m || #vec<=1), \\ tree cannot be [0,0,...] >=2
return(0));
my(count=vectorsmall(m+1));
for(v=1,#vec,
vec[v]>=0 || return(0);
count[vec[v]+1]++);
for(i=1,m, \\ counts must be increasing (non-decreasing)
count[i] >= count[i+1] || return(0));
forest>=0 \\ tree or forest is ok
|| vec[1]==m \\ root=1 as maximum is ok
|| (vec[1]!=0 \\ root=1 having smaller count above is ok
&& count[vec[1]+1] != count[vec[1]+2]);
,
1); \\ empty vec==[] is from empty tree
}
{
addhelp(vpar_is_protnums_vector,
"bool = vpar_is_protnums_vector(vec,forest=0)
vec is a vector of integers. Return 1 if it is a protection numbers vector
of a tree, so vec == vpar_protnums_vector(vpar) for some vpar tree.
Optional parameter \"forest\" can be 1 to test for vec from a forest, or can
be -1 to test from a tree of root=1.
For a forest, the test is the same as vpar_is_subtree_heights_vector() of a
forest. Vertices with prot are children of prot+1, so number of prot must
be >= number of prot+1. Such a vec can be made into a forest with rows of
increasing protection number.
For trees, similarly but not all prot=0 when #vec>=2, since the root has at
least one child so is prot>=1. The tree root can be one of the biggest
prot, and if multiple equal biggest then the others children of that.
For root=1 trees, the number of its prot=vec[1] must be strictly > number of
prot+1 (rather than just >= elsewhere), because the root is not the child of
one of those prot+1.");
}
vpar_protnums_vectors_count(n,forest=0) =
{
\\ The root=1 case is multinomials array up to n-1, which is done by
\\ decrement n at the start. The binomial(n,k) is then equivalent to
\\ (n-1,k-1). a[] array doesn't include an a[0]=1 term wanted for the
\\ binomial so an explicit +1 instead.
if(forest<0, n--);
if(n<=0, 1,
my(a=vpar_INTERNAL_exponential_transform(vpar_INTERNAL_A182926_vector(n)));
if(forest<0, sum(k=1,n,binomial(n,k)*a[k]) + 1 - a[n],
a[n] - (n>=2 && !forest)));
}
{
addhelp(vpar_protnums_vectors_count,
"count = vpar_protnums_vectors_count(n,forest=0)
Return the number of different vpar_protnums_vector(vpar) which
occur for vpar trees of n vertices.
Optional parameter \"forest\" can be 1 to count for forests, or can be -1 to
count for root=1 trees.
Counts follow from the descriptions in vpar_is_protnums_vector().
Forests are F(n) = vpar_subtree_heights_vectors_count() of forests (A005651).
Trees>=2 disallow the all-0s vector so T(n) = F(n) - 1 = 1,1,2,9,46,245,...
Root=1 trees are the root in the first set of its size, choose k-1 other
vertices in that set, and the rest a forest n-k, hence binomial transform of
F(n) (which gives A320566). Except that would allow root in the prot=0,
where it must be prot>=1. Those cases are all forests F(n-1) and root=1 put
in the last term, so subtract that to
B(n) = bintrans(F(n)) - F(n-1)
= 1,1,3,13,63,371,... (A320566 - A005651).");
}
\\ not in OEIS: 1, 2, 9, 46, 245, 1601, 11480 \\ trees
\\ not in OEIS: 3, 13, 63, 371, 2433, 18446 \\ root=1 trees A320566-A005651
\\ A320566
\\ A005651
vpar_is_protected_vertex(vpar,v,k) =
{
\\ Search upwards from childless vertices looking for a distance to v
\\ which is < k. Such a distance means not k protected.
\\
\\ seen[u] = 1 + smallest distance down to a childless so far.
\\ So a childless u already seen has seen[u]=1, its parent seen[p]=2, etc.
\\ If reach seen[u]<=j then have already been up through u and went higher
\\ above u than would do this bigger j, so can going up.
\\
\\ If u reaches the top of its component tree then childless[i] is not
\\ under v. Zero-filling the chain up from childless[i] indicates there's
\\ no point looking upwards there.
\\ print("vpar_is_protected_vertex() "vpar" v="v" k="k);
my(childless=vpar_childless_vertices(vpar),
big=#vpar+1,
seen=vectorsmall(#vpar,v, big));
for(i=1,#childless,
\\ print(" i="i);
my(u=childless[i]);
for(j=1,k,
\\ print(" j="j" u="u" seen "seen[u]);
if(seen[u]<=j, break);
seen[u]=j;
if(u==v, return(0)); \\ found childless to v distance = k.
Ie. vpar_protnum_of_vertex(vpar,v) >= k.
0-protected is all vertices.
1-protected is childful vertices (vpar_is_childful()).
2-protected is childful and all those children are childful too
(ie. children are 1-protected).");
}
\\-----------------------------------------------------------------------------
\\ Matrices
\\ MAYBE: parameter dir=0 undirected (as presently),
\\ dir=1 m[u,v] = u parent of v,
\\ dir=2 m[u,v] = v parent of u
\\ so giving adjacency of the tree as a directed graph.
\\
vpar_adjacency_matrix(vpar) = \
matrix(#vpar,#vpar,u,v, (vpar[u]==v)||(vpar[v]==u));
{
addhelp(vpar_adjacency_matrix,
"m = vpar_adjacency_matrix(vpar)
Return the adjacency matrix of vpar.
This is m[u,v] = vpar_is_neighbour(vpar,u,v), so 1 where an edge u--v and 0
everywhere else.
The matrix does not encode edge directions so effectively treats vpar as
labelled but unrooted. The number of distinct matrices which occur is
therefore vpar_count_unrooted().
Properties of the matrix on trees and forests include
trace(m) == num self-loops
matrank(m) == 2*vpar_matchnum(vpar)
matdet(m) == vpar_has_perfect_matching(vpar) * (-1)^(#vpar\\2)
Matrix m^k has entries (m^k)[u,v] = num paths of length k between u and v.
These paths include detours and backtracking, ie. all sequences vertices
leading from u to v. Case m^2 is length 2, and u to u by 2 steps means
count edges at u, hence
vector(#vpar,u, (m^2)[u,u]) == vpar_degrees_vector(vpar)
trace(m^2) == 2*vpar_num_edges(vpar)
vpar can contain cycles. A self-loop is adjacent to itself so m[v,v]=1.
A 2-cycle has 2 edges between its u--v, but still have just m[u,v]=1. This
has the effect that powers m^k count treat a 2-cycle the same as a path-2
with just 1 way between.
See vpar_adjacency_poly() for characteristic polynomial, or
vpar_from_adjacency_matrix() to recover vpar. See
vpar_skew_adjacency_matrix() for signed matrix entries.");
}
\\ matrank() from
\\
\\ J. H. Bevis, G. S. Domke and V. A. Miller, "Ranks of Trees and Grid
\\ Graphs", J. of Combinatorial Math. and Combinatorial Computing 18 (1995),
\\ pages 109-119.
\\
\\ referred to by
\\
\\ Gerd H. Frickey, Stephen T. Hedetniemi, David P. Jacobsz and Vilmar
\\ Trevisan, "Reducing the Adjacency Matrix of a Tree", Electronic
\\ Journal of Linear Algebra, volume 1, October 1996, pages 34-43.
\\ http://gauss.technion.ac.il/iic/ela
\\
\\ Frickey et al give the matdet(), and note the stronger condition that any
\\ square sub-matrix has determinant 0,1,-1 too, so a "totally unimodular"
\\ matrix.
\\ vpar_INTERNAL_adjacency_poly() is plain adjacency for s=-1 or skew
\\ adjacency for s=1.
\\
\\ vpar_matchpoly() is calculated in plain variable x and only when finished
\\ is spread to x^2. This is so vpar_matchpoly() calculates without
\\ intermediate polys spread out doubling their size.
\\
\\ An incompatible change in gp 2.9 broke Vecrev() padding.
\\ In 2.7.x Vecrev([1],3)==[0,0,1], but in 2.9.x Vecrev([1],3)==[1,0,0].
\\ The append form of 2.9 could be used here, but want 2.7 to work too, so
\\ separate Vecrev for reversal then Vec for padding.
\\
\\ Some polrecip() for reversal is almost but not quite possible. subst()
\\ gives t_INT for constant-only when #vpar<=1, but polrecip() demands
\\ t_POL.
\\
vpar_INTERNAL_adjacency_poly(vpar,s,var) =
{
Pol(Vec(Vecrev( subst(vpar_matchpoly(vpar), 'x, s*'x^2)),
#vpar+1),
var);
}
vpar_adjacency_poly(vpar,var='x) = vpar_INTERNAL_adjacency_poly(vpar,-1,var);
{
addhelp(vpar_adjacency_poly,
"poly = vpar_adjacency_poly(vpar,var='x)
Return the characteristic polynomial of vpar_adjacency_matrix(vpar). This
is charpoly() of that matrix, but is calculated via the match polynomial.
Optional parameter \"var\" can be the polynomial variable to use (a symbol),
or default 'x.
vpar can be a forest, in which case the effect is product adjacency
polynomials of its component trees.
The match polynomial is related here by spread x^2, reverse terms, and
alternating signs,
poly == x^#vpar * subst(vpar_matchpoly(vpar), 'x, -1/x^2)
This is as per vpar_skew_adjacency_poly() but terms alternating signs. (See
there for some properties, in particular term +/- m*x^j as count m matchings
with j many vertices not matched.)
The adjacency matrix is real and symmetric so the roots of the poly here are
real.");
}
\\ cf
\\ Abbe Mowshowitz, "The Characteristic Polynomial of a Graph", Journal of
\\ Combinatorial Theory, series B, volume 12, 1972, pages 177-193.
\\ http://www.sciencedirect.com/science/article/pii/0095895672900238
\\ Section 3 on how adjacency charpoly coeffs are number of matchings.
\\ Theorem 5 coespectral trees have line graphs same num vertices and edges.
\\ h_r(Tree) = how many matchings of r pairs
\\ Lemma 1 h_k(T+uv) = h_k(T) + h_{k-1}(T-v) new vertex u matchings.
\\ Appendix table of trees and their charpoly.
\\ Maybe a down parameter for edges reckoned downwards instead of up, though
\\ that would be simply a negation of the result.
\\
vpar_skew_adjacency_matrix(vpar) = \
matrix(#vpar,#vpar,i,j, (vpar[i]==j) - (vpar[j]==i));
{
addhelp(vpar_skew_adjacency_matrix,
"m = vpar_skew_adjacency_matrix(vpar)
Return the skew adjacency matrix of vpar.
This matrix has m[c,p] = 1 and m[p,c] = -1 for each edge child c to parent p.
All other entries are 0.
This matrix has non-zero entries in the same places as vpar_adjacency_matrix()
but here signed +/-1 according to edge direction.
Properties of the matrix of a tree or forest include
trace(m) == 0
matrank(m) == 2*vpar_matchnum(vpar)
matdet(m) == vpar_has_perfect_matching(vpar)
vpar can contain cycles of length >= 3 and they give entries m[c,p]=1 and
m[p,c]=-1 the same as for other edges.
The behaviour on self-loops or 2-cycles is unspecified.
See vpar_skew_adjacency_poly() for characteristic polynomial (and relation
to matching), and vpar_from_skew_adjacency_matrix() to recover vpar. See
vpar_adjacency_matrix() for unsigned matrix entries.");
}
vpar_skew_adjacency_poly(vpar,var='x) = \
vpar_INTERNAL_adjacency_poly(vpar,1,var);
{
addhelp(vpar_skew_adjacency_poly,
"poly = vpar_skew_adjacency_poly(vpar,var='x)
Return the characteristic polynomial of vpar_skew_adjacency_matrix(vpar).
This is charpoly() of that matrix, but calculated more efficiently.
Optional parameter \"var\" can be the polynomial variable to use (a symbol),
or default 'x.
vpar can be a forest, in which case the effect is product skew adjacency
polynomials of its component trees.
Each term c*x^n is count c of how many matchings leave n many vertices
unmatched (per Mowshowitz). So match polynomial spread x^2 and reverse
terms.
poly == subst(vpar_matchpoly(vpar), 'x, 'x^-2) * x^#vpar
Specific terms from the match polynomial become
polcoeff(poly,#vpar) == 1 \\\\ all unmatched
polcoeff(poly,#vpar-1) == 0 \\\\ all #vpar-odd terms
polcoeff(poly,#vpar-2) == vpar_num_edges(vpar) \\\\ one pair
polcoeff(poly,0) == vpar_has_perfect_matching(vpar) \\\\ 0 or 1
valuation(poly,'x) == #vpar - 2*vpar_matchnum(vpar)
subst(poly,'x,1) == vpar_matchings_count(vpar)");
}
\\ Abbe Mowshowitz, "The Characteristic Polynomial of a Graph", Journal of
\\ Combinatorial Theory, series B, volume 12, 1972, pages 177-193.
\\ http://www.sciencedirect.com/science/article/pii/0095895672900238
vpar_Laplacian_matrix(vpar) =
{
my(m = -vpar_adjacency_matrix(vpar), \\ -1 where edge
p);
for(v=1,#vpar,
if((p=vpar[v]),
m[v,v]++; \\ degrees of v and p on diagonal
m[p,p]++));
m;
}
{
addhelp(vpar_Laplacian_matrix,
"m = vpar_Laplacian_matrix(vpar)
Return the Laplacian matrix of vpar. This matrix is
m[v,v] = degree of v (on diagonal)
m[u,v] = -1 if edge u,v
m[u,v] = 0 if no edge u,v
This is equivalent to
matdiagonal(vpar_degrees_vector(vpar)) - vpar_adjacency_matrix(vpar)
Edge directions are ignored so vpar is treated as labelled but unrooted.
The number of distinct Laplacian matrices which occur is therefore
vpar_count_unrooted().
Properties of the Laplacian matrix include
matrank(m) == vpar_num_edges(vpar)
matdet(m[^v,^v]) == vpar_is_tree(vpar)
for v = any vertex 1 to #vpar (in a non-empty vpar)
Per the matrix tree theorem, the number of spanning trees of a graph is
matdet(R) where R is the Laplacian matrix with one vertex row and column
deleted. A tree has 1 spanning tree and a forest has 0 spanning trees,
hence matdet(R) = vpar_is_tree(vpar).
The signless Laplacian matrix is m[u,v]=1 (rather than -1) where an edge.
That is abs(m).
See vpar_Laplacian_poly() for characteristic polynomial. It is the same for
signed or signless Laplacian. In general that is so of a graph if and only
if bipartite (Brouwer and Haemers \"Spectra of Graphs\" proposition 1.3.10).
Trees and forests are always bipartite.)");
}
\\ Brouwer and Haemers "Spectra of Graphs" page 8 proposition 1.3.10 in
\\ signed/signless charpoly same iff bipartite. Other reference?
\\ Bipartite means exists diagonal matrix with entries +/-1 where
\\ signed = D * signless * D^-1 and hence same spectrum.
vpar_Laplacian_poly(vpar,var='x) =
{
\\ any[v] = ways v present,
\\ or v absent and choose any of its degree many edges.
\\ without[v] = ways v absent and no choice of edge.
\\
\\ When accumulating v under parent p, any*any except cannot have their
\\ mutual edge selected by both, which is without*without. without[p] is
\\ simply product of children any[v].
\\ print("vpar_Laplacian_poly() "vpar" var "var" signless "signless);
my(any=vpar_num_children_vector(vpar), \\ start as num_children
seq=vpar_INTERNAL_seq_upwards(vpar,any),
without = vector(#vpar,v,1), \\ without[v] = 1
ret = var/var); \\ 1 as poly
\\ ret = if(unsigned,1,-1)); \\ initially as sign
\\ any[v] = 'x - degree[v],
\\ using degree = num children + (1 if parent!=0)
for(v=1,#vpar,
any[v] = var - (sign(vpar[v]) + any[v]));
for(i=1,#seq,
my(v=seq[i],
p=vpar[v]);
if(p,
any[p] = any[p]*any[v] - without[p]*without[v];
without[p] *= any[v];
,
ret *= any[v]); \\ at root
any[v] = \\ kill intermediate polys
without[v] = 0);
ret;
}
{
addhelp(vpar_Laplacian_poly,
"poly = vpar_Laplacian_poly(vpar,var='x)
Return the characteristic polynomial of vpar_Laplacian_matrix(vpar).
The signed and signless vpar_Laplacian_matrix() have the same charpoly.
Optional parameter \"var\" can be the polynomial variable to use (a symbol),
or default 'x.
vpar can be a forest, in which case the effect is product Laplacian
polynomials of its component trees.
Properties of the poly include
polcoeff(poly,#vpar) == 1 \\\\ high term 1*x^#vpar
polcoeff(poly,#vpar-1) == -2*vpar_num_edges(vpar) \\\\ second
valuation(poly,'x) == vpar_num_roots(vpar) \\\\ low zeros
abs(polcoeff(poly,valuation(poly,'x))) == vpar_num_rootings(vpar)
coeffs alternating signs, highest +
all roots real (since Laplacian matrix real symmetric)
2 is a root when vpar_has_perfect_matching()
and for a tree,
polcoeff(poly,2) == (-1)^#vpar * vpar_Wiener_index(vpar) \\\\ x^2 term
Term c*x^n is abs(c) = num rooted spanning forests of n components.
vpar is taken as an unrooted forest and some edges are deleted to give n
components and the result rooted in all ways (vpar_num_rootings()).
Equivalently, choose #vpar-n vertices (the non-roots) and one adjacent edge
at each, with an edge chosen at most once.
Lowest non-zero term is no edges deleted, just vpar_num_rootings() of the
existing. For a tree, the x^2 term is second lowest. It is 1 edge deleted
and all combinations of 1 vertex each side of it, so crossings of that edge
for the Wiener index. The x^2 coeff is also sum(1/r) where r are the
polynomial roots, excluding r=0 (one tree root means valuation 1). Those
reciprocals are reverse poly terms, then second highest in the reverse poly.
High term n=#vpar components means spanning forest of all singletons and it
has just 1 rooting. Second highest term n=#vpar-1 is 1 edge and rest
singletons. Its count is 2*num_edges since each edge can be the one
remaining and can be rooted at either end.");
}
vpar_reachability_matrix(vpar) = \
apply(d->d>0, vpar_path_lengths_matrix(vpar));
{
addhelp(vpar_reachability_matrix,
"m = vpar_reachability_matrix(vpar)
Return the reachability matrix of vpar.
It has m[u,v]=1 if there is a non-empty path between u and v.
Diagonal entries are m[u,u]=0 since path u-u would be empty.
Reachability is 1 everywhere vpar_path_lengths_matrix(vpar) > 0.
For a single tree, everything is reachable so matrix 1 everywhere except 0
on the diagonal.
For a forest, there are 0s where u,v are in different components (cf
vpar_compnum_vector()).
The number of different reachability matrices which occur is the number of
different ways to partition vertices 1..n into components, which is the Bell
numbers 1,1,2,5,15,52,203,... (A000110).");
}
vpar_incidence_matrix(vpar) =
{
\\ child_to_edge[v] is the edge number 1..num_edges of an edge v -> vpar[v].
\\ If v is a root then it has no parent and child_to_edge[v]=0.
my(child_to_edge=vectorsmall(#vpar),
upto=0);
for(v=1,#vpar, if(vpar[v], child_to_edge[v]=upto++));
my(m=matrix(#vpar,upto));
for(v=1,#vpar,
if(vpar[v],
m[v, child_to_edge[v]] = 1;
m[vpar[v],child_to_edge[v]] = -1));
m;
}
{
addhelp(vpar_incidence_matrix,
"m = vpar_incidence_matrix(vpar)
Return the signed incidence matrix of vertices and edges in vpar.
Each entry is
m[v,e] = 1 if vertex v has edge e directed away from v,
m[v,e] = -1 if vertex v has edge e directed towards v
m[v,e] = 0 if v is not at either end of edge e
Each column is an edge, numbered 1..vpar_num_edges(vpar) in order of their
child vertex number of edge child -> parent. Each column has an entries 1
and -1 at the two vertices either end of the edge, and 0s otherwise.
Product transpose is m*m~ == vpar_Laplacian_matrix(vpar). That follows
since each row of m is a vertex and +/-1 for its edges. The diagonal of
m*m~ is row v dot product row v, so sum of 1*1 or -1*-1 each edge incident
at v, hence degree of v. Other entries of m*m~ are row u dot product row v
which is 1 * -1 at their common edge so -1 if an edge u<->v.
See vpar_Laplacian_matrix() on the matrix tree theorem deleting one row
there. Here one row deleted r = m[^row,] gives product transpose with one
row and column deleted so matdet(r*r~) = number of spanning trees.");
}
\\ cf Brouwer and Haemers "Spectra of Graphs" calls this directed incidence
\\ matrix.
\\-----------------------------------------------------------------------------
\\ Matrices Restore Vpar
vpar_from_adjacency_matrix(m) =
{
\\ vpar[p+1..n] are decided.
\\ The loops are over parent p and child v, with p <= v.
\\ p=v is a self-loop.
\\ Edges are in m both ways so it's enough to look at the p=3 and they are restored
the same as a tree or forest. m cannot be from a vpar containing self-loops
or cycles of length 2, since the matrix contents for these is as yet
unspecified.
A skew adjacency matrix can specify any simple directed graph, not just
trees and unicyclics, but the behaviour on anything else is unspecified.");
}
\\-----------------------------------------------------------------------------
\\ Independent Sets, Independence Number, Independence Polynomial
\\ A vertex cover is a set of vertices where every edge is incident to one
\\ or more in the cover. Vertex covers are set complements of independent
\\ sets. So counts of covers are the same as the independent set counts
\\ here, and cover sets are set complements of vpar_indsets() etc.
\\
vpar_is_indset(vpar,set) =
{
\\ parent and child both in set is not independent
for(i=1,#set, if(setsearch(set,vpar[set[i]]), return(0)));
1;
}
{
addhelp(vpar_is_indset,
"bool = vpar_is_indset(vpar,set)
set is a Set() of vertex numbers.
Return 1 if set is an independent set in vpar, or return 0 if not.
vpar can contain cycles.
Independent set means each vertex in the set has no neighbour in the set.
For an directed graph such as vpar, this is equivalent to asking that each
vertex in the set must not have its parent in the set. Any adjacent pair
will be parent and child one way around or the other.");
}
vpar_is_indset_flags(vpar,flags) =
{
for(v=1,#flags, if(flags[v] && vpar[v] && flags[vpar[v]], return(0)));
1;
}
{
addhelp(vpar_is_indset_flags,
"bool = vpar_is_indset_flags(vpar,flags)
flags is a vector length #vpar of 0,1 booleans giving a set of vertices.
Return 1 if this is an independent set in vpar, or return 0 if not.
vpar can contain cycles.
Independent set means each vertex in the set has no neighbour in the set
also. So each vertex in the set must not have its parent in the set.");
}
vpar_indsets(vpar) =
{
my(seq=vpar_INTERNAL_seq_upwards(vpar),
with =vector(#vpar,v,[[v]]),
without=vector(#vpar,v,[[]]),
ret=[[]]);
for(i=1,#seq,
my(v=seq[i], p=vpar[v],
all=concat(without[v],with[v]));
if(p,
with[p] = vpar_INTERNAL_setproduct(with[p], without[v]);
without[p] = vpar_INTERNAL_setproduct(without[p], all);
,
ret = vpar_INTERNAL_setproduct(ret,all)); \\ at root
with[v] = \\ kill intermediate vectors
without[v] = 0);
ret;
}
{
addhelp(vpar_indsets,
"vec = vpar_indsets(vpar)
Return a vector of all independent sets in vpar.
Each vec[i] returned is a Set() of vertex numbers which are an independent
set per vpar_is_indset().
The number of sets is #vec == vpar_indsets_count(vpar). This can be large
so vpar_indsets() is usually only practical for small trees.
Each set size is <= vpar_indnum(vpar) many vertices.
The empty set is always present.
The order of the sets in vec is unspecified.");
}
\\ u and v are vectors containing Set()s.
\\ Return a new vector which is a "Cartesian product" setunion(u[i],v[j])
\\ for all of i,j, so unions one set from u and one set from v.
\\ The order of the new sets in the new vector is unspecified.
\\
vpar_INTERNAL_setproduct(u,v) = [setunion(x,y) | x<-u; y<-v];
\\ u and v are vectors containing Set()s.
\\ All sets in u should be the same size, and all sets in v should be the
\\ same size, but may be different size from u sets.
\\ func is min or max (builtin func closures).
\\ Return a vector of the sets which are min or max size.
\\ So for min return u if its sets are smaller than those in v, or v if its
\\ sets are smaller. Conversely max.
\\ If u and v sets are the same size then return them all (for either min or
\\ max).
\\ u and/or v can each be empty vectors, in which case the return is the other.
\\
vpar_INTERNAL_sets_minmax(u,v,func) =
{
if(!#u, v,
!#v, u,
#u[1]==#v[1], concat(u,v),
#u[1] == func(#u[1],#v[1]), u, v);
}
\\ a,b,c,d are vectors containing Set()s.
\\ In each vector all sets should be the same size.
\\ Return set product a*b or c*d or concatenation or both, whichever results
\\ in the small
\\ Any of the vectors can be empty, in which case there are no sets in that
\\ product and the return is the other product.
\\
vpar_INTERNAL_sets_product_min(a,b,c,d) =
{
if(!(#a && #b), vpar_INTERNAL_setproduct(c,d),
!(#c && #d), vpar_INTERNAL_setproduct(a,b),
my(ab = #a[1] + #b[1],
cd = #c[1] + #d[1]);
if(ab < cd, vpar_INTERNAL_setproduct(a,b),
cd < ab, vpar_INTERNAL_setproduct(c,d),
concat(vpar_INTERNAL_setproduct(a,b),
vpar_INTERNAL_setproduct(c,d))));
}
vpar_indsets_count(vpar) = vpar_indpoly(vpar,1);
{
addhelp(vpar_indsets_count,
"count = vpar_indsets_count(vpar)
Return the number of independent sets in vpar.
vpar can be a forest, in which case the effect is product of counts in its
component trees.
The empty set of vertices is always independent (including an empty set in
the empty tree) so always have count >= 1.
See vpar_indpoly() for counts of each possible size independent set.");
}
vpar_indpoly(vpar,var='x) =
{
my(seq=vpar_INTERNAL_seq_upwards(vpar),
with = vector(#vpar,v,var), \\ 1 set size 1
without = vector(#vpar,v,1), \\ 1 set size 0
ret = var/var); \\ 1 as poly
for(i=1,#seq,
my(v=seq[i],
p=vpar[v],
all=without[v]+with[v]);
if(p,
with[p] *= without[v];
without[p] *= all;
,
ret *= all); \\ at root
with[v] = \\ kill intermediate polys
without[v] = 0);
ret;
}
{
addhelp(vpar_indpoly,
"poly = vpar_indpoly(vpar,var='x)
Return the independence polynomial of vpar.
Each term c*x^n is count c of how many independent sets of size n in vpar.
Optional parameter \"var\" can be the polynomial variable to use (a symbol),
or default 'x.
Specific terms include
polcoeff(poly,0) == 1 \\\\ empty set
polcoeff(poly,1) == #vpar \\\\ size 1
polcoeff(poly,2) == binomial(#vpar,2) - vpar_num_edges(vpar)
polcoeff(poly,#vpar) == (vpar_num_edges(vpar)==0)
poldegree(poly) == vpar_indnum(vpar)
pollead(poly) == vpar_indnum_and_count(vpar)[2]
subst(poly,'x,1) == vpar_indsets_count(vpar)
The empty set of vertices is always independent so constant term 1,
including empty tree vpar=[], its independence polynomial being 1 only.
Size 1 sets are all vertices.
Size 2 sets are all pairs except those across an edge.
Size #vpar all vertices is possible only when no edges.
The largest n term is the independence number and its coefficient is the
count of such sets.
vpar can be a forest, in which case the return is polynomial product
pol1*pol2*... of the independence polynomials of the component trees.");
}
\\ table[] is a 12-long vector reckoned as 4 rows and 3 columns.
\\ If a matrix then it would be table[pstate,vstate], but here
\\ 0 <= pstate <= 3and 0 <= vstate <= 2 so entry table[3*pstate+vstate+1].
\\ Each value is table[] = 2*new_pstate + increment.
\\ "increment" is either 0 or 1 to be added to the return value.
\\ If a given vertex v has no parent, then parent pstate=3, so using
\\ the last row of table[].
\\ This table-driven counting suits indnum, domnum and matchnum.
\\
vpar_INTERNAL_xxxnum(vpar,table) =
{
\\ print("vpar_INTERNAL_xxxnum() "vpar);
\\ print(" table "Vec(table));
my(num_children=vpar_INTERNAL_num_children_vecsmall(vpar),
state=vectorsmall(#vpar),
ret=0, v, p);
for(i=1,#vpar,
\\ print(" at i="i" ret="ret" num_children "num_children[i]);
v=i;
while(!num_children[v] && v<=i,
p=vpar[v];
my(t=table[state[v] + if(p,3*state[p]+1,10)]);
\\ print(" v="v" state="state[v]" parent p="vpar[v]" ret "ret" index "state[v] + if(p,3*state[p]+1,10)" add "bitand(t,1));
ret += bitand(t,1);
if(p, state[p] = t>>1; num_children[v=p]--,
break)));
\\ print(" num_children "Vec(num_children));
\\ print(" state "Vec(state));
\\ look for cycles
for(i=1,#vpar,
if(num_children[i],
\\ print(" cycle i="i);
\\ print(" num_children "Vec(num_children));
\\ print(" state "Vec(state));
\\ print(" ret "ret);
\\ find a non-zero state[v]
v=i;
while(!state[v],
if((v=vpar[v])==i,
\\ print(" cycle all state 0 at v="v);
\\ Force
\\ state[v]=1 excluded or dominated
\\ state[p]=2 with
\\ work up from p
state[v]=1;
num_children[v]=0;
state[v=vpar[v]]=2;
num_children[v]=0;
if(v!=i || table[9]>=0, ret++);
break));
until(!num_children[v],
num_children[v]=0;
p=vpar[v];
my(t=table[state[v] + if(p,3*state[p]) + 1]);
\\ print(" v="v" state "state[v]" p="p" state "state[p]" ret="ret" add "bitand(t,1));
ret += bitand(t,1);
state[v=p] = t>>1)));
\\ print(" final ret "ret);
ret;
}
\\ with = vertex is in the set.
\\ excluded = vertex cannot be in the set, because it already has a
\\ neighbour in the set.
\\ unseen = not yet considered, no neighbours yet considered.
\\
\\ Child v can go in the set as long it is not excluded, and as long as its
\\ parent p is not "with". Doing so sets parent to excluded (could already
\\ be so).
\\
vpar_INTERNAL_indnum_table = {Vecsmall([
3, 0, 2, \\ p state 0 unseen
3, 2, 2, \\ p state 1 excluded
4, 4, -2, \\ p state 2 with
1, 0, 0 \\ no p, v is root
\\ --------
\\ v=0 1 2 state, new child under p
])};
vpar_indnum(vpar) = vpar_INTERNAL_xxxnum(vpar,vpar_INTERNAL_indnum_table);
{
addhelp(vpar_indnum,
"num = vpar_indnum(vpar)
Return the independence number of vpar.
This is the number of vertices in the largest independent set of vpar.
vpar can be a forest, in which case the effect is sum of independence
numbers of its component trees.
The calculation follows
Mitchell, Cockayne, Hedetniemi, \"Linear Algorithms on Recursive
Representations of Trees\", Journal of Computer and System Sciences,
volume 18, 1979, pages 76-85.
http://www.sciencedirect.com/science/article/pii/0022000079900539
At a leaf, take that leaf, exclude its attachment (if it has one), and
reckon those two removed from the tree. This works because indnum in the
remainder is either bigger or the same when the attachment is omitted.
Always have indnum >= #vpar/2. This is since an independent set in a tree
can be formed starting anywhere and adjacent vertices alternately in the set
and not. That set and its set complement are both independent so one of
them is >= half.
Trees with indnum==#vpar/2 are those vpar_has_perfect_matching(), since
taking a leaf and attachment each time throughout is a perfect matching.");
}
vpar_indnum_and_count(vpar) =
{
my(seq=vpar_INTERNAL_seq_upwards(vpar),
with = vectorsmall(#vpar,v,1),
without = vectorsmall(#vpar),
with_count = vector(#vpar,v,1),
without_count = with_count,
ret = 0,
ret_count = 1);
for(i=1,#seq,
my(v=seq[i],
p=vpar[v],
v_any = max(with[v], without[v]),
v_any_count = if(v_any==with[v], with_count[v])
+ if(v_any==without[v], without_count[v]));
\\ print("v="v" with "with[v]" without "without[v]" v_any "with_or_without);
if(p,
with[p] += without[v];
with_count[p] *= without_count[v];
without[p] += v_any;
without_count[p] *= v_any_count;
,
ret += v_any; \\ at root
ret_count *= v_any_count);
with_count[v] = \\ kill bignum intermediate values
without_count[v] = 0);
[ret, ret_count];
}
{
addhelp(vpar_indnum_and_count,
"[num,count] = vpar_indnum_and_count(vpar)
Return the independence number of vpar and count of how many independent
sets are that size (maximum independent sets).
The independence number returned is the same as vpar_indnum(vpar).
vpar can be a forest, in which case the effect is sum of independence
numbers, and product of counts, of its component trees.
The empty set of vertices is always independent (including empty set as
independent in the empty tree) so always have count>=1.");
}
vpar_indnum_sets(vpar) =
{
\\ ENHANCE-ME: with[v] is sometimes smaller than without[v] at a given
\\ vertex. Could make an initial pass to find where v_any only wants the
\\ without[v], and not build with[v] there. This can include at the root
\\ so the final return doesn't need with[root].
\\
\\ if(#with[v] && #without[v] && #with[v][1] >= 2 && #with[v][1] < #without[v][1], print(vpar" v="v" p="p));
my(seq=vpar_INTERNAL_seq_upwards(vpar),
with = vector(#vpar,v,[ [v] ]),
without = vector(#vpar,v,[ [] ]),
ret = [ [] ]);
for(i=1,#seq,
my(v=seq[i],
p=vpar[v],
v_any = vpar_INTERNAL_sets_minmax(with[v], without[v], max));
if(p,
with[p] = vpar_INTERNAL_setproduct(with[p], without[v]);
without[p] = vpar_INTERNAL_setproduct(without[p], v_any);
,
ret = vpar_INTERNAL_setproduct(ret, v_any)); \\ at root
with[v] = \\ kill intermediate sets
without[v] = 0);
ret;
}
{
addhelp(vpar_indnum_sets,
"vec = vpar_indnum_sets(vpar)
Return a vector of all maximum independent sets in vpar.
Each vec[i] in the return is a Set() of vertex numbers which are an
independent set (vpar_is_indset()) of the maximum size possible in vpar,
which is size vpar_indnum(vpar).
The number of sets is count #vec == vpar_indnum_and_count(vpar)[2].
The order of the sets in vec is unspecified.
If vpar has a perfect matching (vpar_has_perfect_matching()) then vec has no
vertex common to all its maximum independent sets, since alternate vertices
of such a matching gives a set and its complement both size #vpar/2
maximum.");
}
\\-----------------------------------------------------------------------------
\\ Matching
vpar_is_matching_set(vpar,set) =
{
my(matched=vectorsmall(#vpar));
for(i=1,#set,
my(v=set[i]);
if(matched[v]++>1 || !vpar[v] || matched[vpar[v]]++>1, return(0)));
1;
}
{
addhelp(vpar_is_matching_set,
"bool = vpar_is_matching_set(vpar,set)
set is a Set() of vertex numbers.
These vertices are the child vertex v of a pair v and parent vpar[v].
Return 1 if these pairs are a matching in vpar, or 0 if not.
vpar can contain cycles.
A matching means no overlap between the pairs. So if v in the set then its
parent vpar[v] must not be in the set. If v in the set does not have a
parent then the set is reckoned not a matching since such a v is not a pair
in the tree.
A matching is also called an independent edge set since the pairs are a set
of edges with no endpoint in common.
See vpar_is_matching_flags() for the same on vector of booleans.");
}
vpar_is_matching_flags(vpar,flags) =
{
\\ matched[p] is when p already in a matched pair.
\\ Initially this is all the flags children.
\\ The parent of each pair child are added to matched[] successively.
\\ If a parent has two children claiming to be its pair then the first
\\ puts matched[p] and the second sees it so not a matching.
my(matched=Vecsmall(flags,#vpar)); \\ all of flags itself matched
for(v=1,#vpar,
if(flags[v],
(vpar[v] && matched[vpar[v]]++<=1) || return(0)));
1;
}
{
addhelp(vpar_is_matching_flags,
"bool = vpar_is_matching_flags(vpar,flags)
flags is a vector length #vpar of 0,1 booleans giving a set of vertices.
These vertices are the child vertex v of a pair v and parent vpar[v].
Return 1 if these pairs are a matching in vpar, or 0 if not.
vpar can contain cycles.
A matching means no overlap between the pairs. So if flags[v]==1 then must
not have parent flags[p]==1 in the set, nor a flags[u]==1 where u is another
child of p.
If flags[r]==1 where r is a root of vpar then flags is reckoned not a
matching because such a flag is not a pair in the tree.
See vpar_is_matching_set() for the same on set of vertex numbers.");
}
vpar_matchsets(vpar) =
{
my(seq=vpar_INTERNAL_seq_upwards(vpar),
any = vector(#vpar,v,[ [] ]), \\ no matches yet
without = vector(#vpar,v,[ [] ]),
ret = [ [] ]);
for(i=1,#seq,
my(v=seq[i],
p=vpar[v]);
if(p,
my(v_set = [v]);
any[p] = concat(vpar_INTERNAL_setproduct(any[p], any[v]),
vpar_INTERNAL_setproduct(without[p],
apply(s->setunion(s,v_set),
without[v])));
without[p] = vpar_INTERNAL_setproduct(without[p], any[v]);
,
ret = vpar_INTERNAL_setproduct(ret, any[v])); \\ at root
any[v] = \\ kill intermediate sets
without[v] = 0);
ret;
}
{
addhelp(vpar_matchsets,
"vec = vpar_matchsets(vpar)
Return a vector of all matchings in vpar.
Each vec[i] returned is a Set() of vertex numbers where a v in the set is a
pair v and its parent vpar[v], per vpar_is_matching_set().
The number of sets is #vec == vpar_matchings_count(vpar). This can be large
so vpar_matchsets() is usually only practical for small trees.
Each set size is <= vpar_matchnum(vpar) many vertices.
The order of the sets in vec is unspecified.");
}
vpar_matchings_count(vpar) = vpar_matchpoly(vpar,1);
{
addhelp(vpar_matchings_count,
"count = vpar_matchings_count(vpar)
Return the number of matchings in vpar (also called the Hosoya index).
A matching is a set of pairs of adjacent vertices, with vertices each in at
most one pair. This is also called an independent edge set since it is a
set of edges with have no endpoint in common.
The empty set of pairs is always a matching so the return is always >= 1.
The empty tree vpar==[] has only the empty matching so its return is 1.
vpar can be a forest, in which case the effect is product of counts of the
component trees.
See vpar_matchpoly() for counts of each possible size matching.");
}
\\ Or:
\\ unpaired[p] = prod v_either of its children
\\ paired[p] = sum unpaired[v] * others either
\\ pairs with one child v which must be unpaired, and the other children
\\ can be anything
\\ any[p] = prod unpaired[c] + paired[c]
\\
vpar_matchpoly(vpar,var='x) =
{
\\ any[v] = how many matchings have v either matched or not.
\\ without[v] = how many matchings have v not matched.
\\ Initially all any[v] = without[v] = 1 which is 1 empty matching.
\\ The code works upwards from leaf (childless) nodes to apply counts at v
\\ to parent p.
\\
\\ See test.gp test_matchpoly_by_paired() for an alternative using
\\ with,without instead of any,without. The former comes out wanting
\\ any[v] to update without[p] and for the toplevel return, so may as well
\\ work in its terms.
my(seq=vpar_INTERNAL_seq_upwards(vpar),
any = vector(#vpar,v,1), \\ 1 set size 0
without = any,
ret = var/var); \\ 1 as polynomial
for(i=1,#seq,
my(v=seq[i],
p=vpar[v]);
if(p,
any[p] = any[p]*any[v] \\ no new matches
+ without[p]*without[v]*var; \\ new match p to v
without[p] *= any[v]; \\ no new matches
,
ret *= any[v]); \\ accumulate at root
any[v] = \\ kill intermediate polys
without[v] = 0);
ret;
}
{
addhelp(vpar_matchpoly,
"poly = vpar_matchpoly(vpar,var='x)
Return the match polynomial of vpar.
Each term c*x^n is count c of how many matchings of n pairs are in vpar.
Optional parameter \"var\" can be the polynomial variable to use (a symbol),
or default 'x.
Properties include
polcoeff(poly,0) == 1 \\\\ always empty matching
polcoeff(poly,1) == vpar_num_edges(vpar) \\\\ 1-pairs
poldegree(poly) == vpar_matchnum(vpar) \\\\ high n
pollead(poly) == vpar_matchnum_and_count(vpar)[2]
(polcoeff(poly,#vpar\\2)>0) == vpar_has_perfect_matching(vpar,#vpar%2)
subst(poly,'x,1) == vpar_matchings_count(vpar) \\\\ sum coeffs
all roots real (since related adjacency matrix is real symmetric)
abs(root) >= 1/(4*(maxdegree-1))
Constant term always 1 since no pairs is always a matching. This includes
no pairs in an empty tree vpar=[]. The x term is 1-pair matchings which is
each edge.
High term n is the match number and its coefficient is the count of such
sets as per vpar_matchnum_and_count(vpar). If this n == floor(#vpar/2) then
that is a perfect or near perfect matching, according as #vpar even or odd.
There are matchings of all sizes 0 to matchnum inclusive, ie. all poly
coefficients >=1. This is since can omit pairs from the maximum to make any
smaller.
vpar can be a forest, in which case the effect is polynomial product
pol1*pol2*... of the match polynomials of the component trees.
See also vpar_adjacency_poly() and vpar_skew_adjacency_poly() which have
terms going by how many unmatched vertices.");
}
\\ Also:
\\
\\ Ole J Heilmann and Elliott H Lieb, "Theory of Monomer-Dimer Systems",
\\ Communications in Mathematical Physics, volume 25, number 3, 1972, pages
\\ 190-232.
\\
\\ Real roots.
\\ Maximum root 2*sqrt(d-1) for graph of vertex degrees <= d.
vpar_INTERNAL_matchnum_table = {Vecsmall([
3, 0, 0, \\ p state 0 unmatched
2, 2, 2, \\ p state 1 matched
2, 2, -2, \\ p state 2 matched
0, 0, 0 \\ p doesn't exist, v is root
\\ --------
\\ v=0 1 2 new child under p
])};
vpar_matchnum(vpar) = \
vpar_INTERNAL_xxxnum(vpar,vpar_INTERNAL_matchnum_table);
{
addhelp(vpar_matchnum,
"num = vpar_matchnum(vpar)
Return the match number of vpar.
This is the number of pairs in the largest matching in vpar, so the size of
the largest vpar_is_matching_set().
If vpar is the empty tree [] then return 0 for the empty set as a matching.
vpar can be a forest, in which case the effect is sum match numbers of its
component trees. vpar can contain cycles.");
}
\\ v matchnum = max( sum c_matched,
\\ 1 + one c_unmatched + sum other c_matched )
\\ v matchnum_with = sum(c_matchnum_with) + min(c_matchnum_without - c_matchnum_with)
\\ v matchnum_without = sum(max(c_matchnum_without + c_matchnum_with))
\\
vpar_matchnum_and_count(vpar) =
{
\\ with[v] = size of largest matching with v
\\ without_is_smaller[v] = 0 or 1 and size of largest matching without v
\\ is with[v] - without_is_smaller[v]
\\ with_count[v] = number of such matchings with v
\\ without_count[v] = number of such matchings without v
\\
\\ The choice for new with[p] is between existing with[p] extended by a
\\ with or without v below it and no new pairing. Or build a new pair
\\ p--v from without p and without v. Whichever gives the bigger new
\\ matching is chosen. They might be the same size in which case both are
\\ taken for with_count[p],
\\
\\ with_count[p] * v_max_count extend
\\ and/or without_count[p] * without_count[v] build
\\
\\ The "s" calculated 0,1,2 from without_is_smaller encodes the relative
\\ sizes of the two choices.
\\
\\ s==2 is where without[p] and without[v] both smaller so that the +1 new
\\ pairing would still leave them smaller than with[p] extended.
\\
\\ s==0 is where without[p] and without[v] both same as their respective
\\ with[] so that +1 makes it bigger than with[p] extended.
\\
\\ without_is_smaller = 0 or 1, ie. without is at most 1 smaller,
\\ can be seen in the s cases. Assuming 0 or 1 at v and existing p, the
\\ cases result in 0 or 1 at new p.
\\
my(seq=vpar_INTERNAL_seq_upwards(vpar),
with = vectorsmall(#vpar),
without_is_smaller = with,
with_count = vector(#vpar),
without_count = vector(#vpar,v,1),
ret = 0,
ret_count = 1);
for(i=1,#seq,
my(v=seq[i],
p=vpar[v],
v_max_count = with_count[v]
+ if(!without_is_smaller[v], without_count[v]));
if(p,
my(s = without_is_smaller[p] + without_is_smaller[v]);
with[p] += with[v];
if(!s, with[p]++; without_is_smaller[p] = 1);
with_count[p] = if(s>=1, with_count[p] * v_max_count)
+ if(s<=1, without_count[p] * without_count[v]);
without_count[p] *= v_max_count;
,
ret += with[v]; \\ at root
ret_count *= v_max_count);
with_count[v] = \\ kill bignum intermediate values
without_count[v] = 0);
[ret,ret_count];
}
{
addhelp(vpar_matchnum_and_count,
"[num,count] = vpar_matchnum_and_count(vpar)
Return a vector of match number of vpar and count of how many matchings
attain that number (maximum matchings). The match number returned is the
same as vpar_matchnum(vpar).
vpar can be a forest, in which case the effect is sum matchnums and product
counts of its component trees.
The empty set of pairs is always a matching, including empty matching in the
empty tree, so always have count>=1.
For interest, Heuberger and Wagner determined the unique tree of n vertices
with greatest count (or 2 trees of equal greatest for n=6 or n=34). See
examples/most-maximum-matchings.gp for some code creating those.");
}
\\-----------------------------------------------------------------------------
\\ Perfect Matching, Near Perfect Matching
\\ Maybe: Mitchell and Hedetniemi, "Edge Domination in Trees", in
\\ Proc. Eighth Southeastern Conf. on Combinatorics, Graph Theory and
\\ Computing.
\\
\\ Parameter "perfect" is:
\\ perfect=0 for vpar_has_perfect_matching()
\\ perfect=1 for vpar_has_perfect_matching() near perfect
\\ perfect=#vpar+2, so >=2, for vpar_matchnum()
\\ though vpar_matchnum() uses vpar_INTERNAL_xxxnum() now
\\
vpar_INTERNAL_matchnum(vpar,perfect=0) =
{
\\ print("vpar_INTERNAL_matchnum() "vpar" perfect="perfect);
\\ At a leaf, can pair child+parent if both unmatched.
\\ Continue doing so at grandparent and above too.
\\ If a child has no parent or the parent is already matched then that is
\\ the child left unmatched.
\\ "unmatched" is the count of unmatched vertices.
\\ If unmatched > perfect then that is a failed perfect or near perfect.
\\
\\ Continuing up to grandparent etc is constrained v<=i so that an
\\ unmatched v > i is not counted a second time when the i loop reaches
\\ that v.
\\
\\ seen[v] = num children, decremented as children are processed
\\ or = -1 when in a matched pair
\\
\\ test.gp runs this with perfect=#vpar+2 so the forest is never reckoned
\\ imperfect. The final return (#vpar-unmatched)>>1 is the matchnum for
\\ that case.
\\
\\ ENHANCE-ME: Remove the matchnum bit here (to a copy of the code in
\\ test.gp probably) when happy with vpar_matchnum() by state table.
my(seen=vpar_INTERNAL_num_children_vecsmall(vpar),
unmatched=0); \\ num unmatched vertices
\\ print(" num children "Vec(seen));
for(i=1,#vpar,
\\ print(" i="i);
my(v=i);
while(v<=i && !seen[v],
\\ print(" v="v);
my(p=vpar[v]);
if(p && seen[p]>=0,
\\ print(" pair v="v" p="p);
seen[v] = -1;
seen[p] = -1;
(v=vpar[p]) || break; \\ v = grandparent
seen[v]--; \\ p processed, decrease children of grandparent
,
\\ print(" v no match (at root or p matched), unmatched="unmatched);
if(unmatched++>perfect, return(0));
break)));
\\ print(" final unmatched="unmatched);
if(perfect<=1, 1, (#vpar-unmatched)>>1);
}
vpar_has_perfect_matching(vpar,near=0) = \
bitand(#vpar,1)==near && vpar_INTERNAL_matchnum(vpar,near);
{
addhelp(vpar_has_perfect_matching,
"bool = vpar_has_perfect_matching(vpar,near=0)
Return 1 if vpar has a perfect matching, or return 0 if not.
A perfect matching is all vertices in matched adjacent pairs, which is
possible only in an even size #vpar%2==0.
Optional parameter \"near\" can be 1 to test for a near perfect matching.
A near perfect matching is all vertices except one in matched adjacent
pairs, which is possible only in an odd size #vpar%2==1.
The test is equivalent to asking 2*vpar_matchnum(vpar) == #vpar - near,
though the actual code notices when there are too many unmatched vertices
part way through the calculation for an early return false.
vpar can be a forest, in which case the effect is to require a perfect
matching in each of its component trees. Or for near perfect to require
near perfect in one tree (odd size) and perfect in all others (even sizes)
so that all but one vertex paired.");
}
vpar_count_perfect_matching(n,near=0,forest=0) =
{
\\ root=1 n^(h-2)
\\ trees n^(h-1) near (h+2)*(h+1)/2
\\ forests (n+1)^(h-1) near (h+4)*(h+1)/2
if(bitand(n,1)!=near, 0,
n,
my(h=n>>1);
h!*binomial(n,h)
* (n+(forest>0))^(h-1-(forest<0))
* if(near, ((h+2 + if(forest>0,2))*(h+1)) >> 1, 1);
,
1); \\ n==0
}
{
addhelp(vpar_count_perfect_matching,
"count = vpar_count_perfect_matching(n,near=0,forest=0)
Return the number of labelled trees of n vertices which have a perfect
matching, per vpar_has_perfect_matching().
Optional parameter \"near\" can be 1 to count trees with near perfect
matching, per the near parameter to vpar_has_perfect_matching().
Optional parameter \"forest\" can be 1 to count forests, or can be -1 to
count trees with root=1.
Counts for perfect matching are
T(n) = if(n%2==1,0, h!*binomial(n,h) * n^(h-1)) \\\\ trees
= 1, 0, 2, 0, 48, 0, 4320, ...
where h=n/2
F(n) = if(n%2==1,0, h!*binomial(n,h) * (n+1)^(h-1)) \\\\ forests
= 1, 0, 2, 0, 60, 0, 5880, ... (n even, 2*A036363)
B(n) = if(n==0,1, T(n)/n) \\\\ trees root=1
= 1, 0, 1, 0, 12, 0, 720, ...
A perfect matching is pairings of parent and child. There are h of each.
h!*binomial(n,h) is h parents with order, and the children go under them.
Pairs arranged in a tree would be h^(h-1), but each parent in the pair can
attach under either parent or child of the pair above so n^(h-1). Similarly
F with base n+1. Root=1 trees are all trees divided n to fix the root.
Counts for near perfect matching have an extra factor,
NT(n) = if(n%2==0,0, h!*binomial(n,h) * (h+2)*(h+1)/2 * n^(h-1))
= 0, 1, 0, 9, 0, 600, 0, ...
where h=floor(n/2)
NF(n) = if(n%2==0,0, h!*binomial(n,h) * (h+4)*(h+1)/2 * (n+1)^(h-1))
= 0, 1, 0, 15, 0, 1080, 0, ...
NB(n) = if(n==0,0, NT(n)/n) \\\\ trees root=1
= 0, 1, 0, 3, 0, 120, 0, ...
There are again h ordered parents, and one of h+1 children unmatched.
Arranged in a tree these would be (h+1)^h but again parents can be under
either above so n for those. The unmatched vertex is made a unique location
by taking the near perfect matching which has the unmatched as highest and
biggest. Its h+1 choice times h+1 above has includes both bigger and
smaller sibling, so half when under the h possible parents, (h+1)^2 -
h*(h+1)/2 = (h+2)*(h+1)/2. For forest likewise, but (h+2)^h of pairs and
unmatched which becomes (h+1)*(h+2) - h*(h+1)/2 = (h+4)*(h+1)/2. Near
perfect forests also follow from a near perfect tree and the rest perfect
forest.");
}
\\ GP-Test vector(20,h,h--; sum(t=1,h+1,t-1)) == vector(20,h,h--; (h+1)*h/2)
\\ GP-Test (h+1)*(h+1) - (h+1)*h/2 == (h+2)*(h+1)/2
\\ GP-Test (h+1)*(h+2) - (h+1)*h/2 == (h+4)*(h+1)/2
\\
\\ GP-DEFINE T(n) = my(h=n/2); if(n%2==1,0, n==0,1, h!*binomial(n,h) * n^(h-1));
\\ GP-Test vector(7,n,n--; T(n)) == [1, 0, 2, 0, 48, 0, 4320]
\\ vector(5,n,n=2*n; T(n))
\\ not in OEIS: 2, 48, 4320, 860160, 302400000
\\
\\ GP-DEFINE F(n) = my(h=n/2); if(n%2==1,0, h!*binomial(n,h) * (n+1)^(h-1));
\\ GP-Test vector(7,n,n--; F(n)) == [1, 0, 2, 0, 60, 0, 5880]
\\ vector(7,n,n=2*n; F(n))
\\ not in OEIS: 2, 60, 5880, 1224720, 442743840, 247013807040, 197026830000000
\\ not A036363 which is 1/2 shown
\\
\\ GP-DEFINE B(n) = my(h=n/2); if(n==0,1, n%2==1,0, h!*binomial(n,h) * n^(h-2));
\\ GP-Test vector(7,n,n--; B(n)) == [1, 0, 1, 0, 12, 0, 720]
\\ GP-Test vector(10,n,n--; B(n)) == vector(10,n,n--; if(n==0,1, T(n)/n))
\\ vector(7,n,n=2*n; B(n))
\\ not in OEIS: 1, 12, 720, 107520, 30240000, 13795246080, 9302892318720
\\
\\ my(n=50); vpar_count_perfect_matching(n)*1.0 / vpar_count(n)
\\
\\ GP-DEFINE NT(n) = my(h=n\2); if(n%2==0,0, h!*binomial(n,h) * (h+2)*(h+1)/2 * n^(h-1));
\\ GP-Test vector(7,n,n--; NT(n)) == [0, 1, 0, 9, 0, 600, 0]
\\ vector(5,n,n=2*n+1; NT(n))
\\ not in OEIS: 9, 600, 102900, 33067440, 17045637840
\\
\\ GP-DEFINE NF(n) = my(h=n\2); if(n%2==0,0, h!*binomial(n,h) * (h+4)*(h+1)/2 * (n+1)^(h-1));
\\ GP-Test vector(7,n,n--; NF(n)) == [0, 1, 0, 15, 0, 1080, 0]
\\ vector(7,n,n=2*n+1; NF(n))
\\ not in OEIS: 15, 1080, 188160, 60480000, 31039303680, 23257230796800, 23941516728729600
\\
\\ GP-DEFINE NB(n) = my(h=n\2); if(n%2==0,0, h!*binomial(n,h) * (h+2)*(h+1)/2 * n^(h-2));
\\ GP-Test vector(7,n,n--; NB(n)) == [0, 1, 0, 3, 0, 120, 0]
\\ GP-Test vector(10,n,n--; NB(n)) == vector(10,n,n--; if(n==0,0, NT(n)/n))
\\ vector(7,n,n=2*n+1; NB(n))
\\ not in OEIS: 3, 120, 14700, 3674160, 1549603440, 988055228160, 886620735000000
\\-----------------------------------------------------------------------------
\\ Maximal Matchings
\\ cf maybe
\\ Gorska and Skupien, "Trees With Maximum Number of Maximal Matchings",
\\ Discrete Mathematics, volume 307, number 11-12, 2007, pages 1367-1377.
vpar_is_maximal_matching_set(vpar,set) =
{
\\ same as vpar_is_matching_set()
my(matched=vectorsmall(#vpar));
for(i=1,#set,
my(v=set[i]);
if(matched[v]++>1 || !vpar[v] || matched[vpar[v]]++>1, return(0)));
\\ check maximal by looking for a v which could match to its parent vpar[v]
for(v=1,#vpar,
matched[v] \\ already matched
|| !vpar[v] \\ no parent
|| matched[vpar[v]] \\ parent already matched
|| v==vpar[v] \\ self-loop its own parent not a pair
|| return(0));
1;
}
{
addhelp(vpar_is_maximal_matching_set,
"bool = vpar_is_maximal_matching_set(vpar,set)
set is a Set() of vertex numbers which are each the child vertex v of a pair
v and parent vpar[v]. Return 1 if these pairs are a maximal matching in
vpar, or return 0 if not. vpar can contain cycles.
A matching means no overlap between the pairs, per vpar_is_matching_set().
Maximal means nowhere an unmatched pair which could be added to the set.
See vpar_is_maximal_matching_flags() for the same on vector of booleans.");
}
vpar_is_maximal_matching_flags(vpar,flags) =
{
\\ same as in vpar_is_matching_flags()
my(matched=Vecsmall(flags,#vpar)); \\ all of flags itself matched
for(v=1,#vpar,
if(flags[v],
(vpar[v] && matched[vpar[v]]++<=1) || return(0)));
\\ check maximal, same as in vpar_is_maximal_matching_set()
for(v=1,#vpar,
matched[v] || !vpar[v] || matched[vpar[v]] || v==vpar[v] || return(0));
1;
}
{
addhelp(vpar_is_maximal_matching_flags,
"bool = vpar_is_maximal_matching_flags(vpar,flags)
flags is a vector length #vpar of 0,1 booleans giving a set of vertices.
These vertices are each the child vertex v of a pair v and parent vpar[v].
Return 1 if these pairs are a maximal matching in vpar, or 0 if not.
vpar can contain cycles.
A matching means no overlap between the pairs, per vpar_is_matching_flags().
Maximal means nowhere an unmatched pair which could be added to the set,
so nowhere a flags[]=0 which changed to 1 would also be a matching.
See vpar_is_maximal_matching_set() for the same on set of vertex numbers.");
}
vpar_maximal_matchings_count(vpar) = vpar_maximal_matchpoly(vpar,1);
{
addhelp(vpar_maximal_matchings_count,
"count = vpar_maximal_matchings_count(vpar)
Return the number of maximal matchings in vpar, ie. how many
vpar_is_maximal_matching_set() sets.
There is always at least one maximal matching set so count>=1 always.
The empty vpar=[] or singleton vpar=[0] have count=1 for the empty matching
as the only possible matching there.
vpar can be a forest, in which case the effect is product of counts of its
component trees.
See vpar_maximal_matchpoly() for counts of each possible size.");
}
\\ ENHANCE-ME: Think maximal matchpoly has all real roots. Is that right?
\\ Is there a real symmetric matrix interpretation?
\\
vpar_maximal_matchpoly(vpar,var='x) =
{
\\ with[v] = how many maximal matchings with v matched
\\ without[v] = how many matchings where v not matched and the matchings
\\ might or might not be maximal. The non-maximals are only
\\ non-maximal in respect of v. If v is matched to a parent above it
\\ then all of without[v] are maximal.
\\ without_max[v] = how many matchings where v not matched and the
\\ matchings are maximal.
my(seq=vpar_INTERNAL_seq_upwards(vpar),
with = vector(#vpar), \\ 0 sets
without = vector(#vpar,v,1), \\ 1 set size 0
without_max = without,
ret = var/var); \\ 1 as poly
for(i=1,#seq,
my(v=seq[i],
p=vpar[v],
v_max = with[v] + without_max[v]);
\\ print(" v="v" with "with[v]" without "without[v]" max "without_max[v]);
if(p,
with[p] = with[p] * v_max \\ extend by maximal in v
+ without[p] * without[v] * var; \\ build new matched pair
without[p] *= v_max;
without_max[p] *= with[v];
\\ print(" set p="p" with "with[p]" without "without[p]" max "without_max[p]);
,
ret *= v_max); \\ at root
with[v] = \\ kill intermediate polynomials
without[v] =
without_max[v] = 0);
ret;
}
{
addhelp(vpar_maximal_matchpoly,
"poly = vpar_maximal_matchpoly(vpar,var='x)
Return the maximal match polynomial of vpar.
Each term c*x^n is count c of how many maximal matchings of n pairs are in
vpar. Optional parameter \"var\" can be the polynomial variable to use (a
symbol), or default 'x.
Specific terms and properties include
poly!=0 \\\\ at least one maximal matching set
poldegree(poly) == vpar_matchnum(vpar)
pollead(poly) == vpar_matchnum_and_count(vpar)[2]
subst(poly,'x,1) == vpar_maximal_matchings_count(vpar)
poly!=0 includes the empty tree vpar=[] or singleton vpar=[0] where poly==1
for the empty matching as the only possible there. The largest n term is
the match number and its coefficient is the count of such sets.
The smallest n term is valuation(poly,'x). The terms from there to the
largest all have non-zero coefficients (no gaps), as noted for instance by
Doslic and Zubac. This follows from Edmonds showing that, in any graph, a
maximal matching not yet at the maximum size has an \"augmenting path\"
which can be modified to give a size+1 maximal.
vpar can be a forest, in which case the effect is polynomial product
pol1*pol2*... of the maximal match polynomials of the component trees.");
}
\\------------------------------------------------------------------------------
\\ Predicate By State Table
\\ vpar_INTERNAL_is_state_table() returns 1 if vpar passes the given state
\\ table predicate, or return 0 if not.
\\
\\ States are numbered 1 to num_states inclusive. Each vertex starts state 1.
\\
\\ "table" is a Vecsmall representing a transition matrix num_states x
\\ num_states. For parent state p and child state v, the table entry is the
\\ new state at the parent when such a child is added underneath.
\\ table[x] = 0 entries are where that combination of p,v is not acceptable,
\\ which means an immediate return 0.
\\
\\ "root" is a bit mask where bittest(root,state) == 1 if that state is
\\ acceptable as the final state at a root vertex, or bittest == 0 if not.
\\
\\ In a forest, every component tree must be accepted.
\\
vpar_INTERNAL_is_state_table(vpar,table,num_states,root) =
{
\\ print("vpar_INTERNAL_is_state_table() "vpar);
my(seq=vpar_INTERNAL_seq_upwards(vpar),
state = vectorsmall(#vpar,v,1));
for(i=1,#seq,
my(v=seq[i], p=vpar[v]);
\\ print(" v="v" state "state[v]" p="p,if(!p,"",Str(" state "state[p]" -> ",table[num_states*state[p] + state[v] - num_states])));
if(p,
state[p] = table[num_states*(state[p]-1) + state[v]],
bittest(root,state[v])) \\ at root
|| return(0));
1;
}
\\------------------------------------------------------------------------------
\\ Equimatchable
\\ new_parent_state
\\ = vpar_INTERNAL_is_equimatchable_table[5*parent_state + child_state + 5]
\\ or 0 if this tree and anything including it is not equimatchable
\\
\\ 5*5 == 25 entries
vpar_INTERNAL_is_equimatchable_table = {Vecsmall([
2, 3, 4, 5, 0, \\ p=1 row
2, 0, 2, 0, 0, \\ p=2
0, 3, 0, 0, 0, \\ p=3
2, 0, 4, 0, 0, \\ p=4
0, 0, 0, 0, 0 \\ p=5
\\ -------------
\\ v=1 2 3 4 5 as new child under
])};
vpar_is_equimatchable(vpar) =
{
vpar_INTERNAL_is_state_table(vpar,
vpar_INTERNAL_is_equimatchable_table, 5, 46);
\\ GP-Test binary(46) == [ 1, 0, 1, 1, 1, 0]
\\ 5 4 3 2 1 state
\\ State 4 is not equimatchable.
}
{
addhelp(vpar_is_equimatchable,
"bool = vpar_is_equimatchable(vpar)
Return 1 if vpar is equimatchable, or return 0 if not.
A tree (or forest or graph) is equimatchable when all its maximal matchings
are the same size, that size being the match number (vpar_matchnum()).
The current implementation uses a state table (5 states). Each state
represents smallest maximal matching conditions relative to the matchnum.
A child accumulated under its parent updates the state of the parent.
State n happens to correspond to conditions at the end of a path n.
Non-equimatchable can be identified at various points for an early 0 return.
See comments in the code for details.
See examples/equimatchable-count.gp for some calculation of how many
(ordered) trees are equimatchable.");
}
\\
\\ The states are
\\
\\ /-- smallest maximal ---\
\\ state with without without_max matchnum
\\ ----- ---- ------- ----------- --------
\\ 1 none 0 0 unmatched
\\ 2 0 -1 none matched
\\ 3 0 0 0 unmatched
\\ 4 0 -1 -1 matched
\\ 5 0 -1 0 unmatched
\\
\\ The matchnum column is vertex matched or unmatched in the match number
\\ calculation per vpar_matchnum(). That calculation always takes unmatched
\\ parent and child for a new pairing so no other forms to consider.
\\
\\ The rest are for calculating the smallest maximal matching. The
\\ quantities are relative to the matchnum, so size - matchnum, and so <=0.
\\
\\ "with" is the size of the smallest maximal matching which has the vertex
\\ matched.
\\
\\ "without" is the size of the smallest matching which has v not matched.
\\ v might or might not be maximal but everything below is maximal, in the
\\ sense that if v is paired with another vertex then all the rest below it
\\ is maximal.
\\
\\ "without_max" is the size of the smallest maximal matching which has v
\\ not matched. This is a subset of "without" so always have without_max >=
\\ without.
\\
\\ All quantities are <=0 because the matchnum is the biggest of all
\\ possible maximal matching. "without" includes some non-maximals, which
\\ lets it be smaller, possibly.
\\
\\ Initially all vertices are state 1, representing the conditions at a
\\ singleton. Notice "with" is "none" because a singleton has no way for v
\\ matched.
\\
\\ State 2 is a path-2. Notice its "without_max" is none meaning there are
\\ no maximal matchings without v. There is a "without" though, it being
\\ the two vertices both unmatched, that being not maximal (but if v becomes
\\ matched some other way then the single below it is maximal).
\\
\\ Maximals at v accumulate into parent p by
\\
\\ with[p] = min(with[p] + v_max,
\\ without[p] + without[v] + 1)
\\ without[p] += v_max
\\ without_max[p] += with[v]
\\
\\ where v_max = min(with[v], without_max[v])
\\
\\ without_max[p] can only have with[v] below it, as any without[v] would
\\ make p non-maximal since it and a child would be unmatched. If with[v]
\\ is "none" then without_max[p] becomes "none".
\\
\\ without[p] is allowed to be non-maximal so it can have anything under it
\\ provided that child and the rest below is maximal if/when p is matched,
\\ hence v_max below.
\\
\\ with[p] is either a new match of without[p] and without[v], or keep the
\\ existing with[p] (whatever it might be matched with) and take v_max below.
\\ If with[p] is "none" (because no children to match with yet) then new
\\ match is the only choice.
\\
\\ If the matchnum calculation makes a new pair from unmatched+unmatched
\\ then that increases the matchnum so -1 on all the quantities to stay
\\ relative.
\\
\\ A state combination gives a maximal which is smaller than matchnum when
\\
\\ "with" exists and with < 0 means non-equimatchable
\\
\\ If the tree stops at with<0 then that is a maximal < matchnum.
\\ If the tree continues, then any neighbouring tree added has v_max<=0
\\ added to the "with", hence maintaining with<0 until the tree is complete.
\\ So whenever with<0 seen it means not equimatchable.
\\
\\ At the end of tree processing (ie. the root), the condition is tighter
\\
\\ v_max < 0 at root means non-equimatchable
\\
\\ This brings in without_max < 0 as a possible smaller maximal. Within the
\\ tree this cannot be done because without_max depends on "with" existing
\\ for all its children. Any singleton child (state 1) has with[v]=none and
\\ it makes without_max[p] become none, no matter what it was before.
\\
\\ State 4, which is the end of a path-4, is an example of this without_max.
\\ State 4 has v_max=-1 which is non-equimatchable if stop there, but if it
\\ gains a singleton child then it becomes a path-5 rooted second from the
\\ end (which is state 2), and path-5 is equimatchable.
\\
\\ Working through states 1 to 5 as parent which each possible child 1 to 5
\\ shows that they transition among each other, or to a state with<0 which
\\ is non-equimatchable. These transitions are in the matrix
\\ vpar_INTERNAL_is_equimatchable_table.
\\
\\ v=3 +-+
\\ loop v | v=1 parent states p
\\ p=4 ---------+ child v below
\\ ^ | accumulated
\\ v=3 | | goes to new p
\\ | v=1 v
\\ +-> p=3 <------ p=1 -----> p=2 <-+ all other
\\ +-- v=2 | --+ transitions
\\ v=2 | v=4 v=1,3 are to
\\ loop v loop non-equimatchable
\\ p=5
\\
\\ See devel/pictures.tex for some pgf of the state machine diagram. See
\\ examples/equimatchable-count.gp for using the state table to count
\\ equimatchable trees, and states in terms of zero or more, or one or more,
\\ of other state subtrees.
\\
\\ Notice state p=5 is equimatchable, but anything more under it makes
\\ non-equimatchable. The other states can have various v=1,2,3 children
\\ looping for no state change and continuing equimatchability, or potential
\\ equimatchability in the case of p=4 which cannot be a top as noted above.
\\
\\ Further concerning path-4 being non-equimatchable, notice p=3 with a v=1
\\ child goes to non-equimatchable. This is a path-3 plus singleton making
\\ a path-4 rooted at second last. This differs from state p=4 in where
\\ more tree might join. p=4 is still ok because joining more at the end
\\ can reach path-5. But p=3 plus v=1 is more tree at the second-last
\\ vertex which cannot reach path-5, nor anything else equimatchable.
\\
\\ The states are a recursive characterization of equimatchable structure,
\\ though choice of root would give various ways getting the same free tree.
\\------------------------------------------------------------------------------
\\ Stable Equimatchable
\\
\\ See devel/pictures.tex for a state machine diagram.
\\ See states creation etc in test.gp.
\\
\\ 1-> 2 -1->4 or -5-> 7 -1->4 or -5,9->7loop
\\ 1 2-> 3 doomed
\\ 4-> 5 -4-> 9 -4->9loop
\\ 5-> 6 -1->7
\\ 7-> 8 doomed 3,8 equivalent shrunk together
\\
\\ States 6,7 ok within the tree since more children can go to an edge
\\ stable equimatchable. But at the root, where nothing more to come, they
\\ are not (pairings and cuts shown in test-wip.gp).
\\
\\ 8*8 == 64 entries, values 0 to 8 inclusive
vpar_INTERNAL_is_stable_equimatchable_table = {Vecsmall([
2, 3, 0, 5, 6, 0, 3, 9, 0, \\ p=1 row
4, 0, 0, 0, 7, 0, 0, 7, 0, \\ p=2
0, 0, 0, 0, 0, 0, 0, 0, 0, \\ p=3 (shrunk)
4, 0, 0, 0, 4, 0, 0, 4, 0, \\ p=4
0, 0, 0, 8, 0, 0, 0, 0, 0, \\ p=5
7, 0, 0, 0, 6, 0, 0, 6, 0, \\ p=6
4, 0, 0, 0, 7, 0, 0, 7, 0, \\ p=7
0, 0, 0, 8, 0, 0, 0, 0, 0, \\ p=8
7, 0, 0, 0, 6, 0, 0, 9, 0 \\ p=9
\\ ----------------------------------
\\ v= 1 2 3 4 5 6 7 8 9
])};
vpar_is_stable_equimatchable(vpar) =
{
vpar_INTERNAL_is_state_table(vpar,
vpar_INTERNAL_is_stable_equimatchable_table, 9, 318);
\\ GP-Test binary(318) == [ 1, 0, 0, 1, 1, 1, 1, 1, 0]
\\ 9 8 7 6 5 4 3 2 1 state (shrunk)
\\ Not states 6,7,9 at root.
}
{
addhelp(vpar_is_stable_equimatchable,
"bool = vpar_is_stable_equimatchable(vpar)
Return 1 if vpar is stable equimatchable, or return 0 if not.
Edge or vertex stable equimatchable for a graph is per
Zakir Deniz and Tinaz Ekim, \"Stable Equimatchable Graphs\",
arxiv 1602.09127
Edge stable means equimatchable (vpar_is_equimatchable()) and still
equimatchable if one edge is cut. Vertex stable means equimatchable and
still equimatchable if one vertex removed. For a tree these two are the
same since components left by edge removal can be had by vertex removal of
the far side of the edge, and vice versa.
An empty vpar==[] is reckoned stable equimatchable since it is
equimatchable and there are no edges or vertices to consider removing.
The current code is a state table (of 9 states) similar to
vpar_is_equimatchable(). The state here represents a collection of
equimatchable states from each possible one edge cut below, and no cut.
A child accumulated under a parent must have all possible <= 1 cuts
remaining equimatchable. Not stable equimatchable can be identified at
various points for an early 0 return.");
}
\\------------------------------------------------------------------------------
\\ Almost Equimatchable
\\ See devel/pictures.tex for a state machine diagram.
\\ States 6,8,9 as parents loop only to themselves on further children,
\\ and are accepting at the root (in the bittest).
\\ State 11 is accepted at the root, but any further child, or it as child
\\ of anything else, goes non.
\\
vpar_INTERNAL_is_almost_equimatchable_table = {Vecsmall([
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, \\ p=1
2, 6, 2, 8, 6, 8, 8, 0, 8, 0, 0,
6, 3, 6, 9, 6, 9, 0, 9, 0, 0, 0,
2, 6, 4, 8, 6, 10, 8, 0, 10, 0, 0,
8, 9, 8, 11, 0, 11, 0, 0, 0, 0, 0,
6, 6, 6, 0, 6, 0, 0, 0, 0, 0, 0, \\ p=6
8, 9, 10, 11, 0, 0, 0, 0, 0, 0, 0,
8, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, \\ p=8
0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, \\ p=9
8, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \\ p=11
])};
\\ GP-Test 11*11 == 121 /* table size */
vpar_is_almost_equimatchable(vpar) =
{
#vpar && vpar_INTERNAL_is_state_table(vpar,
vpar_INTERNAL_is_almost_equimatchable_table, 11, 3024);
\\ GP-Test binary(3024) == [ 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0]
\\ 11 10 9 8 7 6 5 4 3 2 1
}
{
addhelp(vpar_is_almost_equimatchable,
"bool = vpar_is_almost_equimatchable(vpar)
Return 1 if vpar is almost equimatchable, or return 0 if not.
Almost equimatchable means maximal matchings are of 2 sizes, matchnum and
matchnum-1 (so vpar_maximal_matchpoly() is two terms). This is vpar not
equimatchable (vpar_is_equimatchable()), but missing equimatchable just by
having maximals 2 sizes instead of 1 size.
The current implementation is a state table (of 11 states) similar to
vpar_is_equimatchable(). The states represent matching conditions in the
same way, but are watching for a maximal which is matchnum - 2 or smaller to
prove isn't almost-equimatchable. It happens that state n is the matching
conditions at the end of a path-n, though arising from smaller structure
too.");
}
\\-----------------------------------------------------------------------------
\\ Dominating Sets, Domination Number, Domination Polynomial
vpar_is_domset(vpar,set) =
{
my(dominated=vectorsmall(#vpar),
count=#vpar-#set,
p);
for(i=1,#set, dominated[set[i]]=1); \\ each of set[] dominates itself
for(i=1,#set, \\ each of set[] dominates its parent
if((p=vpar[set[i]]) && (count-=(dominated[p]++==1))==0,
return(1)));
for(v=1,#vpar, \\ undominated v with parent in set[]
if(!dominated[v] && (p=vpar[v]) && setsearch(set,p) && count--==0,
return(1)));
!count;
}
{
addhelp(vpar_is_domset,
"bool = vpar_is_domset(vpar,set)
set is a Set() of vertex numbers.
Return 1 if it is a dominating set in vpar, or 0 if not.
vpar can contain cycles.
A dominating set means each vertex of vpar is either in the set or has a
neighbour in the set.
Empty vpar==[] is reckoned dominated by the empty set [].");
}
vpar_is_domset_flags(vpar,flags) =
{
\\ "count" is how many undominated vertices.
\\ When v or p is newly dominated the count decreases.
\\ If count reaches 0 then can make an early exit.
\\
\\ Some experimenting with all flags over all vpar suggests there's enough
\\ early exit cases to be worthwhile, though in practice it would depend
\\ how often expecting to find domset or not, and if vpar small then
\\ stopping early hardly matters.
my(dominated=Vecsmall(flags,#vpar), \\ all of flags itself dominated
count=#vpar-hammingweight(dominated),
p);
for(v=1,#vpar,
if(p=vpar[v],
if( (flags[p] && !(count-=(dominated[v]++==1)))
|| (flags[v] && !(count-=(dominated[p]++==1))),
return(1))));
!count;
}
{
addhelp(vpar_is_domset_flags,
"bool = vpar_is_domset_flags(vpar,flags)
flags is a vector length #vpar of 0,1 booleans giving a set of vertices.
Return 1 if this is a dominating set in vpar or return 0 if not.
vpar can contain cycles.
This means each vertex is either in the set (flags[v]==1) or has a neighbour
in the set (either a child or a parent).
Empty vpar==[] is reckoned dominated by the empty set flags==[].");
}
vpar_domsets(vpar) =
{
\\ without[p] = prod v_dom
\\ without_undom[p] = prod without_dom[v]
\\ without_dom[p] = setminus(without, without_undom)
\\ = sum >=1 v with, rest v_dom which is previous without[p]
my(seq=vpar_INTERNAL_seq_upwards(vpar),
with = vector(#vpar,v,[[v]]), \\ sets with v included
without = vector(#vpar,v,[[]]), \\ dom or undom
without_dom = vector(#vpar,v,[]), \\ without v but dominated
ret=[[]]);
for(i=1,#seq,
my(v=seq[i], p=vpar[v],
v_dom = concat(with[v],without_dom[v]));
if(p,
with[p] = vpar_INTERNAL_setproduct(with[p],
concat(with[v], without[v]));
without_dom[p] = concat(vpar_INTERNAL_setproduct(without_dom[v],
without_dom[p]),
vpar_INTERNAL_setproduct(with[v],
without[p]));
without[p] = vpar_INTERNAL_setproduct(without[p], v_dom);
,
ret = vpar_INTERNAL_setproduct(ret,v_dom)); \\ at root
with[v] = \\ kill intermediate vectors
without[v] =
without_dom[v] = 0);
ret;
}
{
addhelp(vpar_domsets,
"vec = vpar_domsets(vpar)
Return a vector of all dominating sets in vpar.
Each vec[i] returned is a Set() of vertex numbers which are a dominating
set per vpar_is_domset().
The number of sets is #vec == vpar_domsets_count(vpar).
Each set size is >= vpar_domnum(vpar) many vertices.
The biggest set is all of 1..#vpar. It is always present, including empty
set for #vpar==0.
The order of the sets in vec is unspecified.");
}
vpar_domsets_count(vpar) = vpar_dompoly(vpar,1);
{
addhelp(vpar_domsets_count,
"count = vpar_domsets_count(vpar)
Return the number of dominating sets in vpar.
vpar can be a forest, in which case the effect is product of counts of its
component trees.
The set of all vertices is always a dominating set (including empty set in an
empty tree) so count>=1 always.
See vpar_dompoly() for counts of each possible size dominating set.");
}
vpar_dompoly(vpar,var='x) =
{
\\ with[v] is sets with v included
\\ without[v] is sets without v, either v dominated or not dominated
\\ without_dom[v] is sets without v but v dominated
my(seq=vpar_INTERNAL_seq_upwards(vpar),
with = vector(#vpar,v,var), \\ 1 set size 1
without = vector(#vpar,v,1), \\ 1 set size 0 (undominated)
without_dom = vector(#vpar), \\ 0 sets
ret = var/var); \\ 1 as poly
for(i=1,#seq,
my(v=seq[i],
p=vpar[v],
v_dom = with[v] + without_dom[v]);
if(p,
with[p] *= with[v] + without[v];
without_dom[p] = without_dom[v]*without_dom[p] + with[v]*without[p];
without[p] *= v_dom;
,
ret *= v_dom); \\ at root
with[v] = \\ kill intermediate vectors
without[v] =
without_dom[v] = 0);
ret;
}
{
addhelp(vpar_dompoly,
"poly = vpar_dompoly(vpar,var='x)
Return the domination polynomial of vpar.
Each term c*x^n is count c of how many dominating sets of size n in vpar.
Optional parameter \"var\" can be the polynomial variable to use (a symbol),
or default 'x.
Specific terms include
polcoeff(poly,0) == (#vpar==0)
polcoeff(poly,#vpar) == 1
polcoeff(poly,#vpar-1) == #vpar - vpar_num_singletons(vpar)
valuation(poly,'x) == vpar_domnum(vpar)
polcoeff(poly,valuation(poly,'x)) == vpar_domnum_and_count(vpar)[2]
subst(poly,'x,1) == vpar_domsets_count(vpar)
The set of all vertices is always dominating so high term 1*x^#vpar.
This includes empty tree vpar=[] dominated by the empty set.
Any non-empty tree is dominated only by a non-empty set.
Sets of all except one vertex are dominating except on singletons.
The smallest n term is the domination number and its coefficient is the
count of such sets.
vpar can be a forest, in which case the effect is polynomial product
pol1*pol2*... of the domination polynomials of the component trees.");
}
\\ Return domnum of an n-path.
\\ Used for n>=1, but is right for n==0 too.
vpar_INTERNAL_domnum_of_path(n) = (n+2)\3;
\\ vpar_domnum() is in the style of
\\
\\ Cockayne, Goodman, and Hedetniemi, "A Linear Algorithm for the
\\ Domination Number of a Tree", Information Processing Letters,
\\ volume 4, number 2, November 1975, pages 41-44.
\\
\\ At an undominated leaf, take its attachment parent and reckon that leaf
\\ deleted from the tree. Or if no parent (singleton) then take the vertex
\\ itself. "Leaf" in the code here means a childless vertex. A root of
\\ degree 1 is left until last.
\\
\\ mtype = 0=undecided, 1=dominated, 2=included
\\
\\ vpar_INTERNAL_domnum_table[] encodes which combinations of which "mtype"
\\ child,parent go to which new mtype in the parent and increment domnum or
\\ not. See test.gp for tests as explicit code, and making the table.
\\
\\ At the top of the tree mtype==0 is undominated and take that vertex into
\\ the set so increment domnum.
\\
\\ A variation in
\\
\\ Mitchell, Cockayne, Hedetniemi, "Linear Algorithms on Recursive
\\ Representations of Trees", Journal of Computer and System Sciences,
\\ volume 18, 1979, pages 76-85.
\\
\\ goes by canonical sequence vertices.
\\
vpar_INTERNAL_domnum_table = {Vecsmall([
5, 0, 2, \\ p state 0 = undom
5, 2, 2, \\ p state 1 = dom
4, 4, 4, \\ p state 2 = with
1, 0, 0 \\ no p, v is root
\\ --------
\\ v=0 1 2 as new child under
])};
vpar_domnum(vpar) = vpar_INTERNAL_xxxnum(vpar,vpar_INTERNAL_domnum_table);
{
addhelp(vpar_domnum,
"num = vpar_domnum(vpar)
Return the domination number of vpar. This is the number of vertices in the
smallest dominating set of vpar (vpar_is_domset()).
vpar can be a forest, in which case the effect is sum domination numbers of
its component trees.");
}
vpar_domnum_and_count(vpar) =
{
my(seq=vpar_INTERNAL_seq_upwards(vpar),
none = #vpar+1,
with = vectorsmall(#vpar,v,1),
without = vectorsmall(#vpar),
without_dom = vectorsmall(#vpar,v,none),
with_count = vector(#vpar,v,1),
without_count = with_count,
without_dom_count = vector(#vpar),
ret = 0,
ret_count = 1);
for(i=1,#seq,
my(v=seq[i],
p=vpar[v],
v_dom =with[v], \\ v_dom = min(v_with,v_without_dom)
v_dom_count=with_count[v]);
if(without_dom[v] < v_dom,
v_dom=without_dom[v]; v_dom_count=without_dom_count[v],
without_dom[v] == v_dom,
v_dom_count+=without_dom_count[v]);
if(p,
my(v_any =with[v], \\ v_any = min(v_with,v_without_any)
v_any_count=with_count[v]);
if(without[v]=without_dom[p],
without_dom_count[p]*=without_dom_count[v]);
if (prospective= 1.
vpar can be a forest, in which case the effect is sum domination numbers and
product counts of its component trees.");
}
vpar_domnum_sets(vpar) =
{
my(seq=vpar_INTERNAL_seq_upwards(vpar),
with = vector(#vpar,v, [ [v] ]),
without = vector(#vpar,v, [ [] ]),
without_dom = vector(#vpar,v, [ ]), \\ no sets
ret = [ [] ]);
for(i=1,#seq,
my(v=seq[i],
p=vpar[v],
v_dom = vpar_INTERNAL_sets_minmax(with[v], without_dom[v], min));
\\ print(" v="v" p="p" with "with[v]" without "without[v]" without_dom "without_dom[v]);
\\ print(" v_dom "v_dom);
if(p,
\\ with[p] = with[p] * min(with[v],without[v])
with[p] = vpar_INTERNAL_setproduct(with[p],
vpar_INTERNAL_sets_minmax(with[v], without[v], min));
\\ print(" to with[p] "with[p]);
without_dom[p]
= vpar_INTERNAL_sets_product_min(without_dom[p], without_dom[v],
without[p], with[v]);
\\ print(" to without_dom[p] "without_dom[p]);
without[p] = vpar_INTERNAL_setproduct(without[p],v_dom);
,
ret = vpar_INTERNAL_setproduct(ret,v_dom)); \\ at root
with[v] = \\ kill intermediate sets
without[v] =
without_dom[v] = 0);
ret;
}
{
addhelp(vpar_domnum_sets,
"vec = vpar_domnum_sets(vpar)
Return a vector of all minimum dominating sets in vpar.
Each vec[i] in the return is a Set() of vertex numbers which are a
dominating set of the minimum size possible in vpar.
Each set size in vec is vpar_domnum(vpar).
The number of sets is count #vec == vpar_domnum_and_count(vpar)[2].
The order of the sets in vec is unspecified.");
}
\\------------------------------------------------------------------------------
\\ Minimal Dominating Sets
vpar_is_minimal_domset(vpar,set) =
{
\\ dominated[v] is the number of dominations of v, ie. the number of
\\ neighbours of v (either parent or child) which are in "set".
\\
\\ When v is in the set, special big b = #vpar+1 is added to dominated[v].
\\ This is used to identify v in the set (instead of setsearch()). Any
\\ neighbours in the set increment too so dominated[v]>=b means v in the set,
\\ and dominated[v]==b means v in the set and nothing else dominating v.
\\
\\ v with itself dominated[v]==b or parent dominated[p]==1 are sole
\\ dominations of those so that v is minimal. That is noted in
\\ dominated[v] by adding a yet further "b". "count" is how many in set
\\ are minimal so far.
\\ Any children of v with dominated[c]==1 likewise is a sole domination by
\\ v so v is minimal. If v not already marked as minimal then it adds to
\\ "count". Reaching count==#set is all minimal so return 1.
\\ print("vpar_is_minimal_domset() "vpar" set "set);
my(dominated=vectorsmall(#vpar),
num_children=vectorsmall(#vpar),
b=#vpar+1,
p);
for(i=1,#set,
my(v=set[i]);
dominated[v] += b; \\ v in set[] dom self
if((p=vpar[v]) && p!=v, \\ have parent, and not self-loop
dominated[p]++)); \\ v in set[] dom parent
\\ children dom by parent p in set[]
for(v=1,#vpar,
if((p=vpar[v]) \\ have parent
&& p!=v \\ not self-loop
&& vpar[p]!=v \\ not 2-cycle already dom parent above
,
num_children[p]++;
if(dominated[p]>=b, \\ p is in set[]
dominated[v]++)));
\\ print(" dominated = "Vec(dominated));
\\ any dominated[v]==0 means not a domset
for(v=1,#vpar,
dominated[v] || return(0));
\\ count is how many vertices are established as minimal
\\ (cannot be removed or would be not a domset)
\\ look first at v in the set and possible parent
my(count=sum(i=1,#set,
my(v=set[i]);
(dominated[v]==b \\ v dom only by itself
|| ((p=vpar[v]) \\ or v has parent p
&& dominated[p]==1)) \\ and p dom only by v
&&
(dominated[v] += b))); \\ double flag as counted
\\ print(" count "count" vs set size "#set" "Vec(dominated));
if(count==#set,
return(1)); \\ all vertices minimal
my(m = b<<1);
for(v=1,#vpar,
if(dominated[v]==1 \\ v dominated only 1
&& (p=vpar[v]) \\ by parent p
&& dominated[p]>=b \\ and p is in the set
&& dominated[p]=2 && !is_min[v], return(0)));
1;
}
{
addhelp(vpar_is_minimal_domset_flags,
"bool = vpar_is_minimal_domset_flags(vpar,flags)
flags is a vector length #vpar of 0,1 booleans giving a set of vertices.
Return 1 if this is a minimal dominating set in vpar or return 0 if not.
This means flags are a dominating set (per vpar_is_domset_flags(vpar,flags))
and also that the set is minimal in the sense that no vertex can be removed
from the set and still be a dominating set. vpar can contain cycles.
The empty tree vpar=[] is reckoned dominated by the empty set flags=[0...0],
like vpar_is_domset_flags() does, and it is reckoned minimal in that nothing
can be removed from it.");
}
\\ Dependencies:
\\ ret: v_with_min = with_min_req + with_min_notreq
\\
\\ with_min[v] = with_min[p] * without_dom_notsole[v]
\\ + with_min[p] * without_undom[v]
\\ + with_min_req[p] * with_min_req[v]
\\ + with_notmin_notreq[p] + without_undom[v]
\\
\\ = with_min[p] * without_dom_notsole[v]
\\ + with_min_req[p] * with_min_req[v]
\\ + p_with * without_undom[v]
\\
\\ with_min_req[p] = with_min_req[p] * [with_min_req[v],
\\ without_dom_notsole[v],
\\ without_undom[v]]
\\ + with_min_notreq[p] * without_undom[v]
\\ + with_notmin_notreq[p]) * without_undom[v]
\\
\\ = with_min[p] * without_undom[v]
\\ + with_min_req[p] * [with_min_req[v],
\\ without_dom_notsole[v]]
\\ + with_notmin_notreq[p]) * without_undom[v]
\\
\\ with_notmin_notreq[p] = with_notmin_notreq[p] * (with_min_req[v],
\\ + without_dom_notsole[v])
\\ + with_min_notreq[p] * with_min_req[v]
\\
\\ my(old_p_with_min = concat(with_min_req[p], with_min_notreq[p]),
\\ v_without_notsole = concat(without_dom_notsole[v], without_undom[v]),
\\ new_p_with_min
\\ = concat([vpar_INTERNAL_setproduct(old_p_with_min,
\\ v_without_notsole),
\\ vpar_INTERNAL_setproduct(with_min_req[p],
\\ with_min_req[v]),
\\ vpar_INTERNAL_setproduct(with_notmin_notreq[p],
\\ without_undom[v])]));
\\ print(" new_p_with_min extend "old_p_with_min" + "v_without_notsole);
\\ print(" build "with_notmin_notreq[p]);
\\
\\ if(vecsort(new_p_with_min) != vecsort(concat(with_min_req[p], with_min_notreq[p])),
\\ print("new "vecsort(new_p_with_min));
\\ print("conc "vecsort(concat(with_min_req[p], with_min_notreq[p])));
\\ error());
\\
vpar_minimal_domsets(vpar) =
{
\\ print("vpar_minimal_domsets() "vpar);
my(seq=vpar_INTERNAL_seq_upwards(vpar),
with_min_req = vector(#vpar,v,[ ]), \\ none
with_min_notreq = vector(#vpar,v,[ [v] ]), \\ 1 set size 1
with_notmin_notreq = with_min_req, \\ none
without_dom_sole = with_min_req, \\ none
without_dom_notsole = with_min_req, \\ none
without_undom = vector(#vpar,v,[ [] ]), \\ empty set
ret = [ [] ]);
for(i=1,#seq,
my(v=seq[i],
p=vpar[v],
v_with_min = concat(with_min_req[v], with_min_notreq[v]),
v_without_dom = concat(without_dom_sole[v], without_dom_notsole[v]),
v_mindom = concat(v_with_min, v_without_dom));
\\ print(" v="v" p="p" with_min_req "with_min_req[v]);
\\ print(" with_min_notreq "with_min_notreq[v]);
\\ print(" with_notmin_notreq "with_notmin_notreq[v]);
\\ print(" without_dom_sole "without_dom_sole[v]);
\\ print(" without_dom_notsole "without_dom_notsole[v]);
\\ print(" without_undom "without_undom[v]);
\\ print(" v_with_min "v_with_min);
\\ setintersect(Set(with_min_req[v]),Set(with_min_notreq[v]))
\\ == [] || error();
\\ setintersect(Set(with_min_req[v]),Set(with_notmin_notreq[v]))
\\ == [] || error();
\\ setintersect(Set(with_min_notreq[v]),Set(with_notmin_notreq[v]))
\\ == [] || error();
\\ setintersect(Set(without_dom_sole[v]),Set(without_dom_notsole[v]))
\\ == [] || error();
\\ setintersect(Set(without_dom_sole[v]),Set(without_undom[v]))
\\ == [] || error();
\\ setintersect(Set(without_dom_notsole[v]),Set(without_undom[v]))
\\ == [] || error();
if(p,
with_min_req[p]
= concat(vpar_INTERNAL_setproduct(with_min_req[p],
concat([with_min_req[v],
without_dom_notsole[v],
without_undom[v]])),
vpar_INTERNAL_setproduct(concat(with_min_notreq[p],
with_notmin_notreq[p]),
without_undom[v]));
\\ print(" set with_min_req[p] "with_min_req[p]);
\\---
\\ print(" with_notmin_notreq[p] extend "with_notmin_notreq[p]" + concat "with_min_req[v]" "without_dom_notsole[v]);
\\ print(" build "with_min_notreq[p]);
with_notmin_notreq[p]
= concat(vpar_INTERNAL_setproduct(with_notmin_notreq[p],
concat(with_min_req[v],
without_dom_notsole[v])),
vpar_INTERNAL_setproduct(with_min_notreq[p], with_min_req[v]));
\\ print(" set with_notmin_notreq[p] "with_notmin_notreq[p]);
\\---
with_min_notreq[p] = vpar_INTERNAL_setproduct(with_min_notreq[p],
without_dom_notsole[v]);
\\ print(" set with_min_notreq[p] "with_min_notreq[p]);
\\ -----
\\ print(" without_dom_sole[p] extend "without_dom_sole[p]" "v_without_dom);
\\ print(" build "without_undom[p]" "with_notmin_notreq[v]);
without_dom_sole[p]
= concat(vpar_INTERNAL_setproduct(without_dom_sole[p], v_without_dom),
vpar_INTERNAL_setproduct(without_undom[p], with_notmin_notreq[v]));
\\ print(" set without_dom_sole[p] "without_dom_sole[p]);
\\---
without_dom_notsole[p]
= concat(vpar_INTERNAL_setproduct(without_dom_notsole[p], v_mindom),
vpar_INTERNAL_setproduct(without_undom[p], v_with_min));
\\ print(" set without_dom_notsole[p] "without_dom_notsole[p]);
\\---
without_undom[p] = vpar_INTERNAL_setproduct(without_undom[p],
v_without_dom);
,
ret = vpar_INTERNAL_setproduct(ret, v_mindom)); \\ at root
with_min_req[v] = \\ kill intermediate sets
with_min_notreq[v] =
with_notmin_notreq[v] =
without_dom_sole[v] =
without_dom_notsole[v] =
without_undom[v] = 0);
ret;
}
{
addhelp(vpar_minimal_domsets,
"vec = vpar_minimal_domsets(vpar)
Return a vector of all minimal dominating sets in vpar.
Each vec[i] returned is a Set() of vertex numbers which are a minimal
dominating set per vpar_is_minimal_domset().
The number of sets is #vec == vpar_minimal_domsets_count(vpar).
Each set is >= vpar_domnum(vpar) many vertices.
The order of the sets in vec is unspecified.");
}
vpar_minimal_domsets_count(vpar) = vpar_minimal_dompoly(vpar,1);
{
addhelp(vpar_minimal_domsets_count,
"count = vpar_minimal_domsets_count(vpar)
Return the number of minimal dominating sets in vpar.
vpar can be a forest, in which case the effect is product of counts of its
component trees.
There is always at least one minimal dominating set so count>=1 always.
This includes empty vpar=[] having count=1 which is the empty set as a
minimal dominating set there.
See vpar_minimal_dompoly() for counts of each possible size set.");
}
vpar_minimal_dompoly(vpar,var='x) =
{
\\ See long comments in nautyextra.c (or perhaps moving to a vpar.c).
\\ print("vpar_minimal_dompoly() vpar="vpar);
my(seq=vpar_INTERNAL_seq_upwards(vpar),
with = vector(#vpar,v,var), \\ 1 set size 1
with_notreq = with, \\ 1 set size 1
with_min_notreq = with, \\ 1 set size 1
without_dom_sole = vector(#vpar), \\ 0 sets
without_notsole = vector(#vpar,v,1), \\ 1 set size 0 (undominated)
without_undom = without_notsole, \\ 1 set size 0 (undominated)
ret = var/var); \\ 1 as poly
for(i=1,#seq,
my(v=seq[i],
p=vpar[v],
v_with_notmin_notreq = with_notreq[v] - with_min_notreq[v],
v_without_dom_notsole = without_notsole[v] - without_undom[v],
v_without_dom = without_dom_sole[v] + v_without_dom_notsole,
v_mindom = with[v] - v_with_notmin_notreq \\ with_min
+ v_without_dom);
\\ print("v="v);
\\ print(" with ",with[v]);
\\ print(" with_notreq ",with_notreq[v]);
\\ print(" with_min_notreq ",with_min_notreq[v]);
\\ print(" without_dom_sole ",without_dom_sole[v]);
\\ print(" without_notsole ",without_notsole[v]);
\\ print(" without_undom ",without_undom[v]);
\\ print(" v_with_req ",with[v] - with_notreq[v]);
\\ print(" v_with_notmin_notreq ",v_with_notmin_notreq);
\\ print(" v_with_min ",with[v] - v_with_notmin_notreq);
\\ print(" v_mindom ",v_mindom);
if(p,
my(v_with_req = with[v] - with_notreq[v]);
with[p] *= (v_with_req + without_notsole[v] );
with_notreq[p] *= (v_with_req + v_without_dom_notsole );
with_min_notreq[p] *= ( v_without_dom_notsole );
without_dom_sole[p] = (without_dom_sole[p] * v_without_dom
+ without_undom[p] * v_with_notmin_notreq);
without_notsole[p] *= v_mindom;
without_undom[p] *= v_without_dom;
,
ret *= v_mindom); \\ at root
with[v] = \\ kill intermediate vectors
with_notreq[v] =
with_min_notreq[v] =
without_dom_sole[v] =
without_notsole[v] =
without_undom[v] = 0);
ret;
}
{
addhelp(vpar_minimal_dompoly,
"poly = vpar_minimal_dompoly(vpar,var='x)
Return the minimal domination polynomial of vpar.
Each term c*x^n is count c of how many minimal dominating sets of size n are
in vpar. Optional parameter \"var\" can be the polynomial variable to use
(a symbol), or default 'x.
Specific terms etc include
valuation(poly,'x) == vpar_domnum(vpar)
polcoeff(poly,valuation(poly,'x)) == vpar_domnum_and_count(vpar)[2]
subst(poly,'x,1) == vpar_minimal_domsets_count(vpar)
The smallest n non-zero term is the domination number vpar_domnum() and its
coefficient is the count of such sets.
vpar can be a forest, in which case the effect is polynomial product
pol1*pol2*... of the minimal domination polynomials of the component
trees.");
}
\\-----------------------------------------------------------------------------
\\ Total Dominating Sets, Total Domination Number
\\ ENHANCE-ME: Is a self-loop total-dominating of itself?
\\ Think probably yes.
\\ A300738(1)=0 would be no.
\\ A302653(1)=1 would be yes.
\\ Then self-loop and 2-cycle both semitotdom of self?
\\ But path-2 step to and then back not semitotdom of self
\\ because re-traverses an edge.
vpar_is_totdomset(vpar,set) =
{
my(dominated=vectorsmall(#vpar), p);
for(i=1,#set,
if((p=vpar[set[i]]), \\ parent p
dominated[p]=1)); \\ dominated by v from set
for(v=1,#vpar,
if((p=vpar[v]),
if(setsearch(set,p), \\ parent p in set
dominated[v]=1))); \\ dominates child v
hammingweight(dominated) == #vpar; \\ all dominated
}
{
addhelp(vpar_is_totdomset,
"bool = vpar_is_totdomset(vpar,set)
set is a Set() of vertex numbers.
Return 1 if it is a total dominating set in vpar, or 0 if not.
This means each vertex has a neighbour in the set.
The empty tree vpar==[] is reckoned as dominated by the empty set==[].
The difference from ordinary domination is that a vertex in the set does not
dominate itself, but must have a neighbour in the set. This extra
restriction means vpar_is_totdomset(vpar,set) <= vpar_is_domset(vpar,set).");
}
vpar_is_totdomset_flags(vpar,flags) =
{
my(dominated=vectorsmall(#vpar));
for(v=1,#vpar,
my(p);
if((p=vpar[v]),
dominated[p]+=flags[v]; \\ p dominated by v
dominated[v]+=flags[p])); \\ v dominated by p
hammingweight(dominated) == #vpar; \\ all dominated
}
{
addhelp(vpar_is_totdomset_flags,
"bool = vpar_is_totdomset_flags(vpar,flags)
flags is a vector length #vpar of 0,1 booleans giving a set of vertices.
Return 1 if flags is a total dominating set in vpar, or 0 if not.
See vpar_is_totdomset() on total dominating sets.");
}
vpar_totdomsets(vpar) =
{
\\ print("vpar_totdomsets() "vpar);
my(seq=vpar_INTERNAL_seq_upwards(vpar),
with_dom = vector(#vpar,v,[ ]),
with_undom = vector(#vpar,v,[ [v] ]),
without_dom = with_dom,
without_undom = vector(#vpar,v,[ [] ]),
ret = [ [] ]);
for(i=1,#seq,
my(v=seq[i],
p=vpar[v],
v_dom = concat(with_dom[v], without_dom[v]));
\\ print(" v="v" with_dom "with_dom);
\\ print(" with_undom "with_undom);
\\ print(" v_dom "v_dom);
if(p,
without_dom[p]
= concat(vpar_INTERNAL_setproduct(without_dom[p], v_dom),
vpar_INTERNAL_setproduct(without_undom[p], with_dom[v]));
my(v_with = concat(with_dom[v], with_undom[v]),
v_without = concat(without_dom[v], without_undom[v]));
with_dom[p]
= concat(vpar_INTERNAL_setproduct(with_dom[p],
concat(v_with, v_without)), \\ v_any
vpar_INTERNAL_setproduct(with_undom[p], v_with));
with_undom[p] = vpar_INTERNAL_setproduct(with_undom[p],
v_without);
without_undom[p] = vpar_INTERNAL_setproduct(without_undom[p],
without_dom[v]);
,
\\ print(" is root");
#v_dom || return(v_dom); \\ isolated vertex has no totdomsets
ret = vpar_INTERNAL_setproduct(ret,v_dom));
with_dom[v] = \\ kill intermediate sets
with_undom[v] =
without_dom[v] =
without_undom[v] = 0);
\\ print(" ret "ret);
ret;
}
{
addhelp(vpar_totdomsets,
"vec = vpar_totdomsets(vpar)
Return a vector of all total dominating sets in vpar.
Each vec[i] returned is a Set() of vertex numbers which are a total
dominating set per vpar_is_totdomset().
The number of sets is #vec == vpar_totdomsets_count(vpar).
The smallest set has vpar_totdomnum(vpar) many vertices.
The biggest set is all of 1..#vpar.
The order of the sets in vec is unspecified.");
}
vpar_totdomsets_count(vpar) = vpar_totdompoly(vpar,1);
{
addhelp(vpar_totdomsets_count,
"count = vpar_totdomsets_count(vpar)
Return the number of total dominating sets in vpar (vpar_is_totdomset()).
vpar can be a forest, in which case the effect is product of counts of its
component trees.
The empty tree is reckoned total dominated by the empty set so count 1.
Any isolated vertex has no total dominating set so count 0.
When no isolated vertices, the set of all vertices is a total dominating set
so count>=1.
See vpar_totdompoly() for counts of each possible size set.");
}
vpar_totdompoly(vpar,var='x) =
{
\\ with[v] is sets with v included, either v dominated or not dominated
\\ without[v] is sets with v included, either v dominated or not dominated
\\ "_undom[v]" is the respective sets which are v not dominated
my(seq=vpar_INTERNAL_seq_upwards(vpar),
with = vector(#vpar,v,var), \\ 1 set size 1, undominated
with_undom = with,
without = vector(#vpar,v,1), \\ 1 set size 0, undominated
without_undom = without,
ret = var/var); \\ 1 as poly
for(i=1,#seq,
my(v=seq[i], p=vpar[v],
v_with_dom = with[v] - with_undom[v],
v_without_dom = without[v] - without_undom[v],
v_dom = v_with_dom + v_without_dom);
if(p,
with[p] *= with[v] + without[v];
with_undom[p] *= without[v];
without[p] *= v_dom;
without_undom[p] *= v_without_dom;
,
ret *= v_dom);
with[v] = \\ kill intermediate polys
with_undom[v] =
without[v] =
without_undom[v] = 0);
ret;
}
{
addhelp(vpar_totdompoly,
"poly = vpar_totdompoly(vpar,var='x)
Return the total domination polynomial of vpar.
Each term c*x^n is count c of how many total dominating sets of size n in vpar.
See vpar_is_totdomset() on what comprises such a set.
Optional parameter \"var\" can be the polynomial variable to use (a symbol),
or default 'x.
Properties of the polynomial include
(poly!=0) == (vpar_num_singletons(vpar)==0)
subst(poly,'x,1) == vpar_totdomsets_count(vpar)
and when no singletons
polcoeff(poly,#vpar) == 1
valuation(poly,'x) == vpar_totdomnum(vpar)
polcoeff(poly,valuation(poly,'x)) == vpar_totdomnum_and_count(vpar)[2]
If vpar contains an isolated vertex then has no total dominating sets and
the return is 0 for no sets of any size.
The empty tree is reckoned total dominated by the empty set so polynomial 1.
When no isolated vertex, the set of all vertices is total dominating so high
term x^#vpar. And the smallest n term is the total domination number and
its coefficient is the count of such sets.
vpar can be a forest, in which case the effect is a polynomial product
pol1*pol2*... of the total domination polynomials of the component trees.");
}
\\ See test.gp for totdomnum state table construction and checking.
\\ Values in the table are 2*new_state + increment.
\\ new_state is 0 to 4. Each state[v] value likewise 0 to 4.
\\ increment is 0 or 1 to add to the totdomnum so far.
\\ At the root add increment from the first row too.
\\ At the root if state=0 then it's a singleton so no total domination.
\\
vpar_INTERNAL_totdomnum_table = {Vecsmall([
3, 5, 6, 8, 3, \\ p=1 row
2, 4, 4, 2, 2, \\ p=2
4, 4, 4, 4, 4, \\ p=3
5, 5, 6, 6, 5, \\ p=4
3, 5, 6, 8, 3 \\ p=5
\\ --------------
\\ v=1 2 3 4 5 as new child under
])};
vpar_totdomnum(vpar) =
{
my(seq=vpar_INTERNAL_seq_upwards(vpar),
state=vectorsmall(#vpar),
ret=0);
for(i=1,#seq,
my(v=seq[i], p=vpar[v],
t=vpar_INTERNAL_totdomnum_table[state[v] + if(p, 5*state[p]) + 1]);
if(p, state[p] = t>>1,
!state[v], return(-1));
ret += bitand(t,1));
ret;
}
{
addhelp(vpar_totdomnum,
"num = vpar_totdomnum(vpar)
Return the total domination number of vpar.
This is the number of vertices in the smallest total dominating set of vpar
(vpar_is_totdomset()).
vpar can be a forest, in which case the return is sum of total domination
number of its component trees.
If vpar contains a singleton then it cannot be total dominated by any set
and the return is -1.");
}
vpar_totdomnum_and_count(vpar) =
{
\\ print("vpar_totdomnum_and_count() "vpar);
my(seq=vpar_INTERNAL_seq_upwards(vpar),
none = #vpar+1,
with_dom = vectorsmall(#vpar,v,none), \\ none
with_dom_count = vector(#vpar), \\ [0]
with_undom = vectorsmall(#vpar,v,1), \\ 1
with_undom_count = vector(#vpar,v,1), \\ [1]
without_dom = with_dom, \\ none
without_dom_count = with_dom_count, \\ [0]
without_undom = vectorsmall(#vpar), \\ 0
without_undom_count = with_undom_count, \\ [1]
ret = 0,
ret_count = 1);
for(i=1,#seq,
my(v=seq[i],
p=vpar[v],
v_dom = min(with_dom[v], without_dom[v]),
v_dom_count = if(v_dom==with_dom[v], with_dom_count[v])
+ if(v_dom==without_dom[v], without_dom_count[v]));
\\ print(" v="v" with "with_dom[v]"["with_dom_count[v]"] "with_undom[v]"["with_undom_count[v]"] "" without "without_dom[v]"["without_dom_count[v]"] "without_undom[v]"["without_undom_count[v]"] ");
\\ print(" v_dom "v_dom"["v_dom_count"]");
if(p,
my(v_without = min(without_dom[v], without_undom[v]),
v_without_count = if(v_without==without_dom[v], without_dom_count[v])
+ if(v_without==without_undom[v],without_undom_count[v]));
\\ without_dom[p] = min(without_dom[p] + v_dom,
\\ without_undom[p] + with_dom[v])
my(extend = without_dom[p] + v_dom,
prospective = without_undom[p] + with_dom[v]);
\\ print(" to without_dom extend "extend" prospective "prospective);
without_dom[p] = min(extend,prospective);
without_dom_count[p]
= if(without_dom[p]==extend, without_dom_count[p] * v_dom_count)
+ if(without_dom[p]==prospective,
without_undom_count[p] * with_dom_count[v]);
\\ print(" set without_dom[p] "without_dom[p]"["without_dom_count[p]"]");
\\ with_dom[p] = min(with_dom[p] + v_any,
\\ with_undom[p] + v_with)
my(v_with = min(with_dom[v], with_undom[v]),
v_with_count = if(v_with==with_dom[v], with_dom_count[v])
+ if(v_with==with_undom[v], with_undom_count[v]),
v_any = min(v_without, v_with));
extend = with_dom[p] + v_any;
prospective = with_undom[p] + v_with;
\\ print(" to with_dom extend "extend" prospective "prospective" v_with "v_with"["v_with_count"] v_any "v_any);
with_dom[p] = min(extend,prospective);
with_dom_count[p]
= if(with_dom[p]==extend,
with_dom_count[p]
* (if(v_any==v_with, v_with_count)
+ if(v_any==v_without, v_without_count)))
+ if(with_dom[p]==prospective,
with_undom_count[p] * v_with_count);
\\ print(" set with_dom[p] "with_dom[p]"["with_dom_count[p]"]");
with_undom[p] += v_without;
with_undom_count[p] *= v_without_count;
\\ min(...,none) to avoid overflow Vecsmall if many without_dom[v]==none
without_undom[p] = min(without_undom[p] + without_dom[v],
none);
without_undom_count[p] *= without_dom_count[v];
\\ print(" to p with "with_dom[p]"["with_dom_count[p]"] "with_undom[p]"["with_undom_count[p]"] "" without "without_dom[p]"["without_dom_count[p]"] "without_undom[p]"["without_undom_count[p]"] ");
,
if(v_dom_count,
ret += v_dom; ret_count *= v_dom_count,
return([-1,0]))); \\ isolated vertex, cannot totdom
with_dom_count[v] = \\ kill bignum intermediate values
with_undom_count[v] =
without_dom_count[v] =
without_undom_count[v] = 0);
[ret,ret_count];
}
{
addhelp(vpar_totdomnum_and_count,
"[num,count] = vpar_totdomnum_and_count(vpar)
Return a vector of the total domination number of vpar and count of how many
total dominating sets of that size. The total domination number returned is
the same as vpar_totdomnum(vpar).
vpar can be a forest, in which case the return is sum of total domination
numbers and product of counts of its component trees.
If vpar contains an isolated vertex then it cannot be total dominated by any
set and the return is totdomnum -1 count 0.");
}
vpar_totdomnum_sets(vpar) =
{
\\ print("vpar_totdomnum_sets() "vpar);
my(seq=vpar_INTERNAL_seq_upwards(vpar),
with_dom = vector(#vpar,v,[ ]),
with_undom = vector(#vpar,v,[ [v] ]),
without_dom = with_dom,
without_undom = vector(#vpar,v,[ [] ]),
none = #vpar+1,
ret = [ [] ]);
for(i=1,#seq,
my(v=seq[i],
p=vpar[v],
v_dom = vpar_INTERNAL_sets_minmax(with_dom[v], without_dom[v], min));
\\ print(" v="v" with_dom "with_dom[v]" with_undom[v] "with_undom[v]);
\\ print(" without_dom "without_dom[v]" without_undom[v] "without_undom[v]);
\\ print(" v_dom "v_dom);
\\ if(#without_dom[v] && #without_undom[v],
\\ #without_dom[v][1] >= #without_undom[v][1] || error());
\\ if(#without_undom[v] && #with_undom[v],
\\ #without_undom[v][1] <= #with_undom[v][1] || error());
\\ if(#without_dom[v] && #with_dom[v],
\\ #without_dom[v][1] <= #with_dom[v][1] || error());
if(p,
my(v_without=vpar_INTERNAL_sets_minmax(without_dom[v],
without_undom[v], min));
\\ print(" v_with "v_with" v_without "v_without);
\\ without_dom[p] = min(without_dom[p] + without_dom[v], \ v_dom
\\ without_dom[p] + with_dom[v]) /
\\ without_undom[p] + with_dom[v])
without_dom[p]
= vpar_INTERNAL_sets_product_min(without_dom[p], v_dom,
without_undom[p], with_dom[v]);
\\ with_dom[p] = min(with_dom[p], v_without, \ v_any
\\ with_dom[p], v_with, /
\\ with_undom[p], v_with
my(v_with_dom_size = if(#with_dom[v], #with_dom[v][1], none),
v_with_size = min(v_with_dom_size,
if(#with_undom[v], #with_undom[v][1], none)),
v_any_size = min(v_with_size,
if(#v_without, #v_without[1], none)));
my(extend =if(#with_dom[p], #with_dom[p][1] + v_any_size, none),
prospective=if(#with_undom[p],#with_undom[p][1]+ v_with_size, none));
\\ print(" with_dom v_any_size "v_any_size" extend "extend" prospective "prospective);
if(extend <= prospective,
my(v_any = v_without);
if(v_any_size==v_with_dom_size,
v_any = vpar_INTERNAL_sets_minmax(v_any, with_dom[v], min));
with_dom[p] = vpar_INTERNAL_setproduct(with_dom[p],
vpar_INTERNAL_sets_minmax(v_any,
with_undom[v], min)));
if(prospective <= extend,
my(v_with = vpar_INTERNAL_sets_minmax(with_dom[v],
with_undom[v], min));
if(prospective == extend,
with_dom[p] = concat(with_dom[p],
vpar_INTERNAL_setproduct(with_undom[p],v_with)),
with_dom[p] = vpar_INTERNAL_setproduct(with_undom[p],v_with)));
\\ print(" set with_dom[p] = "with_dom[p]);
with_undom[p] = vpar_INTERNAL_setproduct(with_undom[p],
v_without);
without_undom[p] = vpar_INTERNAL_setproduct(without_undom[p],
without_dom[v]);
,
\\ print(" is root #v_dom=",#v_dom);
if(#v_dom,
ret = vpar_INTERNAL_setproduct(ret,v_dom),
return([]))); \\ isolated vertex, cannot totdom
with_dom[v] = \\ kill intermediate sets
with_undom[v] =
without_dom[v] =
without_undom[v] = 0);
\\ print(" ret "ret);
ret;
}
{
addhelp(vpar_totdomnum_sets,
"vec = vpar_totdomnum_sets(vpar)
Return a vector of all minimum total dominating sets in vpar.
Each vec[i] in the return is a Set() of vertex numbers which are a total
dominating set of the minimum size possible in vpar.
Each set size is #vec[i] == vpar_totdomnum(vpar).
The number of sets is count #vec == vpar_totdomnum_and_count(vpar)[2].
The order of the sets in vec is unspecified.");
}
\\------------------------------------------------------------------------------
\\ Semi-Total Domination
\\
\\ Each in the set dominates its neighbours.
\\ Each in the set must itself be dominated by a neighbour in the set (like
\\ total domination), or by something in the set distance-2 away.
vpar_is_semitotdomset(vpar,set) =
{
\\ print("vpar_is_semitotdomset() "vpar" set "set);
\\ dominated[v] = 1 if v is in set[]
\\ +2 each domination of v
\\ If v not in set then dominations are by immediate neighbours in set[].
\\ If v in set then dominations can also be by distance-2 neighbour in set[].
my(dominated=vectorsmall(#vpar), p);
for(i=1,#set,
dominated[set[i]]++); \\ 1 = v in set
\\ g grandparent
\\ |
\\ p
\\ |
\\ v (in set)
for(i=1,#set,
my(v=set[i]);
if((p=vpar[v]),
dominated[p]+=2; \\ +2 = p dominated by v
if((p=vpar[p]) && bitand(dominated[p],1), \\ grandparent
dominated[p]+=2; \\ +2 = g in set dominated by v
dominated[v]+=2) \\ and vice versa
));
for(v=1,#vpar,
if((p=vpar[v]) \\ parent p
&& bitand(dominated[p],1), \\ in set
dominated[v]+=2)); \\ +2 = v dominated by p
\\ p >=4 p dominated >= 2 * +2,
\\ / \ is from v in set and another x child,
\\ v x and that x is distance-2 away from v so ok
\\ print(" dominated "Vec(dominated));
for(v=1,#vpar,
if(dominated[v]<2 \\ v undominated
&& !(bitand(dominated[v],1) \\ v in set
&& (p=vpar[v]) \\ has parent p
&& dominated[p]>=4 ), \\ and p>=4, is ok
return(0)));
1;
}
{
addhelp(vpar_is_semitotdomset,
"bool = vpar_is_semitotdomset(vpar,set)
set is a Set() of vertex numbers.
Return 1 if it is a semi-total dominating set in vpar, or return 0 if not.
This means each vertex must have a neighbour in the set, or those in the set
allowed to have another in the set at distance 2.
Semi-total domination relaxes total domination by allowing a vertex in the
set to be dominated by distance 2 away as well as by an immediate neighbour.
Dominating, semi-total dominating and total dominating are increasingly
restricted so
vpar_is_domset(vpar,set)
>= vpar_is_semitotdomset(vpar,set)
>= vpar_is_totdomset(vpar,set)
The empty tree vpar==[] is reckoned as dominated by the empty set flags==[].
A singleton has no adjacent vertex so is never semi-total dominated.
vpar can contain cycles. A self-loop vertex dominates itself (the same as
in total domination). Distance 2 means two different edges traversed to
reach another in the set, so a vertex in a 2-cycle dominates itself.");
}
vpar_is_semitotdomset_flags(vpar,flags) =
{
\\ If v in the set (flags[v]==1) is a self-loop, then p to v and v to p
\\ result in dominated[v]==2 instead of just 1 domination (by itself).
\\ That doesn't affect the later domianted[p]>=2 etc as those are only on
\\ p not in the set.
\\
\\ If v in the set and u not are a 2-cycle, then dominated[u]=2 due to u
\\ parent v and v parent u. This is correct for the distance 2 test since
\\ v to v is a distance 2 around the cycle so it dominates itself.
my(dominated=vectorsmall(#vpar), p);
for(v=1,#vpar,
if((p=vpar[v]), \\ across each edge
dominated[p]+=flags[v]; \\ p dominated by v
dominated[v]+=flags[p]; \\ v dominated by p
));
\\ Across each edge, v in set and p not in the set but p having 2
\\ dominations means p has another neighbour in the set in addition to v,
\\ and so a distance-2 to v.
for(v=1,#vpar,
if((p=vpar[v]),
if(flags[v] && !flags[p] && dominated[p]>=2, dominated[v]=1);
if(flags[p] && !flags[v] && dominated[v]>=2, dominated[p]=1);
));
hammingweight(dominated) == #vpar; \\ none undominated, all dominated
}
{
addhelp(vpar_is_semitotdomset_flags,
"bool = vpar_is_semitotdomset_flags(vpar,flags)
flags is a vector length #vpar of 0,1 booleans giving a set of vertices.
Return 1 if flags is a semi-total dominating set in vpar, or 0 if not.
See vpar_is_semitotdomset() for description of semi-total domination.
vpar can contain cycles.");
}
vpar_semitotdomnum(vpar) =
{
\\ with_dom[v] = size of the smallest set with v and v dominated
\\ with_undom[v] = size of the smallest set with v and v undominated
\\ without_dom_child_dom[v] = size of the smallest set without v and v
\\ dominated by a child in the set and that child is dominated
\\ without_dom_child_undom[v] = size of the smallest set without v and v
\\ dominated by a child in the set but that child is undominated
\\ without_undom[v] = size of the smallest set without v and v undominated
\\ print("test_semitotdomnum_by_separate() "vpar);
my(seq=vpar_INTERNAL_seq_upwards(vpar),
none = #vpar+100,
with_dom = vectorsmall(#vpar,v,none),
with_undom = vectorsmall(#vpar,v,1),
without_dom_child_dom = vectorsmall(#vpar,v,none),
without_dom_child_undom = vectorsmall(#vpar,v,none),
without_undom = vectorsmall(#vpar,v,0),
ret=0);
for(i=1,#seq,
my(v=seq[i], p=vpar[v]);
\\ print(" v="v" with "with_dom[v]" "with_undom[v]" without "without_dom_child_dom[v]" "without_dom_child_undom[v]" "without_undom[v]);
\\ print(" p="p,if(p,Str(" with "with_dom[p]" "with_undom[p]" without "without_dom_child_dom[p]" "without_dom_child_undom[p]" "without_undom[p]),""));
if(p,
my(v_with = min(with_dom[v], with_undom[v]),
v_without_dom = min(without_dom_child_dom[v], without_dom_child_undom[v]));
\\ p_with, v_with neighbour
\\ p_with, v_without_dom distance 2
\\ with_dom[p], without_undom[v] extend
\\
with_dom[p] = min(min(with_dom[p], with_undom[p])
+ min(v_with, v_without_dom),
with_dom[p] + without_undom[v]);
with_undom[p] += without_undom[v];
without_dom_child_dom[p]
= vecmin([without_dom_child_dom[p] + min(v_with,
without_dom_child_dom[v]),
without_dom_child_undom[p] + v_with,
without_undom[p] + with_dom[v]]);
without_dom_child_undom[p]
= min(without_dom_child_undom[p] + without_dom_child_dom[v],
without_undom[p] + with_undom[v]);
without_undom[p] += without_dom_child_dom[v];
\\ print(" to with "with_dom[p]" "with_undom[p]" without "without_dom_child_dom[p]" "without_dom_child_undom[p]" "without_undom[p]);
,
my(d = min(with_dom[v], without_dom_child_dom[v]));
\\ print(" final d="d);
if(d >= none, return(-1));
ret += d));
\\ print(" ret "ret);
ret;
}
{
addhelp(vpar_semitotdomnum,
"num = vpar_semitotdomnum(vpar)
Return the semi-total domination number of vpar. This is the number of
vertices in the smallest semi-total dominating set of vpar.
See vpar_is_semitotdomset() on such sets.
vpar can be a forest, in which case the effect is sum semi-total domination
numbers of its component trees.
If vpar contains a singleton then it cannot be semi-total dominated by any
set and the return is -1.
Semi-total domination falls in between dominating and total dominating in
its restriction. So, when not -1,
vpar_domnum(vpar) <= vpar_semitotdomnum(vpar) <= vpar_totdomnum(vpar)");
}
vpar_semitotdomnum_and_count(vpar) =
{
\\ print("vpar_semitotdomnum_and_count() "vpar);
\\ ENHANCE-ME: Might be possible to rearrange to better combinations of
\\ quantities.
my(seq=vpar_INTERNAL_seq_upwards(vpar),
none = #vpar+100,
with_dom = vectorsmall(#vpar,v,none), \\ none
with_dom_count = vector(#vpar), \\ 0 sets
with_undom = vectorsmall(#vpar,v,1), \\ size 1
with_undom_count = vector(#vpar,v,1), \\ 1 sets
without_dom_child_dom = with_dom, \\ none
without_dom_child_dom_count = with_dom_count,
without_dom_child_undom = with_dom, \\ none
without_dom_child_undom_count = with_dom_count,
without_undom = vectorsmall(#vpar,v,0), \\ size 0
without_undom_count = with_undom_count, \\ 1 sets
ret = 0,
ret_count = 1);
for(i=1,#seq,
my(v=seq[i], p=vpar[v]);
\\ print(" v="v" with "with_dom[v]"["with_dom_count[v]"] "with_undom[v]"["with_undom_count[v]"] without "without_dom_child_dom[v]"["without_dom_child_dom_count[v]"] "without_dom_child_undom[v]"["without_dom_child_undom_count[v]"] "without_undom[v]"["without_undom_count[v]"]");
\\ print(" p="p,if(p,Str(" with "with_dom[p]"["with_dom_count[p]"] "with_undom[p]"["with_undom_count[p]"] without "without_dom_child_dom[p]" "without_dom_child_undom[p]" "without_undom[p]),""));
if(p,
my(v_with = min(with_dom[v], with_undom[v]),
v_with_count = if(v_with==with_dom[v], with_dom_count[v])
+ if(v_with==with_undom[v], with_undom_count[v]));
\\ print(" v_with "v_with"["v_with_count"]");
my(v_without_dom = min(without_dom_child_dom[v],
without_dom_child_undom[v]),
v_without_dom_count = if(v_without_dom==without_dom_child_dom[v],
without_dom_child_dom_count[v])
+ if(v_without_dom==without_dom_child_undom[v],
without_dom_child_undom_count[v]));
my(v_with_or_without_dom = min(v_with, v_without_dom),
v_with_or_without_dom_count
= if(v_with_or_without_dom==v_with, v_with_count)
+ if(v_with_or_without_dom==v_without_dom, v_without_dom_count));
\\ print(" v_with_or_without_dom "v_with_or_without_dom"["v_with_or_without_dom_count"]");
my(p_with = min(with_dom[p], with_undom[p]),
p_with_count = if(p_with==with_dom[p], with_dom_count[p])
+ if(p_with==with_undom[p], with_undom_count[p]));
\\ print(" p_with "p_with"["p_with_count"]");
\\ with_dom[p] + v_any
\\ with_undom[p] + v_with_or_without_dom not without_undom
\\
my(extend = with_dom[p] + without_undom[v],
prospective = p_with + v_with_or_without_dom);
with_dom[p] = min(extend, prospective);
with_dom_count[p] = if(with_dom[p] == extend,
with_dom_count[p] * without_undom_count[v])
+ if(with_dom[p] == prospective,
p_with_count * v_with_or_without_dom_count);
with_undom[p] = with_undom[p] + without_undom[v];
with_undom_count[p] = with_undom_count[p] * without_undom_count[v];
my(v_with_or_without_dom_child_dom = min(v_with,
without_dom_child_dom[v]),
v_with_or_without_dom_child_dom_count
= if(v_with_or_without_dom_child_dom==v_with,
v_with_count)
+ if(v_with_or_without_dom_child_dom==without_dom_child_dom[v],
without_dom_child_dom_count[v]));
\\ p_without + with_dom[v]
\\ p_without_dom + v_with
\\ without_dom_child_dom[p] + without_dom_child_dom[v]
\\
extend = without_dom_child_dom[p] + v_with_or_without_dom_child_dom;
prospective = without_dom_child_undom[p] + v_with;
my(prospective2 = without_undom[p] + with_dom[v]);
without_dom_child_dom[p] = vecmin([extend, prospective, prospective2]);
without_dom_child_dom_count[p]
= if(without_dom_child_dom[p] == extend,
without_dom_child_dom_count[p] * v_with_or_without_dom_child_dom_count)
+ if(without_dom_child_dom[p] == prospective,
without_dom_child_undom_count[p] * v_with_count)
+ if(without_dom_child_dom[p] == prospective2,
without_undom_count[p] * with_dom_count[v]);
extend = without_dom_child_undom[p] + without_dom_child_dom[v];
prospective = without_undom[p] + with_undom[v];
without_dom_child_undom[p] = min(extend, prospective);
without_dom_child_undom_count[p]
= if(without_dom_child_undom[p] == extend,
without_dom_child_undom_count[p] * without_dom_child_dom_count[v])
+ if(without_dom_child_undom[p] == prospective,
without_undom_count[p] * with_undom_count[v]);
without_undom[p] += without_dom_child_dom[v];
without_undom_count[p] *= without_dom_child_dom_count[v];
\\ print(" to "Str(" with "with_dom[p]"["with_dom_count[p]"] "with_undom[p]"["with_undom_count[p]"] without "without_dom_child_dom[p]" "without_dom_child_undom[p]" "without_undom[p]));
,
my(d = min(with_dom[v], without_dom_child_dom[v]));
\\ print(" at root d="d" ret_count="ret_count);
if(d >= none, return([-1,0]));
ret += d;
ret_count *= if(d==with_dom[v],
with_dom_count[v])
+ if(d==without_dom_child_dom[v],
without_dom_child_dom_count[v]);
\\ print(" to ret_count "ret_count);
));
\\ print(" ret "ret"["ret_count"]");
[ret, ret_count];
}
{
addhelp(vpar_semitotdomnum_and_count,
"[num,count] = vpar_semitotdomnum_and_count(vpar)
Return the semi-total domination number of vpar and count of how many sets
of that size. See vpar_is_semitotdomset() on these sets. num is the same
as vpar_semitotdomnum().
vpar can be a forest, in which case the effect is sum semi-total domination
numbers and product counts of the component trees (except semitotdomnum -1
if any singletons).");
}
\\------------------------------------------------------------------------------
\\ Independent Domination
\\ Could have one loop checking indset and making the domination cover.
\\ Seems good enough to call the separate funcs. vpar_is_indset_flags() is
\\ first since it might find a non-indomset part way through its checking.
\\
vpar_is_indomset(vpar,set) = \
vpar_is_indset(vpar,set) && vpar_is_domset(vpar,set);
{
addhelp(vpar_is_indomset,
"bool = vpar_is_indomset(vpar,set)
set is a Set() of vertex numbers.
Return 1 if it is an independent dominating set (maximal independent set) in
vpar, or return 0 if not. vpar can contain cycles.
This means both independent per vpar_is_indset() and dominating per
vpar_is_domset(). Such a set is a maximal independent set in the sense that
no further vertex can be added and still be independent. (Such a vertex
would have no neighbours in the set, so the set not dominating.)
The empty tree vpar==[] is independent dominated by the empty set==[].");
}
vpar_is_indomset_flags(vpar,flags) = \
vpar_is_indset_flags(vpar,flags) && vpar_is_domset_flags(vpar,flags);
{
addhelp(vpar_is_indomset_flags,
"bool = vpar_is_indomset_flags(vpar,flags)
flags is a vector length #vpar of 0,1 booleans giving a set of vertices.
Return 1 if flags is an independent dominating set (maximal independent set)
in vpar, or 0 if not. vpar can contain cycles.
This means both independent per vpar_is_indset_flags() and dominating per
vpar_is_domset_flags(). See vpar_is_indomset() for further notes.
The empty tree vpar==[] is independent dominated by the empty set flags==[].");
}
vpar_indomsets(vpar) =
{
\\ This code keeps without_dom[] sets rather than the combined without[]
\\ used in vpar_indomsets_count(), since subtracting without-without_undom
\\ is not as easy for vectors.
\\
\\ Set test.gp test_indompoly_by_dom() for code using this without_dom[]
\\ style for indompoly.
my(seq=vpar_INTERNAL_seq_upwards(vpar),
with = vector(#vpar,v,[ [v] ]), \\ 1 set size 1
without_dom = vector(#vpar,v,[ ]), \\ 0 sets
without_undom = vector(#vpar,v,[ [] ]), \\ 1 set size 0 undominated
ret = [ [] ]); \\ 1 empty set
for(i=1,#seq,
my(v=seq[i],
p=vpar[v],
v_domsets = concat(with[v], without_dom[v]));
if(p,
with[p] = vpar_INTERNAL_setproduct(with[p],
concat(without_dom[v], without_undom[v]));
without_dom[p]
= concat(vpar_INTERNAL_setproduct(without_dom[p], v_domsets),
vpar_INTERNAL_setproduct(without_undom[p], with[v]));
without_undom[p] = vpar_INTERNAL_setproduct(without_undom[p],
without_dom[v]);
,
ret = vpar_INTERNAL_setproduct(ret, v_domsets));
with[v] = \\ kill intermediate vectors
without_dom[v] =
without_undom[v] = 0);
ret;
}
{
addhelp(vpar_indomsets,
"vec = vpar_indomsets(vpar)
Return a vector of all independent dominating sets (maximal independent
sets), in vpar. Each vec[i] returned is a Set() of vertex numbers which are
vpar_is_indomset().
The number of sets is #vec == vpar_indomsets_count(vpar).
The order of the sets in vec is unspecified.");
}
vpar_indomsets_count(vpar) = vpar_indompoly(vpar,1);
{
addhelp(vpar_indomsets_count,
"count = vpar_indomsets_count(vpar)
Return the number of independent dominating sets (maximal independent sets)
in vpar.
vpar can be a forest, in which case the effect is product of counts of its
component trees.
The empty tree vpar==[] is reckoned dominated by the empty set so count=1.
For bigger trees an independent dominating set always exists by successive
adjacent vertices present or absent. So count>=1 always.
See vpar_indomsets_count_max() for the largest counts which occur.
See vpar_indompoly() for counts of each possible size set.");
}
vpar_indomsets_count_max(n,forest=0) =
{
if(n,
forest = (forest<=0); \\ 1=tree, 0=forest
(1 << ((n-forest)>>1))
+ bitnegimply(forest,n) \\ +1 when n even for trees
,
1); \\ n==0
\\ (-1 >> 1) == 0 \\ or is it meant to be 2s-complement?
}
{
addhelp(vpar_indomsets_count_max,
"count = vpar_indomsets_count_max(n,forest=0)
Return the largest count of independent dominating sets (maximal independent
sets) which occurs in a tree of n vertices, being the largest possible
vpar_indomsets_count() on a tree. Per Wilf this is
T(n) = if(n==0, 1, \\\\ initial
n%2==0, 2^(n/2-1) + 1, \\\\ even
2^((n-1)/2)); \\\\ odd
= 1,1,2,2,3,4,5,8,9,... (A228693)
Herbert S. Wilf, \"The Number of Maximal Independent Sets in a Tree\",
SIAM Journal on Algebraic and Discrete Methods, volume 7, number 1,
January 1986, pages 125-130.
http://www.math.upenn.edu/~wilf/website/Maximal%20independent%20sets%20in%20a%20tree.pdf
Optional parameter \"forest\" can be 1 for largest count occurring in a
forest of n vertices.
F(n) = 2^floor(n/2) = 1,1,2,2,4,4,8,8,... (A016116)
Forests count is product its component trees count, so max product T(p) over
partitions of n. T(2)=2^(n/2) and other T(k)<2^(k/2), so taking terms 2 is
bigger product than any other combination. For n even, a forest of path-2s
uniquely attains the maximum. For n odd, have all T(n)=2^((n-1)/2) uniquely
attained by a path, so max forest is path-2s and an odd length path.");
}
\\ Wilf also (but broken site demands cookies or nothing at all)
\\ https://doi.org/10.1137/0607015
\\ https://epubs.siam.org/doi/abs/10.1137/0607015
\\ GP-DEFINE {
\\ GP-DEFINE L(n) = if(n==0,1,
\\ GP-DEFINE n%2==0, 2^(n/2-1)+1, \\ even
\\ GP-DEFINE 2^((n-1)/2)); \\ odd
\\ GP-DEFINE }
\\ GP-Test vector(9,n,n--; L(n)) == [1,1,2,2,3,4,5,8,9]
vpar_indompoly(vpar,var='x) =
{
\\ same as vpar_dompoly() but here with[p] is only without[v] to ensure
\\ independent, whereas full dompoly is with[v]+without[v]
my(seq=vpar_INTERNAL_seq_upwards(vpar),
with = vector(#vpar,v,var), \\ 1 set size 1
without = vector(#vpar,v,1), \\ 1 set size 0 (undominated)
without_undom = without, \\ 1 set size 0 (undominated)
ret = var/var); \\ 1 as poly
for(i=1,#seq,
my(v=seq[i],
p=vpar[v],
v_without_dom = without[v] - without_undom[v],
v_dom = with[v] + v_without_dom);
if(p,
with[p] *= without[v];
without[p] *= v_dom;
without_undom[p] *= v_without_dom;
,
ret *= v_dom);
with[v] = \\ kill bignum intermediate values
without[v] =
without_undom[v] = 0);
ret;
}
{
addhelp(vpar_indompoly,
"poly = vpar_indompoly(vpar,var='x)
Return the independent domination polynomial of vpar.
Each term c*x^n is count c of how many independent dominating sets (maximal
independent sets) of size n in vpar.
Optional parameter \"var\" can be the polynomial variable to use (a symbol),
or default 'x.
Properties of the polynomial include
poly != 0 \\\\ always at least one set, even in empty tree
valuation(poly,'x) == vpar_indomnum(vpar)
polcoeff(poly,valuation(poly,'x)) == vpar_indomnum_and_count(vpar)[2]
subst(poly,'x,1) == vpar_indomsets_count(vpar)
The empty tree is reckoned dominated by the empty set so polynomial 1.
For bigger trees an independent dominating set always exists by successive
adjacent vertices present or absent. So poly != 0 always.
The smallest n term is the independent domination number and its coefficient
is count of such sets per vpar_indomnum_and_count(vpar).
vpar can be a forest, in which case the effect is a polynomial product
pol1*pol2*... of the independent domination polynomials of the component
trees.");
}
vpar_indomnum(vpar) =
{
\\ without_dom[p] is either extended by v_dom child, or built from
\\ without_undom[p] (before it is updated) plus a with[v] child to dominate.
\\
\\ without_dom[v] is not possible initially, with just v itself not in the
\\ set. This is represented by size #vpar+1 which subsequent combinations
\\ will better. At a singleton the with[v]=1 will better it in v_dom for
\\ the return.
\\
\\ without_dom[v] == none only occurs at leaf (childless) vertices.
\\ Above there the "build" form for without_dom[p] can take all with[v]
\\ children for some size <= #vpar.
\\
\\ without_undom[v] == none only occurs when all its children are leaves
\\ (childless), so one up from a leaf. And consequently too with[p] is
\\ never none as it is a choice of without_dom or without_undom child.
\\
\\ without_undom[p] adds without_dom[v] children sizes. If
\\ without_dom[v]==none then this sum can overflow Vecsmall on a 32-bit
\\ system. Eg. vpar_indomnum(vpar_make_star(2^16)). The min(...,none) in
\\ the code avoids overflow, though that many leaves would be unlikely in
\\ normal use.
my(seq=vpar_INTERNAL_seq_upwards(vpar),
none = #vpar+1,
with = vectorsmall(#vpar,v,1), \\ v with, size 1
without_dom = vectorsmall(#vpar,v,none),
without_undom = vectorsmall(#vpar), \\ v without, size 0
ret = 0);
for(i=1,#seq,
my(v=seq[i], p=vpar[v],
v_dom = min(without_dom[v], with[v]));
if(p,
without_dom[p] = min(without_dom[p] + v_dom,
without_undom[p] + with[v]);
with[p] += min(without_dom[v], without_undom[v]);
without_undom[p] = min(without_undom[p] + without_dom[v],
none);
,
ret += v_dom)); \\ at root
ret;
}
{
addhelp(vpar_indomnum,
"num = vpar_indomnum(vpar)
Return the independent domination number of vpar.
This is the number of vertices in the smallest independent dominating set of
vpar (meaning a set which is both independent and dominating).
A vertex set is independent dominating if and only if it is a maximal
independent set, so indomnum is also called the lower independence number,
being the size of the smallest maximal independent set.
vpar can be a forest, in which case the effect is sum of independent
domination numbers of its component trees.");
}
vpar_indomnum_and_count(vpar) =
{
my(seq=vpar_INTERNAL_seq_upwards(vpar),
with = vectorsmall(#vpar,v,1), \\ 1
with_count = Vec(with), \\ 1
without_dom = vectorsmall(#vpar), \\ 0
without_dom_count = Vec(without_dom), \\ 0
without_undom = without_dom, \\ 0
without_undom_count = with_count, \\ 1
ret = 0,
ret_count = 1,
p);
for(i=1,#seq,
my(v=seq[i],
p=vpar[v],
v_dom = with[v],
v_dom_count = with_count[v]);
if(without_dom_count[v] && without_dom[v] < v_dom,
v_dom = without_dom[v];
v_dom_count = without_dom_count[v];
,
without_dom[v] == v_dom,
v_dom_count += without_dom_count[v]);
if(p,
\\ v_without = min(without_dom[v], without_undom[v])
my(v_without = without_dom[v],
v_without_count = without_dom_count[v]);
if(without_undom_count[v] && without_undom[v] < v_without,
v_without = without_undom[v];
v_without_count = without_undom_count[v],
without_undom[v] == v_without,
v_without_count += without_undom_count[v]);
\\ v_any = min(v_without, with[v])
my(v_any = v_without,
v_any_count = v_without_count);
if(with[v] < v_any,
v_any = with[v];
v_any_count = with_count[v],
with[v] == v_any,
v_any_count += with_count[v]);
\\ p_without = min(p_without_dom, p_without_undom) calculated
\\ and is also p_without = sum v_dom
\\ before update
my(p_without = without_dom[p],
p_without_count = without_dom_count[p]);
if(without_undom_count[p] && without_undom[p] < p_without,
p_without = without_undom[p];
p_without_count = without_undom_count[p],
without_undom[p] == p_without,
p_without_count += without_undom_count[p]);
with[p] += v_without;
with_count[p] *= v_without_count;
without_undom[p] += without_dom[v];
without_undom_count[p] *= without_dom_count[v];
without_dom[p] +=without_dom[v];
without_dom_count[p]*=without_dom_count[v];
my(prospective =with[v] +p_without,
prospective_count=with_count[v]*p_without_count);
if (prospective=4, \\ parent p multi-dominated
return(0))); \\ is not perfect
\\ dominations each v from parent p in the set
for(v=1,#vpar,
if(bitnegimply(1,seen[v]) \\ v not in set
&& (p=vpar[v]) \\ have parent p
&& bitand(1,seen[p]) \\ parent p in set
&& (seen[v]+=2)>=4, \\ v multi-dominated
return(0))); \\ is not perfect
hammingweight(seen) == #vpar; \\ all dominated
}
{
addhelp(vpar_is_perfect_domset,
"bool = vpar_is_perfect_domset(vpar,set)
set is a Set() of vertex numbers.
Return 1 if it is a perfect dominating set in vpar, or 0 if not.
vpar can contain cycles.
A perfect dominating set is a dominating set (vpar_is_domset()) and any
vertex not in the set is dominated by exactly 1 of the set. Vertices in the
set can be dominated any number of times, but those not in the set must be
dominated just once.
The empty tree vpar=[] is reckoned dominated by the empty set=[] (like
vpar_is_domset() does) and is reckoned perfect.");
}
vpar_is_perfect_domset_flags(vpar,flags) =
{
my(seen=Vecsmall(flags,#vpar), \\ all of flags itself seen
p);
for(v=1,#flags,
if(p=vpar[v],
if( ((seen[p]+=flags[v])>=2 && !flags[p]) \\ p dominated by v
|| ((seen[v]+=flags[p])>=2 && !flags[v]), \\ v dominated by p
return(0)))); \\ multi-dominated vertex not in set
hammingweight(seen) == #vpar; \\ all dominated
}
{
addhelp(vpar_is_perfect_domset_flags,
"bool = vpar_is_perfect_domset_flags(vpar,flags)
flags is a vector length #vpar of 0,1 booleans giving a set of vertices.
Return 1 if this is a perfect dominating set in vpar or return 0 if not.
vpar can contain cycles.
A perfect dominating set is a dominating set (vpar_is_domset_flags()) and
any vertex not in the set is dominated by exactly 1 of the set. Vertices in
the set can be dominated any number of times, but those not in the set must
be dominated just once.
The empty tree vpar=[] is reckoned dominated by the empty set flags=[] (the
same as vpar_is_domset_flags()) and is reckoned perfect.");
}
vpar_perfect_domsets_count(vpar) = vpar_perfect_dompoly(vpar,1);
{
addhelp(vpar_perfect_domsets_count,
"count = vpar_perfect_domsets_count(vpar)
Return the number of perfect dominating sets in vpar.
See vpar_is_perfect_domset_flags() on perfect dominating sets.
vpar can be a forest, in which case the effect is product counts of its
component trees.
The set of all vertices is always a perfect dominating set, including empty
set in an empty tree, so count>=1 always.");
}
vpar_perfect_dompoly(vpar,var='x) =
{
\\ with[v] = poly of sets including v
\\ without_dom[v] = poly of sets without v and v dominated
\\ without_undom[v] = poly of sets without v and v undominated
\\
\\ without_dom[p] is exactly one with[v] and the rest without_dom[v].
\\
\\ Counting all without[] would be
\\ without[p] = without[p] * without_dom[v]
\\ + without_undom[p] * with[v]
\\ which is no better than the individual desired without_dom[p].
\\
my(seq=vpar_INTERNAL_seq_upwards(vpar),
with = vector(#vpar,v,var), \\ 1 set size 1
without_dom = vector(#vpar), \\ 0 sets
without_undom = vector(#vpar,v,1), \\ 1 set size 0 (undominated)
ret = var/var, \\ 1 as poly
p);
for(i=1,#seq,
my(v=seq[i]);
if(p=vpar[v],
without_dom[p] = without_dom[p] * without_dom[v]
+ without_undom[p] * with[v];
without_undom[p] *= without_dom[v];
with[p] *= with[v] + without_undom[v];
,
ret *= with[v] + without_dom[v]); \\ at root
with[v] = \\ kill intermediate values
without_dom[v] =
without_undom[v] = 0);
ret;
}
{
addhelp(vpar_perfect_dompoly,
"poly = vpar_perfect_dompoly(vpar,var='x)
Return the perfect domination polynomial of vpar.
Each term c*x^n is count c of how many perfect dominating sets of size n in
vpar. Optional parameter \"var\" can be the polynomial variable to use (a
symbol), or default 'x. See vpar_is_perfect_domset() on perfect dominating
sets.
Specific terms of the polynomial include
polcoeff(poly,#vpar) == 1 \\\\ all vertices is perfect
valuation(poly,'x) == vpar_perfect_domnum(vpar)
subst(poly,'x,1) == vpar_perfect_domsets_count(vpar) \\\\ all sets
The set all vertices is perfect dominating so high term always 1*x^#vpar.
This includes empty tree vpar=[], its perfect domination polynomial being
1*x^0 = 1 only.
The smallest n term is the perfect domination number and its coefficient is
the count of such sets as per vpar_perfect_domnum_and_count(vpar).
vpar can be a forest, in which case the effect is polynomial product
pol1*pol2*... of the perfect domination polynomials of the component trees.");
}
vpar_perfect_domnum(vpar) =
{
\\ with[v] = smallest size including v
\\ without_dom[v] = smallest size without v and v dominated
\\ without_undom[v] = smallest size without v and v undominated
\\
\\ Initially there are no without_dom sets. Taking it as size #vpar means
\\ any actual perfect domset will be smaller (or the same if the best set
\\ is all vertices).
\\
\\ without_dom[p] is to have exactly one with[v] child. Its choice is
\\ between existing without_undom[p] and this with[v], vs existing
\\ without_dom[p] (which has a previous with) and this child without, that
\\ being without_dom[v] since p absent can't have without_undom[v] child.
\\
\\ without_undom[p] as sum without_dom[v] might overflow Vecsmall on
\\ 32-bit if it has 2^16 without_dom[v]=none under it. The min(...,none)
\\ avoids overflow, though that many leaf children would be unlikely in
\\ practice.
my(seq=vpar_INTERNAL_seq_upwards(vpar),
none = #vpar,
with = vectorsmall(#vpar,v,1), \\ size 1
without_dom = vectorsmall(#vpar,v,none), \\ no such
without_undom = vectorsmall(#vpar), \\ size 0
ret = 0,
p);
for(i=1,#seq,
my(v=seq[i]);
if(p=vpar[v],
without_dom[p] = min(without_dom[p] + without_dom[v],
without_undom[p] + with[v]);
without_undom[p] = min(without_undom[p] + without_dom[v],
none);
with[p] += min(with[v], without_undom[v]);
,
ret += min(with[v], without_dom[v]))); \\ at root
ret;
}
{
addhelp(vpar_perfect_domnum,
"num = vpar_perfect_domnum(vpar)
Return the perfect domination number of vpar.
This is the number of vertices in the smallest perfect dominating set of vpar.
See vpar_is_perfect_domset_flags() on perfect dominating sets.
vpar can be a forest, in which case the effect is sum perfect domination
numbers of its component trees.");
}
vpar_perfect_domnum_and_count(vpar) =
{
\\ Like vpar_perfect_domnum() but with counts of sets too.
\\ As usual, the counts must distinguish a new low where set the count or
\\ an equal low where add the count.
\\ print("vpar_perfect_domnum_and_count()\n"vpar);
my(seq=vpar_INTERNAL_seq_upwards(vpar),
none = #vpar,
with = vectorsmall(#vpar,v,1), \\ 1 set size 1
without_dom = vectorsmall(#vpar,v,none), \\ no sets, dummy size
without_undom = vectorsmall(#vpar), \\ 1 set size 0
with_count = vector(#vpar,v,1),
without_dom_count = vector(#vpar,v,0),
without_undom_count = with_count,
ret = 0,
ret_count = 1,
p);
for(i=1,#seq,
my(v=seq[i]);
\\ print(" v="v);
if(p=vpar[v],
\\ print(" without_dom="without_dom[v]"["without_dom_count[v]"] without_undom="without_undom[v]"["without_undom_count[v]"]");
\\ print(" p="p" without_dom[p]="without_dom[p]"["without_dom_count[p]"] without_undom[p]="without_undom[p]"["without_undom_count[p]"]");
\\ without_dom[p] = one of with[v] rest without_dom[v], choose
\\ smallest with[v], rest is existing without_undom[p].
\\ prospective is taking this with[v]
\\
without_dom[p] += without_dom[v];
my(prospective = without_undom[p] + with[v]);
if(prospective >= without_dom[p],
without_dom_count[p] *= without_dom_count[v];
if(prospective == without_dom[p],
without_dom_count[p] += with_count[v] * without_undom_count[p]);
,
without_dom[p] = prospective;
without_dom_count[p] = with_count[v] * without_undom_count[p]);
without_undom[p] = min(without_undom[p] + without_dom[v],
none); \\ avoid overflow Vecsmall
without_undom_count[p] *= without_dom_count[v];
my(v_with_or_undom = with[v], \\ min(with[v],without_undom[v])
v_with_or_undom_count = with_count[v]);
if(without_undom[v] < v_with_or_undom,
v_with_or_undom=without_undom[v];
v_with_or_undom_count=without_undom_count[v];
,
without_undom[v] == v_with_or_undom,
v_with_or_undom_count += without_undom_count[v]);
\\ print(" v_with_or_undom "v_with_or_undom"["v_with_or_undom_count"]");
with[p] += v_with_or_undom;
with_count[p] *= v_with_or_undom_count;
,
my(v_with_or_dom = with[v], \\ min(with[v],without_dom[v])
v_with_or_dom_count = with_count[v]);
if(without_dom_count[v] && without_dom[v] < v_with_or_dom,
v_with_or_dom = without_dom[v];
v_with_or_dom_count = without_dom_count[v];
,
without_dom[v] == v_with_or_dom,
v_with_or_dom_count += without_dom_count[v]);
\\ print(" root with="with[v]"["with_count[v]"]");
\\ print(" without_dom="without_dom[v]"["without_dom_count[v]"]");
\\ print(" v_with_or_dom="v_with_or_dom" ["v_with_or_dom_count"]");
ret += v_with_or_dom; \\ at root
ret_count *= v_with_or_dom_count);
with_count[v] = \\ kill intermediate values
without_dom_count[v] =
without_undom_count[v] = 0);
[ret,ret_count];
}
{
addhelp(vpar_perfect_domnum_and_count,
"[num,count] = vpar_perfect_domnum_and_count(vpar)
Return a vector of perfect domination number of vpar and count of how many
perfect dominating sets of this size are in vpar. The perfect domination
number returned is the same as vpar_perfect_domnum(vpar).
The set of all vertices is always a perfect dominating set (including the
empty set of vertices when the empty tree) so always have count >= 1.
vpar can be a forest, in which case the effect is sum perfect domination
numbers and product counts of its component trees.");
}
\\-----------------------------------------------------------------------------
\\ Disjoint Domination Number
\\ The code calculates five quantities at each vertex which are the size of
\\ the set and the set in the complement with certain combinations,
\\
\\ set complement initial
\\ ---- ------------ -------
\\ with_dom[v] = with (without) dom no sets
\\ with_undom[v] = with (without) undom 1 set, size 1
\\ without_dom_dom[v] = dom dom no sets
\\ without_dom_undom[v] = dom undom no sets
\\ without_undom_undom[v] = undom undom 1 set, size 0
\\
\\ Vertices below v (if any) are dominated in the set and complement.
\\
\\ with_dom, with_undom and without_dom_undom have different vertex types in
\\ the set and complement.
\\
\\ without_dom_dom and without_undom_undom are the same types in set and
\\ complement and are kept as counts "one way around". When used they can
\\ be taken 2* if either way around is appropriate. Must have size > 0 to
\\ go either way, so that there are actually some vertices to swap.
\\ without_dom_dom is by its nature always size > 0 but without_undom_undom
\\ may be size=0.
\\
\\ When there are "no sets" of some combination, that is represented by a
\\ set size none = #vpar+1 so that any actual set size is smaller and will
\\ be seen to be a new low. Where sizes are added may give a multiple of
\\ none. To avoid overflowing a 2^31 vectorsmall there are min(...,none)
\\ capping such additions. Both overflow for example on star-n (exercised
\\ in devel/test-slow.gp).
\\
\\ Sizes and counts are built by accumulating counts of some child into its
\\ parent, with whatever existing in the parent.
\\
\\ with_dom[p] is to have >=1 "with" child underneath the complement, and
\\ anything at all under the set. This is built by either existing
\\ with_dom[p] extended by a new child, or with_undom[p] getting a "with"
\\ under the complement which is v the first child under the complement.
\\
\\ with_dom[p]
\\ =
\\ with_dom[p] OR with_undom[p]
\\ + undom dom without + dom with
\\ or + dom dom without \ each way or + undom with
\\ or + dom dom without /
\\ or + undom with
\\ or + dom with \ each way
\\ or + with dom /
\\
\\ The with_undom[p] new form is simplest, it is child either with_dom[v] or
\\ with_undom[v], whichever is smaller. They are swapped so the with is
\\ reckoned in the complement. In the code v_with is the smaller of
\\ with_dom[v] and with_undom[v] and so is the amount added to with_undom[p]
\\ as prospective size of that form.
\\
\\ The with_dom[p] extending form can have anything under the set and any
\\ with or dom under the complement, so various combinations.
\\ without_dom_dom[v] is taken either way around as noted above.
\\
\\ For comparing sizes, it will be seen below that always have
\\
\\ without_dom_undom[p] <= without_dom_dom[p]
\\
\\ and then in the code v_with again is the with_dom or with_undom, so the
\\ size added to with_dom[p] is min(v_with, without_dom_undom[p]) and size
\\
\\ with_dom[p] = min(with_dom[p] + min(v_with,without_dom_undom[v]),
\\ with_undom[p] + v_with);
\\
\\ with_undom[p] starts as size 1 count 1 and is extended by suitable
\\ children. The children cannot have "with" under the complement as the
\\ complement must remain undominated.
\\
\\ with_undom[p]
\\ =
\\ with_undom[p]
\\ + undom dom without
\\ or + dom dom without \ each way
\\ or + dom dom without /
\\ or + with dom
\\
\\ with_undom[p] = with_undom[p] + min(with_dom[v],without_dom_undom[v])
\\
\\ without_dom_dom[p] is to have >=1 with_dom[v] children in the set, and
\\ >=1 with_dom[v] children in the complement. The rest must be
\\ without_dom_dom[v]. It can extend existing with_dom_dom[p] or build up
\\ from with_dom_undom[p].
\\
\\ without_dom_dom[p]
\\ = (prospective)
\\ without_dom_dom[p] OR without_dom_undom[p]
\\ + dom dom \ each way + dom with
\\ + dom dom /
\\ or + with dom \ each way
\\ or + dom with /
\\
\\ without_dom_dom[p] is a count of sets just "one way around". The set of
\\ the child v accumulated can be either way relative to that existing
\\ without_dom_dom[p]. In the code, v_dom is the smaller of with_dom or
\\ without_dom_dom as used for extending without_dom_dom[p] here. The count
\\ of sets is 2*.
\\
\\ without_dom_undom[p] is >=1 child with_dom[v] and the rest children
\\ without_dom_dom[v]. Can either extend existing or build up from
\\ without_undom_undom[p].
\\
\\ without_dom_undom[p]
\\ =
\\ without_dom_undom[p] OR without_undom_undom[p]
\\ + dom dom without \ each + with dom [v]
\\ or + dom dom without / way *2 if not empty
\\ or + with dom
\\
\\ without_dom_undom[p] = min(without_dom_undom[p] + v_dom,
\\ without_undom_undom[p] + with_dom[v])
\\
\\ For extending, child without_dom_dom[v] can be either way around relative
\\ to the existing without_dom_undom[p]. Building from
\\ without_undom_undom[p] can have that without_undom_undom[p] either way
\\ round relative to the with_dom[v] child. But only when
\\ without_undom_undom[p] is not the empty set, so the two ways are in fact
\\ different.
\\
\\ without_undom_undom[p] is the simplest accumulation. The children are to
\\ be all without_dom_dom[v], so both set and complement have p undominated.
\\
\\ without_undom_undom[p] += without_dom_dom[v]
\\
\\ The final target result at root v is smaller of with_dom or
\\ without_dom_dom, for set and complement both dominated. This is the
\\ v_dom noted above.
\\
\\ disjoint_domnum = v_dom
\\ = min(with_dom[v], without_dom_dom[v])
\\
\\ For a forest, the disjoint_domnum sizes add and the counts multiply, with
\\ 2* ways around for the second and each subsequent, relative to the first.
vpar_disjoint_domnum(vpar) =
{
\\ print("vpar_disjoint_domnum() "vpar);
my(seq=vpar_INTERNAL_seq_upwards(vpar),
none = #vpar+1,
with_dom = vectorsmall(#vpar,v,none), \\ no such
with_undom = vectorsmall(#vpar,v,1),
without_dom_dom = with_dom,
without_dom_undom = with_dom,
without_undom_undom = vectorsmall(#vpar,v,0),
ret = 0);
for(i=1,#seq,
my(v=seq[i], p=vpar[v]);
\\ print("v="v" (p="p") with "with_dom[v]" "with_undom[v]" without "without_dom_dom[v]" "without_dom_undom[v]" "without_undom_undom[v]);
\\ with_undom[v] <= with_dom[v] || error();
\\ without_undom_undom[v] <= without_dom_undom[v] || error("undom undom");
\\ with_dom[v] >= without_dom_dom[v] || error();
my(v_dom = min(with_dom[v], without_dom_dom[v]));
if(p,
my(v_with = min(with_dom[v], with_undom[v]));
with_dom[p] = min(with_dom[p] + min(v_with,without_dom_undom[v]),
with_undom[p] + v_with);
with_undom[p] = min(with_undom[p]
+ min(with_dom[v], without_dom_undom[v]),
none);
without_dom_dom[p] = min(without_dom_dom[p] + v_dom,
without_dom_undom[p] + with_dom[v]);
without_dom_undom[p] = min(without_dom_undom[p] + v_dom,
without_undom_undom[p] + with_dom[v]);
without_undom_undom[p] = min(without_undom_undom[p]+without_dom_dom[v],
none);
\\ print(" to p with "with_dom[p]" "with_undom[p]" without "without_dom_dom[p]" "without_dom_undom[p]" "without_undom_undom[p]);
,
if((ret += v_dom) > #vpar,
return(-1)))); \\ top singleton no combinations
\\ print(" ret "ret);
ret;
}
{
addhelp(vpar_disjoint_domnum,
"num = vpar_disjoint_domnum(vpar)
Return the disjoint domination number of vpar.
This is the smallest number of vertices in two disjoint sets which are both
dominating sets in vpar. (Total number of vertices in the two sets.)
vpar can be a forest, in which case the effect is sum disjoint domination
numbers of its component trees.
If vpar contains an isolated vertex then there are no two disjoint sets
dominating it and the return is -1.");
}
vpar_disjoint_domnum_and_count(vpar) =
{
\\ print("vpar_disjoint_domnum_and_count() "vpar);
my(seq=vpar_INTERNAL_seq_upwards(vpar),
none = #vpar+1,
with_dom = vectorsmall(#vpar,v,none), \\ no such
with_undom = vectorsmall(#vpar,v,1),
without_dom_dom = with_dom,
without_dom_undom = with_dom,
without_undom_undom = vectorsmall(#vpar,v,0),
with_dom_count = vector(#vpar),
with_undom_count = vector(#vpar,v,1),
without_dom_dom_count = with_dom_count,
without_dom_undom_count = with_dom_count,
without_undom_undom_count = with_undom_count,
ret = 0,
ret_count = 1,
ret_count_shift = 0);
for(i=1,#seq,
my(v=seq[i], p=vpar[v]);
\\ print(" v="v" p="p);
\\ print(" with "with_dom[v]"["with_dom_count[v]"] "with_undom[v]"["with_undom_count[v]"] without "without_dom_dom[v]"["without_dom_dom_count[v]"] "without_dom_undom[v]"["without_dom_undom_count[v]"] "without_undom_undom[v]"["without_undom_undom_count[v]"]");
my(v_dom = min(with_dom[v], without_dom_dom[v]),
v_dom_count = if(v_dom==with_dom[v], with_dom_count[v])
+ if(v_dom==without_dom_dom[v], without_dom_dom_count[v]));
\\ print(" v_dom "v_dom"["v_dom_count"]");
if(p,
my(v_with = min(with_dom[v], with_undom[v]),
v_with_count = if(v_with==with_dom[v], with_dom_count[v])
+ if(v_with==with_undom[v], with_undom_count[v]));
\\ print(" v_with "v_with"["v_with_count"]");
\\ with_dom[p]
\\ =
\\ with_dom[p] OR with_undom[p]
\\ + undom dom without + dom with
\\ or + dom dom without \ each way or + undom with
\\ or + dom dom without /
\\ or + undom with
\\ or + dom with \ each way
\\ or + with dom /
my(v_ww = min(v_with,without_dom_undom[v]));
\\ print(" to with_dom[p]="with_dom[p]" v_ww "v_ww);
with_dom[p] += v_ww;
my(prospective = with_undom[p] + v_with);
if(with_dom[p] <= prospective,
with_dom_count[p]
*= if(v_ww==without_dom_undom[v], without_dom_undom_count[v])
+ if(v_ww==without_dom_dom[v], without_dom_dom_count[v] << 1)
+ if(v_ww==with_undom[v], with_undom_count[v])
+ if(v_ww==with_dom[v], with_dom_count[v] << 1));
\\ print(" or prospective "prospective"["prospective_count"]");
if(prospective <= with_dom[p],
if(prospective < with_dom[p],
with_dom[p] = prospective;
with_dom_count[p] = 0);
with_dom_count[p] += with_undom_count[p] * v_with_count);
\\ with_undom[p] = with_undom[p] + min(with_dom[v],without_dom_undom[v])
\\
\\ with_undom[p]
\\ =
\\ with_undom[p]
\\ + undom dom without
\\ or + dom dom without \ each way
\\ or + dom dom without /
\\ or + with dom
v_ww = min(with_dom[v],without_dom_undom[v]);
\\ print(" to with_undom[p]="with_undom[p]"["with_undom_count[p]"] v_ww "v_ww);
with_undom[p] = min(with_undom[p] + v_ww,
none);
with_undom_count[p]
*= if(v_ww==without_dom_undom[v], without_dom_undom_count[v])
+ if(v_ww==without_dom_dom[v], without_dom_dom_count[v] << 1)
+ if(v_ww==with_dom[v], with_dom_count[v]);
\\ without_dom_dom[p]
\\ = (prospective)
\\ without_dom_dom[p] OR without_dom_undom[p]
\\ + dom dom \ each way + dom with
\\ + dom dom /
\\ or + with dom \ each way
\\ or + dom with /
\\ print(" to without_dom_dom[p] "without_dom_dom[p]);
without_dom_dom[p] += v_dom;
\\ print(" extends without_dom_dom[p]="without_dom_dom[p]);
prospective = without_dom_undom[p] + with_dom[v];
if(without_dom_dom[p] <= prospective,
without_dom_dom_count[p] *= v_dom_count << 1);
if(prospective <= without_dom_dom[p],
if(prospective < without_dom_dom[p],
without_dom_dom[p] = prospective;
without_dom_dom_count[p] = 0);
without_dom_dom_count[p]
+= without_dom_undom_count[p] * with_dom_count[v]);
\\ without_dom_undom[p]
\\ =
\\ without_dom_undom[p] OR without_undom_undom[p]
\\ + dom dom without \ each + with dom [v]
\\ or + dom dom without / way *2 if not empty
\\ or + with dom
\\
\\ without_dom_undom[p] = min(without_dom_undom[p] + v_dom,
\\ without_undom_undom[p] + with_dom[v])
\\ print(" to without_dom_undom[p]="without_dom_undom[p]" sum "without_dom_undom[p]+v_dom);
without_dom_undom[p] += v_dom;
prospective = without_undom_undom[p] + with_dom[v];
\\ print(" prospective "prospective" from without_undom_undom[p]="without_undom_undom[p]"["without_undom_undom_count[p]"]");
if(without_dom_undom[p] <= prospective,
without_dom_undom_count[p] *= v_dom_count+without_dom_dom_count[v]);
if(prospective <= without_dom_undom[p],
if(prospective < without_dom_undom[p],
without_dom_undom[p] = prospective;
without_dom_undom_count[p] = 0);
without_dom_undom_count[p]
+= (without_undom_undom_count[p] * with_dom_count[v])
<< (without_undom_undom[p]!=0));
without_undom_undom[p] = min(without_undom_undom[p] + without_dom_dom[v],
none);
without_undom_undom_count[p] *= without_dom_dom_count[v];
\\ print(" to p "with_dom[p]"["with_dom_count[p]"] "with_undom[p]"["with_undom_count[p]"] without "without_dom_dom[p]"["without_dom_dom_count[p]"] "without_dom_undom[p]"["without_dom_undom_count[p]"] "without_undom_undom[p]"["without_undom_undom_count[p]"]");
,
(ret_count *= v_dom_count << ret_count_shift) || return([-1,0]);
ret += v_dom;
ret_count_shift = 1));
\\ print(" ret "ret);
[ret,ret_count];
}
{
addhelp(vpar_disjoint_domnum_and_count,
"[num,count] = vpar_disjoint_domnum_and_count(vpar)
Return a vector of the disjoint domination number of vpar and count of how
many pairs of sets of this size. The disjoint domination number returned is
per vpar_disjoint_domnum(vpar).
vpar can be a forest, in which case the effect is sum disjoint domination
numbers and product counts of component trees.
If vpar contains an isolated vertex then there are no two disjoint sets
dominating it and the return is num -1 and count 0.
See examples/disjoint-domnum-most-sets.gp on trees with large count.");
}
\\------------------------------------------------------------------------------
\\ Edge Covers
\\
\\ An edge cover is a set of edges where every vertex is at tne end of one
\\ or more edge in the set.
\\
\\ Edges are represented by a vertex v and take the edge v to its parent.
\\ vpar_is_edgecover_flags() is vertices v as flags or
\\ vpar_is_edgecover_set() is vertices in a Set().
\\
\\ Edge covers treat vpar as a free tree. The edge representation uses
\\ direction v to parent, bu relabelling or re-rooting is no change.
vpar_is_edgecover_flags(vpar,flags) =
{
\\ parents[p]=1 when some vertex v in the set,
\\ ie. flags[v]=1, has parent vpar[v]=p, meaning
\\ p is covered by an edge in the set
my(parents=vectorsmall(#vpar));
for(v=1,#vpar,
if(flags[v],
vpar[v] || return(0); \\ no parent is no edge
parents[vpar[v]]=1));
for(v=1,#vpar,
flags[v] || parents[v] || return(0));
1;
}
{
addhelp(vpar_is_edgecover_flags,
"bool = vpar_is_edgecover_flags(vpar,flags)
flags is a vector of length #vpar of 0,1 booleans giving a set of vertices.
Each v with flags[v]=1 represents the edge from v to its parent vpar[v].
Return 1 if these edges are an edge cover, meaning every vertex is the end
of one or more edges in the set.
If a vertex with flag[v]=1 has no parent, ie. vpar[v]=0, then it is not an
edge and return 0 for not an edge cover.
The empty tree [] is covered by the empty set of edges.");
}
vpar_is_edgecover_set(vpar,set) =
{
\\ parents is set of parents of vertices in 'set".
\\ Any 0 in parents is a vertex with a parent, so
\\ no edge up so not an edge cover
my(parents=Set(vecextract(vpar,set)));
!setsearch(parents,0)
&& #setunion(set,parents) == #vpar;
}
{
addhelp(vpar_is_edgecover_set,
"bool = vpar_is_edgecover_set(vpar,set)
set is a Set() of vertex numbers, each representing the edge from it to its
parent.
Return 1 if these edges are an edge cover, meaning every vertex is the end
of one or more edges in the set.
If a vertex in the set has no parent, then it does not represent an edge and
the return is 0 for not an edge cover.
The empty tree [] is covered by the empty set of edges.");
}
vpar_edgecovers_count(vpar) = \
vpar_edgecovers_poly(vpar,1);
{
addhelp(vpar_edgecovers_count,
"count = vpar_edgecovers_count(vpar)
Return the number of edge covers in vpar.
An edge cover is a set of edges where every vertex is at tne end of one or
more of those edges.
The empty tree [] is covered by the empty set of edges so its count is 1.
A singleton vertex has no edges so no edge covers.
vpar can be a forest and its count the product of the counts in its
component trees.");
}
vpar_edgecovers_poly(vpar,var='x) =
{
\\ covered[v] = how many where v is covered
\\ uncovered[v] = how many where v not covered
\\ In both cases all vertices below v are covered.
\\
\\ To accumulate v counts into its parent p, the ne_covered cases are
\\ where the edge v to p is taken or not taken.
\\
\\ new_covered[p] = all[p] * all[v] edge
\\ covered[p] * covered[v] not
\\ new_uncovered[p] = uncovered[p] * covered[v]
\\
\\ "ret" is product of counts in the component trees, so at each root.
\\ Any singleton makes ret=0 and can early exit.
my(seq=vpar_INTERNAL_seq_upwards(vpar),
covered = vector(#vpar),
uncovered = vector(#vpar,v,1), \\ singleton uncovered
ret = var/var); \\ 1 as poly
for(i=1,#seq,
my(v=seq[i],
p=vpar[v]);
if(p,
covered[p] = (covered[p] + uncovered[p])
* (covered[v] + uncovered[v]) * var
+ covered[p] * covered[v];
uncovered[p] *= covered[v];
,
(ret *= covered[v]) || break);
covered[v] = \\ kill intermediate polys
uncovered[v] = 0);
ret;
}
{
addhelp(vpar_edgecovers_poly,
"poly = vpar_edgecovers_poly(vpar,var='x)
Return the edge covers polynomial of vpar.
Each term c*x^n is count c of how many edge covers of size n edges in vpar.
Optional parameter \"var\" can be the polynomial variable to use (a symbol),
or default 'x.
All edges is always an edge cover so high term 1*x^vpar_num_edges(vpar).
This includes empty tree vpar=[] covered by the empty set.
But not a singleton which has no covers at all and return 0.
vpar can be a forest, in which case the effect is polynomial product
pol1*pol2*... of the edge cover polynomials of the component trees.");
}
\\-----------------------------------------------------------------------------
\\ Claws
vpar_num_claws(vpar) =
{
\\ ENHANCE-ME: Not right for cyclics yet.
\\ Avoiding self-loop looking like its own neighbour would be
\\ degrees[v]-((vpar[v]==v)<<1).
\\ To count induced claws would check against edges between the neighbours
\\ chosen by the binomial.
my(degrees=vpar_degrees_vector(vpar));
sum(v=1,#degrees, binomial(degrees[v],3));
}
{
addhelp(vpar_num_claws,
"count = vpar_num_claws(vpar)
Return the number of claws in vpar.
This is simply sum(binomial(degree[v],3)), choosing sets of 3 neighbours
at each v as arms of the claw, or nothing at degree[v]<3.");
}
\\ GP-Test vector(3,forest, forest-=2; if(forest<=0,4,16)) == [4,4,16]
\\ GP-Test vector(3,forest, forest-=2; 3+(forest==1)) == [3,3,4]
\\ GP-Test vector(3,forest, forest-=2; if(forest<=0, 3-forest, 5))==[4,3,5]
\\
vpar_num_claws_total(n,forest=0) = \
if(n>=4, (binomial(n,4) * (n+(forest>0))^(n-4-(forest<0))) << 4, 0);
{
addhelp(vpar_num_claws_total,
"count = vpar_num_claws_total(n,forest=0)
Return the total number of claws in labelled rooted trees of n vertices.
Optional parameter \"forest\" can be 1 for the total in forests of n
vertices, or can be -1 for total in trees root=1.
Totals for trees, root=1 trees, and forests, are respectively
T(n) = n*B(n) = if(n<4,0, 16*binomial(n,4) * n^(n-4))
= 0, 0, 0, 0, 16, 400, 8640, 192080, ... (4*n*A053508)
B(n) = if(n<4,0, 16*binomial(n,4) * n^(n-5)) \\\\ root=1
= 0, 0, 0, 0, 4, 80, 1440, 27440, ... (4*A053508)
F(n) = if(n<4,0, 16*binomial(n,4) * (n+1)^(n-4)) \\\\ forests
= 0, 0, 0, 0, 16, 480, 11760, 286720, ... (16*A053509)
For trees this is choose binomial(n,4) claw vertices, times 4 choices of
middle, times 4 choices of top-most. The claw and n-4 other vertices are
n-3 blocks arranged in a tree (n-3)^(n-4) ways. But in their Prufer code,
everywhere the claw block number appears has 3 extra choices so n^(n-4).
Similarly root=1 and forests as n-3 blocks then 3 extras.");
}
\\ GP-Test vector(20,n, 4*n*binomial(n-1,3)) ==vector(20,n, 16*binomial(n,4))
\\ GP-DEFINE B(n) = if(n==0,0, 4*binomial(n-1,3)*n^(n-4));
\\ GP-DEFINE B(n) = if(n==0,0, 16*binomial(n,4)*n^(n-5));
\\ GP-Test vector(8,n,n--; B(n)) == [0, 0, 0, 0, 4, 80, 1440, 27440]
\\ GP-Test vector(8,n,n--; n*B(n)) == [0, 0, 0, 0, 16, 400, 8640, 192080]
\\ vector(8,n,n--; B(n)) == vector(8,n,n--; vpar_num_claws_total(n,-1))
\\ GP-DEFINE F(n) = if(n<4,0, 16*binomial(n,4)*(n+1)^(n-4));
\\ GP-Test vector(8,n,n--; F(n)) == [0, 0, 0, 0, 16, 480, 11760, 286720]
\\ vector(8,n,n--; F(n)) == vector(8,n,n--; vpar_num_claws_total(n,1))
\\ vector(8,n,n--; vpar_num_claws_total(n,-1))
\\ vector(8,n,n--; vpar_num_claws_total(n))
\\ vector(8,n,n--; vpar_num_claws_total(n,1))
\\ vector(8,n,n--; vpar_num_claws_total(n,1)/16)
\\ 0, 0, 0, 0, 16, 480, 11760, 286720
\\ A053509
\\ 1, 30, 735, 17920, 459270, 12600000, 372027810,
\\ GP-DEFINE T(n) = if(n<4,0, 4*binomial(n-1,3)*n^(n-3));
\\ GP-DEFINE T(n) = if(n<4,0, 16*binomial(n,4)*n^(n-4));
\\ GP-Test vector(8,n,n--; T(n)) == [0, 0, 0, 0, 16, 400, 8640, 192080]
\\ vector(8,n,n--; T(n)) == vector(8,n,n--; vpar_num_claws_total(n))
\\ vector(8,n,n+=3; T(n))
\\ vector(8,n,n+=3; 16* binomial(n,4) * n^(n-4))
\\------------------------------------------------------------------------------
\\ Prime Code
\\ Also:
\\ Peter R. Cappello, "A Note on a Bijection between Natural Numbers and
\\ Rooted Trees", 4th SIAM Conference on Discrete Mathematics, June 1988.
\\ http://sites.cs.ucsb.edu/~cappello/papers/1988SiamDM.html
\\ http://sites.cs.ucsb.edu/~cappello/papers/bijections/p.pdf
\\ http://www.cs.ucsb.edu/~cappello/papers/bijections/p.ps
\\ http://www.cs.ucsb.edu/~cappello/papers/bijections/j.tex.bak
\\ http://sites.cs.ucsb.edu/~cappello/presentations.shtml
\\
\\ Gets the same coding, and for decode reckons an ordered tree as having
\\ the factors in ascending order -- which is what vpar_from_primecode()
\\ currently gives.
\\ Goes on to tree code exponents for edge labelling.
\\ flag=0 to return vector of primecodes
\\ flag=1 to return primecode of the root
vpar_INTERNAL_to_primecode(vpar,flag) =
{
\\ No kill of intermediate vec[] when flag=1 single return.
\\ The limitations of prrime() mean the only really big value will be at
\\ the root and will be the return.
\\
my(seq=vpar_INTERNAL_seq_upwards(vpar),
vec=vector(#vpar,i,1));
for(i=1,#seq,
my(v=seq[i], p=vpar[v]);
if(p, vec[p]*=prime(vec[v]),
flag, return(vec[v])));
if(flag, 0, vec);
}
vpar_to_primecode(vpar) = \
vpar_INTERNAL_to_primecode(vpar,1);
{
addhelp(vpar_to_primecode,
"primecode = vpar_to_primecode(vpar)
Return the integer primecode of tree vpar, per (independently) Matula and
Goebel. vpar must be a tree, not a forest.
D. W. Matula, \"A Natural Rooted Tree Enumeration by Prime Factorization\".
Abstract in SIAM Review, \"Chronicle\", volume 10, number 2, April 1968,
pages 244-290. http://www.jstor.org/stable/2027327
F. Goebel, \"On a 1-1-Correspondence between Rooted Trees and Natural
Numbers\", Journal of Combinatorial Theory, Series B, volume 29, 1980,
pages 141-143,
Suppose the root has child subtrees vpar1,vpar2,etc. Then, where prime() is
the nth-prime function,
primecode = prime(primecode(vpar1)) * prime(primecode(vpar2)) * ...
The empty tree vpar=[] is primecode 0. A singleton vpar=[0] is primecode 1.
Then for example path-2 is a root with singleton child, that child is
primecode 1 so path-2 is primecode = prime(1) = 2.
primecode represents an unlabelled rooted tree, so all relabellings of vpar
give the same primecode.
The biggest primecode for n vertices (A005518), at least for smallish n, is
a path with 3 childless at the bottom so prime(prime(...(prime(8)))).
Nested prime of prime like this becomes large quite quickly, requiring
potentially large table or calculation of primes above roughly n=12
vertices.");
}
\\ GP-Test prime(1) == 2
vpar_to_primecode_vector(vpar) = \
vpar_INTERNAL_to_primecode(vpar,0);
{
addhelp(vpar_to_primecode_vector,
"vec = vpar_to_primecode_vector(vpar)
Return a vector where vec[v] is the primecode of the subtree at and below
vertex v. See vpar_to_primecode() on primecodes.
vpar can be a forest.");
}
vpar_from_primecode(primecode) =
{
\\ List "l" is a stack of pending actions in the form of blocks of 3 or 4
\\ list elements.
\\
\\ reps, primecode, parent, 1 append tree of code
\\ reps, start, 0 make copies of start..#vpar
\\
\\ "primecode" is >= 1 and is a subtree >=1 vertices to go under "parent"
\\ vertex number
\\ The initial tree has parent=0 so as to be the root.
\\ "reps" is >= 1 and is how many copies of the primecode tree to put
\\ under parent.
\\
\\ When "append tree" action is reached, its first vertex becomes the
\\ "start" of a reps action.
\\ The reps action copies everything from "start" to the end of vpar.
\\ The first vertex is the top of the subtree being the "parent".
\\ In the copies that top is unchanged, and the rest are offset.
\\
\\ When a primecode occurs again in a different part of the tree, it would
\\ be possible to copy the block where previously decoded. The guess here
\\ is that it's not worth keeping track of codes and blocks since usually
\\ a second occurrence should be uncommon, or only small ones common and
\\ their factor() work is small. The whole concept of coding by prime
\\ index is limited to small to medium trees anyway.
\\ print("vpar_from_primecode() "primecode);
if(primecode==0,return([]));
my(vpar=List([]),
l=List([1,primecode,0,1])); \\ reps,code,parent,action
while(#l,
\\ print(" l="Vec(l));
my(a=l[#l]); listpop(l); \\ action
if(a,
\\ print(" at p="l[#l]" code "l[#l-1]" reps="l[#l-2]" vpar="Vec(vpar));
listput(vpar,l[#l]); listpop(l); \\ parent
my(m=factor(l[#l])); listpop(l); \\ primecode
if(l[#l]>1, \\ reps
listput(l,#vpar); \\ start
listput(l,0); \\ action = copies
,
listpop(l));
\\ print(" m "m);
forstep(i=matsize(m)[1],1,-1,
\\ print(" push p="#vpar" code="primepi(m[i,1])" reps="m[i,2]);
listput(l,m[i,2]); \\ reps
listput(l,primepi(m[i,1])); \\ code
listput(l,#vpar); \\ parent
listput(l,1)); \\ action = code
,
\\ print(" repeats "l[#l-1]" start "l[#l]" vpar="Vec(vpar));
my(offset=#vpar-l[#l]); listpop(l);
my(r=l[#l]); listpop(l); \\ repetitions
\\ print(" offset "offset);
for(i=2,r,
\\ print(" rep "i" vpar="Vec(vpar));
listput(vpar,vpar[#vpar-offset]);
for(j=1,offset,
listput(vpar,vpar[#vpar-offset]+offset+1)));
));
Vec(vpar);
}
{
addhelp(vpar_from_primecode,
"vpar = vpar_from_primecode(primecode)
Return a vpar tree represented by the given primecode integer, per
(independently) Matula and Goebel. See vpar_to_primecode() for a
description of the coding.
primecode represents an unlabelled rooted tree. The vertex numbers in the
vpar return have root 1 but otherwise unspecified labelling. The current
implementation is pre-order (vpar_is_preorder()) with siblings by ascending
subtree primecode (vpar_to_primecode(vpar_subtree(vpar,v))), but don't rely
on that yet.
This decode requires factorizing primecode and looking up primepi() for its
factors, repeated recursively down. This might be slow for a big
primecode.");
}
\\------------------------------------------------------------------------------
\\ Balanced Binary
\\ If vpar_INTERNAL_fromdigits is already defined as an alias then must
\\ hide assignment vpar_INTERNAL_fromdigits=... in an eval, since it's a
\\ parse error even when not executed.
\\
\\ Instead of vpar_INTERNAL_fromdigits(), could duplicate
\\ vpar_to_balanced_binary() with code using either fromdigits() or subst()
\\ as necessary. Or could make a string to eval with the difference.
{
if(iferr(fromdigits([1]),e,0),
alias("vpar_INTERNAL_fromdigits","fromdigits"),
eval("
vpar_INTERNAL_fromdigits=(vec,base=10)->subst(Pol(vec),'x,base)
"));
}
vpar_to_balanced_binary(vpar) =
{
\\ ret[] is a vector of bits for the final result. Must be a full Vec for
\\ fromdigits(). It'd be possible to work upwards accumulating subtree
\\ bits into the parent, but doing so by bit shifts would be n^2 in the
\\ worst case (a path upwards).
\\ print("vpar_to_balanced_binary() "vpar);
my(seq=vpar_seq_preorder(vpar),
ret=vector(#vpar << 1),
pos=0,
prev=0);
for(i=1,#seq,
my(v=seq[i]);
\\ print(" at v="v" prev="prev" pos="pos);
while(vpar[v]!=prev,
pos++;
prev=vpar[prev];
\\ print(" step up to prev="prev);
);
ret[pos++] = 1;
prev=v);
\\ print(" final pos="pos" "ret);
vpar_INTERNAL_fromdigits(ret,2);
}
{
addhelp(vpar_to_balanced_binary,
"b = vpar_to_balanced_binary(vpar)
Return an integer b which is the balanced binary coding of vpar as an
ordered tree. b has 2 bits per vertex, so #binary(b) == 2*#vpar.
See vpar_is_balanced_binary() for more on balanced binary.
vpar is coded by pre-order traversal (vpar_seq_preorder()) with 1-bit at a
vertex and matching 0-bit on returning to that vertex after its child
subtrees. A childless has no subtrees so is 10. Or path-2 is 1100. Empty
vpar=[] is b=0 reckoned as no bits.
Ordered forests are in one-to-one correspondence with balanced binary so b
is a canonical form for vpar as an ordered tree or forest. Sorting
numerically by b goes first by vpar size then by lex() increasing preorder
vpar vector (which is also lex() increasing preorder vpar_depths_vector()).
See vpar_from_balanced_binary() to go back to a vpar (but in preorder).
See vpar_preorder_to_i() for another forest -> integer.");
}
vpar_from_balanced_binary(b) =
{
\\ print("vpar_from_balanced_binary() b="binary(b));
my(i = if(b,logint(b,2)), \\ highest 1-bit position
vpar=vector((i+1)>>1));
\\ print(" high i="i);
for(v=2,#vpar,
\\ print(" at v="v" i decr ="i-1);
my(p=v-1);
while(!bittest(b,i--), \\ pre-decrement
p=vpar[p]);
vpar[v]=p);
\\ print(" return "vpar);
vpar;
}
{
addhelp(vpar_from_balanced_binary,
"vpar = vpar_from_balanced_binary(b)
b is a balanced binary integer as per vpar_is_balanced_binary().
b corresponds to an ordered forest. Return that as a preorder labelled vpar
(vpar_is_preorder()).
See vpar_to_balanced_binary() to go back to b.");
}
vpar_is_balanced_binary(b) =
{
my(d=0); \\ count of 0 bits at and below i (less significant)
if(b,
for(i=0,logint(b,2),
if(bittest(b,i), d-- >=0 || return(0), \\ 1-bit
d++))); \\ 0-bit
!d;
}
{
addhelp(vpar_is_balanced_binary,
"bool = vpar_is_balanced_binary(b)
b is an integer >=0. Return 1 if b is balanced binary, or return 0 if not.
Balanced binary means b has an equal number of 1-bits and 0-bits and has
them in matched pairs 1 preceding 0. For example 110010. This is 1 and 0
as matching nesting open and close parens (())().
The test is at every bit position in b, the number of 1s at and above is >=
the number of 0s at and above. (Or equivalently number of 0s below >=
number of 1s below.)");
}
vpar_balanced_binary_next(b) =
{
\\ Low run ones ...0111000
\\ ...1001010
\\ ^ ^^^^
\\ High bit moves up, low bits become 1010 at low end for smallest increase.
\\ l = low zeros is location of low end of the 1s.
\\ +1 there steps 0111 -> 1000.
\\ Then low end is (4^k-1)*2/3 for alternating bits 1010.
\\ Difference between valuation l before and t after the +1 gives how many
\\ low bits there.
\\
\\ Top of a 2n bit block is 111000. It instead goes to 10101010.
\\ That case is when the +1 leaves t at the highest 1 position, so the run
\\ of 1s incremented was all the 1s in b.
\\
\\ (Similar description in Math::NumSeq::BalancedBinary.)
if(b,
my(h=logint(b,2),
l=valuation(b,2));
b += (1< ...1000...
my(t=valuation(b,2));
\\ print(t" "h" "l);
if(t>h,
\\ b was 11110000, and become now 1,0000,0000
\\ next is 1010101010 of two more bits
b=0; t+=2);
((1 << (((t-l)<<1)-1)) - 2)/3 + b; \\ (4^k-1)*2/3 = 101010
,
2); \\ b=empty -> 10
}
{
addhelp(vpar_balanced_binary_next,
"b = vpar_balanced_binary_next(b)
b is a balanced binary integer per vpar_is_balanced_binary().
Return the next bigger balanced binary integer.
The step finds the lowest run of 1-bits, moves the first up 1 place and the
rest (if any) down to the low end of b as 101010. Or for 11110000 top of a
2n block step to all 101010 of one more 1-bit.");
}
vpar_balanced_binary_prev(b) =
{
\\ Low run 10s ...1001010
\\ ...0111000
\\ ^ ^^^^
\\ or empty ....100000
\\ ....010000
\\
\\ The search up for length of low ...101010 can test just the 1-bits,
\\ since if they are set then 0-bits must be 0s to be balanced binary.
\\ print("vpar_balanced_binary_prev() "binary(b));
my(l=-1);
while(bittest(b,l+=2), ); \\ l = length of low ...101010
\\ print(" l="l);
b -= ((1 << l) - 2)/3; \\ (4^k-1)*2/3 = 101010
\\ print(" low ",binary(((1 << l) - 2)/3)," gives "binary(b));
if(b,
\\ further higher 1 bits, make a 1s block just below them
my(t=valuation(b,2));
\\ print(" sub at ",(t-(l>>1)-1));
b - (1 << (t-(l>>1)-1)); \\ 1000... to 0111...
,
\\ b = 10101010 entirely, prev is 111000 of two fewer bits
\\ (including b=10 prev b=0)
l-=3; (1 << l) - (1<<(l>>1)));
}
{
addhelp(vpar_balanced_binary_prev,
"b = vpar_balanced_binary_prev(b)
b >= 2 is a balanced binary integer per vpar_is_balanced_binary().
Return the previous smaller balanced binary integer.
This is a reversal of vpar_balanced_binary_next().
Must not give b=0 since it has no previous. Behaviour in that case is
currently unspecified.");
}
\\-----------------------------------------------------------------------------
\\ Deleting
vpar_delete_vertex(vpar,v) = \
vector(#vpar-1,i, my(p=vpar[i+(i>=v)]); if(p==v,0,p-(p>v)));
{
addhelp(vpar_delete_vertex,
"vpar = vpar_delete_vertex(vpar,v)
Return vpar with vertex v deleted.
Vertex numbers >v are moved down.
Edges to and from v are deleted, so children of v become roots.
vpar can contain cycles. Deleting a cyclic vertex breaks that cycle since
its looping child becomes a root.");
}
vpar_delete_vertices(vpar,vec) =
{
\\ m[old_v] = new_v, map from old vertex number to new
vec=vecsort(vec);
my(m=vectorsmall(#vpar),
i=1,new_v=0,
new_vpar=if(#vpar==#vec,[],Vec(vpar,#vpar-#vec)));
for(v=1,#vpar,
if(i<=#vec&&v==vec[i], i++, \\ delete v
new_vpar[new_v++]=vpar[v]; m[v]=new_v)); \\ keep v
i==#vec+1 || error("vpar_delete_vertices() out of range or duplicates");
for(j=1,#new_vpar,
my(p=new_vpar[j]);
new_vpar[j]=if(p,m[p],0));
new_vpar;
}
{
addhelp(vpar_delete_vertices,
"vpar = vpar_delete_vertices(vpar,vec)
vec is a vector of vertex numbers.
Return vpar with those vertices deleted.
vec can have vertices in any order, but no duplicates.
Edges to and from vec vertices are deleted, so children of a deleted vertex
become roots (if not deleted themselves too).
The return has vertices in the same order as the original vpar but
renumbered down to collapse out those of vec.
vpar can contain cycles. Deleting a cyclic vertex breaks that cycle since
its loop-back child becomes a root.
This is equivalent to individual vpar_delete_vertex() applied from highest
to lowest vertex number, but here in one operation.");
}
vpar_keep_vertices(vpar,vec) =
{
\\ m[old_v] = new_v maps from old vertex number to new.
\\ Explicit check for duplicates in vec[] so as to avoid returning
\\ subtly bad rubbish. Could quietly ignore duplicates, but for now
\\ disallow the same as in vpar_delete_vertices().
vec=vecsort(vec);
my(m=vectorsmall(#vpar));
for(i=1,#vec,
if(m[vec[i]],error("duplicate vertex to keep"));
m[vec[i]] = i);
vector(#vec,i, my(p=vpar[vec[i]]); if(p, m[p], 0));
}
{
addhelp(vpar_keep_vertices,
"vpar = vpar_keep_vertices(vpar,vec)
vec is a vector of vertex numbers.
Return vpar with the vertices of vec retained and the rest deleted in the
manner of vpar_delete_vertices(). See vpar_delete_vertices() for details.
vec can have vertices in any order, but no duplicates.
vpar can contain cycles.");
}
vpar_keep_flags(vpar,flags) =
{
my(new_len = hammingweight(flags), \\ how many non-0
new_vpar = if(new_len,Vec(vpar,new_len),[]),
m = vectorsmall(#vpar), \\ m[old_v]
i=1,new_v=0);
for(v=1,#flags,
if(flags[v], \\ keep v
new_vpar[new_v++]=vpar[v]; \\ extract existing parent number
m[v]=new_v)); \\ mapping v to new
for(j=1,#new_vpar,
my(p=new_vpar[j]);
new_vpar[j]=if(p,m[p],0)); \\ map parent numbers to new
new_vpar;
}
{
addhelp(vpar_keep_flags,
"vpar = vpar_keep_flags(vpar,flags)
Return vpar with vertices kept according to the flags vector.
If flags[v] != 0 then vertex v is kept. The rest are deleted in the manner
of vpar_delete_vertices(). See vpar_delete_vertices() for details.
vpar can contain cycles.");
}
\\-----------------------------------------------------------------------------
\\ Modifications and Joining
vpar_join(vpar,v,sub_vpar) =
{
\\ This is clean to Vecsmall for vpar and sub_vpar, though don't want to
\\ guarantee that, as yet.
my(n=#vpar);
for(i=1,#sub_vpar, sub_vpar[i] += if(sub_vpar[i],n,v));
concat(vpar, sub_vpar);
}
{
addhelp(vpar_join,
"vpar = vpar_join(vpar,v,sub_vpar)
Return vpar with sub_vpar joined to it underneath vertex v.
Existing vertices of vpar are unchanged and sub_vpar vertices are appended.
The root or roots of sub_vpar are set to have parent v.
Joining a single new vertex under v would be vpar_join(vpar1,v,[0]), and
this is equivalent to concat(vpar,[v]).
vpar can contain cycles, but behaviour is unspecified if sub_vpar contains
cycles.");
}
vpar_concat_forest(vpar1,vpar2) =
{
\\ concat() here accepts Vecsmall vpar1,vpar2
my(vpar=concat(vpar1,vpar2));
for(i=#vpar1+1,#vpar,
if(vpar[i], vpar[i]+=#vpar1));
vpar;
}
{
addhelp(vpar_concat_forest,
"vpar = vpar_concat_forest(vpar1,vpar2)
Return a new vpar which is vpar1 and vpar2 concatenated to make a forest.
vpar1 and vpar2 can themselves be forests, and either can contain cycles.
The return has vpar1 first and then vpar2 with offset +#vpar1 on its vertex
numbers.");
}
vpar_concat_forests(vpars) =
{
if(#vpars,
my(vpar=concat(vpars),
s=#vpars[1]);
for(c=2,#vpars,
for(i=s+1,s+#vpars[c], if(vpar[i], vpar[i]+=s));
s+=#vpars[c]);
vpar;
,
[]);
}
{
addhelp(vpar_concat_forests,
"vpar = vpar_concat_forests(vpars)
vpars is a vector containing vpar vectors. Return a new vpar which is all
of vpars concatenated to make a forest. Each vpars[i] can be a forest too,
and can contain cycles. The return has vpars[1] first then vpars[2] with
offset +#vpars[1] on its vertex numbers, and so on.
vpars can contain empty elements [] and they add nothing to the return. If
vpars is entirely empty elements, or has no elements at all, then the return
is an empty vpar=[].");
}
vpar_add_root_above_forest(vpar) =
{
vpar=Vec(vpar,#vpar+1); \\ copy and extend
for(v=1,#vpar-1, vpar[v] || (vpar[v]=#vpar)); \\ zeros
vpar;
}
{
addhelp(vpar_add_root_above_forest,
"vpar = vpar_add_root_above_forest(vpar)
Return vpar with a new vertex v=#vpar+1 which becomes parent of what had
been roots of existing component trees in vpar.
The new vertex is always added, even when there is nothing for it to become
parent of, so an empty vpar==[] gets new vertex as a singleton.
vpar can contain cycles. Any cycles have no roots and are unchanged.
If all components are cyclic then the new vertex is a singleton.");
}
vpar_subdivide(vpar,s=1) =
{
my(n = #vpar,
new_v = n+1,
p);
vpar=Vec(vpar, #vpar + s*vpar_num_edges(vpar)); \\ extend vpar
for(v=1,n,
if ((p=vpar[v]),
for(i=1,s, \\ insert s many vertices between v and p
vpar[new_v]=p;
p=new_v;
new_v++);
vpar[v]=p));
vpar;
}
{
addhelp(vpar_subdivide,
"vpar = vpar_subdivide(vpar,s=1)
Return a copy of vpar with each existing edge subdivided by inserting s many
new vertices. Default is s=1 new in each. vpar can contain cycles.
Existing vertex numbers in vpar are unchanged. New vertices are #vpar+1
onwards. If s==0 or vpar has no edges then the return is vpar unchanged.
Each v from 1 to #vpar which has a parent gets s many vertices inserted.
The new vertex numbers are upwards. For example a tree 1<-2 (so root=1)
with s=4 inserted becomes 1<-3<-4<-5<-6<-2.");
}
vpar_contract_vertex(vpar,v) =
{
\\ g = grandparent for the children of v, renumbered for v removed.
\\ But only if v!=vpar[v], since v==vpar[v] is a self-loop and grandparent
\\ would be v itself which is being deleted.
my(g=vpar[v]);
g = if(g!=v, g - (g>v));
vector(#vpar-1,i,
my(p=vpar[i+(i>=v)]); \\ parent, of non-v vertices
if(p==v,
g, \\ i is child of v, go to grandparent
p-(p>v))); \\ i not child of v, renumber p for v removed
}
{
addhelp(vpar_contract_vertex,
"vpar = vpar_contract_vertex(vpar,v)
Return vpar with vertex v contracted out of the tree.
v is deleted and its children are adopted by v's parent.
If v is a root (no parent) then its children become roots.
vpar can contain cycles. If v is a self-loop then its children become
roots, because their grandparent is v which is deleted.
This is similar to vpar_delete_vertex(), but children are adopted by their
grandparent when they have one, rather than always becoming roots.");
}
vpar_branch_reduce(vpar) =
{
\\ vec[v]=1 when want to keep.
\\ Delete is when have vpar[v] parent and num_children[v]==1.
\\
\\ At a vertex kept but have parent and that parent is delete,
\\ search upwards for higher kept vertex, which is new parent for v.
\\
\\ This search is safe to cycles because either v is in the cycle and at
\\ worst will arrive back to v being kept, or v not in the cycle and the
\\ bottom of the cycle has 1 child up and 1 child looping and parent above
\\ so keep (including when a self-loop).
my(vec = vpar_INTERNAL_num_children_vecsmall(vpar));
for(v=1,#vec, vec[v] = !(vpar[v] && vec[v]==1)); \\ flag vec[v] keep
for(v=1,#vpar,
if(vec[v] && vpar[v] && !vec[vpar[v]],
my(p=vpar[v]);
while(p=vpar[p], if(vec[p], vpar[v]=p;break))));
vpar_keep_flags(vpar,vec);
}
{
addhelp(vpar_branch_reduce,
"vpar = vpar_branch_reduce(vpar)
Return vpar branch reduced. This means all vertices of with a parent and
1 child are contracted out (per vpar_contract_vertex()). For example,
p
/ \\
a contract out a,b
\\ c becomes child of p
b
\\
c
/ \\
vpar can contain cycles. A cyclic vertex with nothing else below it has
parent and 1 child (the cycle coming back around) and is contracted out.
A cycle with nothing else at all below is deleted entirely. A cycle with
one vertex having further below will leave that as a self-loop.");
}
vpar_free_branch_reduce(vpar) =
{
\\ print("vpar_free_branch_reduce() "vpar);
\\ vec[v] = flag whether to keep v
my(keep = vpar_INTERNAL_num_children_vecsmall(vpar));
for(v=1,#vpar, keep[v] = keep[v]!=if(vpar[v],1,2));
\\ At keep[v] but not keep[p] its parent, search upwards for new keep
\\ parent for v.
\\ If reach p root and still no keep seen, then it is like
\\ root not keep
\\ / \
\\ keep1 keep2
\\ For v=keep1, set keep[p]=-v ready to tell keep2.
\\ For v=keep2, will find root keep < 0 which is -keep1 new parent for
\\ keep2, then clear back to keep[root]=0.
\\ Using negatives in keep[] means don't need an extra array to remember.
for(v=1,#vpar,
if(keep[v]>0,
my(p=vpar[v]);
if(p && keep[p]<=0,
\\ print(" v="v" seek new parent");
while(!keep[p],
if(vpar[p],
p=vpar[p], \\ look further up
keep[p]=-v; next(2))); \\ remember keep1
if(keep[p]>0, vpar[v]=p, \\ found ancestor
vpar[v]=-keep[p]; keep[p]=0)))); \\ prev "keep1"
\\ print(" keep "Vec(keep));
vpar_keep_flags(vpar,keep);
}
{
addhelp(vpar_free_branch_reduce,
"vpar = vpar_free_branch_reduce(vpar)
Return vpar branch reduced as a free tree. This means all degree=2 vertices
are contracted out by deleting then re-attaching the two sides by an edge.
A chain up like k1 -> d1 -> d2 -> d3 -> k2 deletes d1,d2,d3, keeps k1 and
k2, and sets k1 to have parent k2 (the same as rooted vpar_branch_reduce()).
A fork up to a degree=2 like
k1 -> d1 -> d3
k2 -> d2 --^
deletes d1,d2,d3, and then sets the smaller vertex number k1 as parent of
bigger vertex number k2.
vpar can contain cycles. A cyclic vertex, including a self-loop, with
nothing else below is degree 2 and is contracted out. A cycle with just one
having further below will delete all the others, leaving it a self-loop.");
}
vpar_reverse_cycles(vpar) =
{
\\ Search upwards from each vertex i, similar to vpar_is_cyclic().
\\ seen[v]=i for the vertices traversed.
\\ If reach seen[v] v2 -> v3 becomes v1 <- v2 <- v3
^----------+ +----------^
A vpar without cycles is returned unchanged.");
}
vpar_transpose(vpar,opp=0) =
{
\\ Knuth 4A 7.2.1.6 exercise 12, draft fasc4a.ps page 31.
\\ F^T "transpose" of ordered forest.
\\
\\ Horizontal sets of siblings become vertical chains up. This is
\\ prev_sibling vector.
\\ Vertical chains up of first sibling each time become horizontal set of
\\ siblings. These first siblings are where prev_sibling[v]==0.
\\ Their mutual parent is the prev_sibling of the top of the chain
\\ (ie. where not first sibling), or 0 if top is the first root.
\\ Work up from each prev_sibling[v]==0 to find its top prev_sibling[p]!=0.
\\ A chain up might be taken in two parts, up from a certain point and later
\\ up from somewhere lower. In both cases ret[p] is the desired parent.
\\ The first one has ret[p] = prev_sibling, and the second has ret[p] =
\\ parent which is to be copied.
\\
\\ The chains loop is run high to low in slight expectation that the code
\\ will be used on downwards labelled vpar, and that therefore each chain
\\ will be taken in one part.
\\ print("vpar_transpose() "vpar);
my(ret=vpar_next_sibling_vector(vpar,1-opp)); \\ opp=0 prev sibling
\\ print(" prev_sibling "ret);
forstep(i=if(opp,1,#vpar), if(opp,#vpar,1), (opp<<1)-1,
if(!ret[i],
\\ print(" i="i);
my(p=i, t);
while((p=vpar[p]) && !(t=ret[p]), );
\\ print(" parent at p="p" t="t);
my(v=i);
until((v=vpar[v])==p, ret[v]=t)));
\\ print(" ret "ret);
ret;
}
{
addhelp(vpar_transpose,
"vpar = vpar_transpose(vpar,opp=0)
Return vpar with each vertex smallest child and next sibling swapped, as a
transpose across a leading diagonal down from the first root.
At each vertex v, smallest child v moves up to be a sibling of v, and what
had been next sibling of v moves to child of v. Roots are reckoned siblings
of each other. The effect is horizontal siblings across become vertical
child chains down, and vice versa. In the natural correspondence of ordered
forests to binary trees, first child is binary left and next sibling is
binary right, so transpose is left<->right mirror in the binary tree.
Transpose twice gives the original vpar if and only if vpar_is_downwards().
Downwards ensures successive first children are increasing so their order is
maintained when made successive siblings. If not then the second transpose
sends them to a different downward chain so different vpar.
For all vpar, vpar_horizpos_vector() of the original is vpar_depths_vector()
of the transpose. But only downwards forests are converse
vpar_depths_vector() of the original is vpar_horizpos_vector() of the
transpose.
Optional parameter \"opp\" can be 1 to transpose across the opposite
diagonal. At each vertex, biggest child and previous sibling swap, as for a
mirror across a diagonal going down from the last root at top-right. This
is equivalent to transpose with vertex numbers reversed, then un-reversed,
vpar_transpose(vpar,1)
== vpar_relabel_reverse(vpar_transpose(vpar_relabel_reverse(vpar),1))
Opposite transpose twice returns to the original vpar if and only if
vpar_is_upwards().
See examples/transpose.gp for some sample transposes.");
}
\\-----------------------------------------------------------------------------
\\ Relabelling
vpar_relabel_swap(vpar,u,v) =
{
\\ use u,v to validate them as indices (guards against bad vpar output)
vpar[u]; vpar[v];
vector(#vpar,i,
if(i==u,i=v, i==v,i=u); \\ swapped for lookup of p
my(p=vpar[i]);
if(p==u,v, p==v,u, p)); \\ swapped target parent
}
{
addhelp(vpar_relabel_swap,
"vpar = vpar_relabel_swap(vpar,u,v)
Return vpar with its vertices relabelled to swap u and v.
This means the children of u become children of v, and the parent of u
becomes parent of v, and vice versa v children and parent.
If u==v then no change. vpar can contain cycles.");
}
vpar_relabel_perm(vpar,perm) =
{
my(new_vpar=vpar);
for(v=1,#vpar,
new_vpar[perm[v]] = if(vpar[v],perm[vpar[v]]));
new_vpar;
}
{
addhelp(vpar_relabel_perm,
"vpar = vpar_relabel_perm(vpar,perm)
perm is a permutation of the integers 1..#vpar.
Return vpar with its vertex numbers permuted so vertex v becomes perm[v].
perm can be Vecsmall which is the usual representation of a permutation,
or perm can be full Vec which is what for example numtoperm() gave before GP
2.11. vpar can contain cycles.
Successive permutation relabellings
vpar = vpar_relabel_perm(vpar, a)
vpar = vpar_relabel_perm(vpar, b)
are equivalent to pre-multiply b*a (of Vecsmalls)
vpar = vpar_relabel_perm(vpar, b*a)");
}
vpar_relabel_seq(vpar,seq) =
{
my(perm=Vecsmall(seq)^-1);
vector(#vpar,v, my(p=vpar[seq[v]]); if(p,perm[p],0));
}
{
addhelp(vpar_relabel_seq,
"vpar = vpar_relabel_seq(vpar,seq)
Return vpar relabelled in sequence seq. So vertex number seq[1] becomes 1,
vertex seq[2] becomes 2, etc.
seq can be Vecsmall which is the usual representation for a sequence
(since it is a permutation of 1..#vpar), or seq can be full Vec.
vpar can contain cycles.
This is the same as inverse permute vpar_relabel_perm(vpar,seq^-1).
\"perm\" says where each vertex 1,2,... is to go, whereas \"seq\" is which
vertex becomes 1,2,... In the current implementation, the perm form is more
efficient, since vpar[v] parent numbers are re-mapped directly by perm.");
}
\\ Return a uniformly distributed random permutation of the integers 1..n,
\\ as a Vecsmall as usual for a perm.
vpar_INTERNAL_random_perm(n) =
{
\\ Could numtoperm(#vpar,random(n!)), but for large n that would be a lot
\\ of multiplying and dividing. Think repeated random() usually better.
\\ first i=1 is n-1+1=n random 0 to n-1 add to i for 1 to n
\\ last i=n-1 is n-(n-1)+1=2 random 0 or 1 add to i for n-1 or n
\\
my(perm=vpar_INTERNAL_identity_perm(n));
for(i=1,n-1, my(j=i+random(n-i+1), t=perm[i]); perm[i]=perm[j]; perm[j]=t);
perm;
}
vpar_relabel_random(vpar) = \
vpar_relabel_perm(vpar, vpar_INTERNAL_random_perm(#vpar));
{
addhelp(vpar_relabel_random,
"vpar = vpar_relabel_random(vpar)
Return vpar with its vertex numbers randomly relabelled.
This is vpar_relabel_perm() with a uniformly distributed random permutation.
The possible different vpar returns occurring are uniformly distributed too,
because each different vpar is from the same number of permutations.
That number is the size of the automorphism group (since divide out the
automorphisms from all permutations).");
}
vpar_relabel_reverse(vpar) = \
vector(#vpar,v, my(p=vpar[#vpar+1-v]); if(p,#vpar+1-p,0));
{
addhelp(vpar_relabel_reverse,
"vpar = vpar_relabel_reverse(vpar)
Return vpar vertex numbers relabelled by reversal #vpar..1.
This is the same as vpar_relabel_perm(vpar,rev) with reversing permutation
rev = vectorsmall(#vpar,i,#vpar+1-i).
Reckoning vpar as an ordered tree (siblings ordered by vertex number), the
effect is a horizontal mirror image (reverse all sibling sets), though the
vertex numbers are changed. If vpar was preorder labelled
(vpar_is_preorder()) then it becomes postorder (vpar_is_postorder()).");
}
\\ Knuth fasc4a.ps exercise 11 "conjugate" F^R of ordered forest.
\\-----------------------------------------------------------------------------
\\ Re-Rooting
\\ Example program examples/reroot-path.gp not mentioned in the help as it
\\ seems a bit trivial.
\\
vpar_reroot(vpar,v) =
{
\\ ENHANCE-ME: What to do in a cycle? Leave unchanged, or error() ?
\\ p vpar[vpar[v]]
\\ \
\\ p vpar[v]
\\ \
\\ v
my(p=0); \\ desired parent for v
until(!v, my(new_v=vpar[v]); vpar[v]=p; p=v; v=new_v);
vpar;
}
{
addhelp(vpar_reroot,
"vpar = vpar_reroot(vpar,v)
Return vpar with edge directions changed so that v is a root.
Edge directions on the path from v up to its root are reversed.
If vpar is a forest then v becomes the root of its component tree and the
rest unchanged.
All vertices have their neighbouring vertex numbers unchanged but some may
switch from parent to child so that v is a root.
See vpar_reroot_forest() for rerooting multiple component trees of a forest
in one operation.");
}
vpar_reroot_forest(vpar,roots) =
{
for(i=1,#roots,
my(v=roots[i], p=0); \\ p = desired parent for v
while(v, my(new_v=vpar[v]); vpar[v]=p; p=v; v=new_v));
vpar;
}
{
addhelp(vpar_reroot_forest,
"vpar = vpar_reroot_forest(vpar,roots)
roots is a vector of vertex numbers. Return vpar with edge directions
changed so that each of roots is a root of the forest.
Each of roots should be in a different component tree. (This is not
enforced presently, but don't rely on that, yet).
This is the same as individual vpar_reroot() to reroot each component tree,
but here as a single operation.");
}
vpar_reroot_minmax(vpar,func) = \
vpar_reroot_forest(vpar, vpar_component_minmax(vpar,func));
{
addhelp(vpar_reroot_minmax,
"vpar = vpar_reroot_minmax(vpar,func)
Return vpar rerooted so that the minimum or maximum vertex in each component
tree is the root of that tree.
\"func\" parameter is min for minimum or max for maximum,
vpar = vpar_reroot_minmax(vpar,min)
vpar = vpar_reroot_minmax(vpar,max)
This is simply vpar_reroot_forest(vpar, vpar_component_minmax(vpar,func)).
See vpar_is_root_minmax() to test for vpar in this form.");
}
vpar_reroot_random(vpar) = \
vpar_reroot_forest(vpar, vpar_component_random_vertices(vpar));
{
addhelp(vpar_reroot_random,
"vpar = vpar_reroot_random(vpar)
Return vpar with edge directions changed to re-root to a random vertex in a
tree or to a random set of vertices in a forest.
This is vpar_reroot_forest(vpar, vpar_component_random_vertices(vpar)).
The component randoms are a uniform random choice among the vertices of
their component trees so the rerooting is to a uniform choice among all
different vpar which can result from rerootings (vpar_num_rootings()).");
}
\\-----------------------------------------------------------------------------
\\ Making Particular Trees
vpar_make_path(n) = vector(n,v,v-1);
{
addhelp(vpar_make_path,
"vpar = vpar_make_path(n)
Return a vpar which is a path of n vertices.
Vertex 1 is the root and the rest are in sequence downwards 2 to n.
1<--2<--3<--4 path n=4
If n==0 then return the empty tree.");
}
vpar_make_star(n) = vector(n,v, v!=1);
{
addhelp(vpar_make_star,
"vpar = vpar_make_star(n)
Return a vpar which is a star of n vertices.
The root vertex 1 is the centre and the rest are its children.
1
^^^
/ | \\ star n=4 (\"claw\")
2 3 4
If n==0 then return the empty tree.
Having 1 as the root allows that centre to be attached under another tree
say. Re-rooting to 2 can make an attachment at an arm instead of centre.");
}
vpar_make_bistar(n,m) = vector(n+m,v, if(v==1,0, v<=n,1, v==n+1,1, n+1));
{
addhelp(vpar_make_bistar,
"vpar = vpar_make_bistar(n,m)
Return a vpar which is bi-star n,m.
This is a star-n and star-m with an edge between, so n+m vertices.
The first n vertices are the same as vpar_make_star() and the rest a further
star-m with centre at n+1 and children n+2 through n+m inclusive.
For example,
1
^^^ bi-star n=3, m=4
/ | \\
2 3 4
^^^
/ | \\
5 6 7
1,2,3 is the first star. 4,5,6,7 is the second star. Its middle 4 has an
edge up to the middle 1 of the first star.");
}
vpar_make_complete_tree(c,h) =
{
my(vpar=vector((c^(h+1) - 1)/(c-1)),
width=1,v=1,p=1);
for(i=1,h,
for(j=1,width,
for(k=1,c, vpar[v++]=p); \\ pre-increment
p++);
width*=c);
vpar;
}
{
addhelp(vpar_make_complete_tree,
"vpar = vpar_make_complete_tree(c,h)
Return a vpar which is a complete c-ary tree of height h.
Each vertex has c many children (or none at the deepest), so for example c=2
is a complete binary tree.
The root vertex is 1 and the children are successive rows below.
Height 0 means the root vertex and nothing more.");
}
vpar_make_binomial_tree(n) = vector(n,v, if(v>1, v - (1<0))+(forest<=0)) || vpar[v]>=0,
while(vpar[upto++]>=0, ); upto, \\ pre-increment
v)] = prev;
prev=v);
vpar;
}
{
addhelp(vpar_make_random,
"vpar = vpar_make_random(n,forest=0)
Return a random vpar tree of n vertices.
Optional parameter \"forest\" can be 1 to make a random forest, or can be -1
to make a random root=1 tree.
In each case the return is a uniformly distributed choice among the
respective labelled rooted trees, or forests, or root=1 trees.
See vpar_count() for the number of each such.
The current implementation is based on vpar_from_successive_paths() applied
to random vec terms. See comments in the code for some notes.");
}
\\-----------------------------------------------------------------------------
\\ Pre-Order and Post-Order Sequence
vpar_INTERNAL_seq_porder(vpar,down,post) =
{
\\ Return sequence of vertices in pre-order or post-order.
\\ Combinations are
\\ post=1 down=0 pre-order
\\ post=0 down=1 pre-order down=1
\\ post=0 down=0 post-order
\\ post=1 down=1 post-order down=1
\\
\\ For post=1,
\\ child[v+1] = smallest number child of vertex v, or 0 if no child.
\\ child[0+1] = smallest root, imagining an extra vertex v=0 above the roots.
\\ sibling[v] = next bigger sibling of vertex v, or 0 if no bigger sibling
\\ (so a linked list of siblings).
\\ For post=0,
\\ child[v+1] = biggest child
\\ sibling[v] = prev sibling
\\
\\ The child[],sibling[] vectors are traversed pre-order in all cases.
\\ For pre-order, which is post!=down, store seq[] start to end.
\\ For post-order, which is post==down, store seq[] end to start.
\\ print("vpar_INTERNAL_seq_porder() "vpar" down="down" post="post);
my(child=vectorsmall(#vpar+1),
sibling=child,
upto=if(post,#vpar,1),
end=if(post,1,#vpar),
step=1-(post<<1)); \\ +1 if post=0, -1 if post=1
forstep(v=upto,end,step,
my(p=vpar[v]);
sibling[v]=child[p+1]; \\ next sibling
child[p+1] = v); \\ first child
\\ print(" child "Vec(child));
\\ print(" sibling "Vec(sibling));
if(!down, \\ symbol-ness is in "post" too and picked up there
step = -step;
upto = end);
upto -= step;
\\ print(" step="step);
my(seq=vectorsmall(#vpar),
v=0);
while(1,
my(c=child[v+1]);
\\ print(" at v="v" upto="upto" child "c);
if(c,
\\ print(" descend to "c" and sibling "sibling[c]);
seq[upto+=step] = c;
child[v+1] = sibling[c];
v=c;
,
if(!v,break);
\\ print(" ascend to "vpar[v]);
v=vpar[v]));
\\ print(" upto="upto" seq "Vec(seq));
seq;
}
vpar_seq_preorder(vpar,down=0) = \
vpar_INTERNAL_seq_porder(vpar,down,1-down);
{
addhelp(vpar_seq_preorder,
"seq = vpar_seq_preorder(vpar,down=0)
Return a Vecsmall of the vertex numbers 1..#vpar in pre-order sequence.
The return is Vecsmall since it is a permutation of the integers 1..#vpar.
Pre-order is vertex v followed by pre-order of its smallest child vertex
number subtree, then next bigger child vertex subtree, etc.
v |-------| |------| ... |------|
first second last
child child child
subtree subtree subtree
vpar can be a forest, in which case each root is by pre-order and in
ascending order of roots (as if an extra parent vertex above them).
seq is downwards (vpar_seq_is_downwards()) since parents precede children.
Siblings are in ascending order (vpar_seq_is_ordered()).
An equivalent definition is sort by lex() of path vectors [root,...,v] as
from Vecrev(vpar_ancestors(vpar,v)). A shorter but otherwise equal path is
lex smaller thereby sorting a vertex before its descendants.
Optional parameter \"down\" can be 1 to instead take child subtrees in
descending order, from biggest child to smallest. This is still
vpar_seq_is_downwards(), but sibling order is in reverse. Keys for sorting
are [-root,...,-v] so child order reversed, but still v before descendants.
down=1 is the same as plain vpar_seq_postorder() with that seq taken last to
first, though that is subtler to think about.");
}
vpar_seq_postorder(vpar,down=0) = \
vpar_INTERNAL_seq_porder(vpar,down,down);
{
addhelp(vpar_seq_postorder,
"seq = vpar_seq_postorder(vpar,down=0)
Return a Vecsmall of the vertex numbers 1..#vpar in post-order sequence.
The return is Vecsmall since it is a permutation of the integers 1..#vpar.
Post-order is smallest child vertex number subtree in post-order, then next
bigger child vertex number subtree in post-order, and so on until after all
of them finally vertex v itself.
|-------| |------| ... |------| v
first second last
child child child
subtree subtree subtree
vpar can be a forest, in which case each root post-order in ascending order
of roots (as if there was an extra parent vertex above them).
seq is upwards per vpar_seq_is_upwards() since child precedes parent.
Siblings are in ascending order so vpar_seq_is_ordered().
An equivalent definition is sort by lex() of a path-like [root,...,v, BIG]
where extra BIG=#vpar+1. BIG causes v itself to sort as bigger than paths
to its descendants, so visit v after them.
Optional parameter \"down\" can be 1 to take children in descending order,
so subtrees from biggest child vertex to smallest. This is still
vpar_seq_is_upwards(), but sibling order is in reverse. Sort keys become
[-root,...,-v, +BIG] so child order is reversed, but still +BIG so v after
descendants. down=1 is the same as plain vpar_seq_preorder() with that seq
taken last to first, though that is subtler to think about.");
}
\\ To test for preorder, suppose vertices 1 to v-1 inclusive are preorder,
\\
\\ z *
\\ | \
\\ y
\\ | \
\\ x
\\ | \
\\ v-1
\\ \ check vpar[v] == v-1 or some ancestor
\\ v incl 0 for v is a root
\\
\\ Vertex v can have as its parent v-1, or some ancestor x,y,z of v-1, or
\\ parent 0 as a new root. When v==1 the 1 to v-1 is empty and only Parent
\\ 0 to be a new root is permitted.
\\
\\ Since vertices 1 to v-1 are known to be a preorder forest, the search up
\\ from v-1 will reach a root so the loop is safe to v possibly being in a
\\ cycle or some wild value. The effect is that any cycle is not preorder,
\\ but don't want to guarantee that yet.
\\
\\ ENHANCE-ME: Think probably cyclics should not be reckoned preorder.
\\ Could take the smallest of the cycle as the start, even though it has a
\\ parent (a bigger vertex number), but not sure that would be useful.
\\ Maybe in vpar_seq_preorder() it would at least be a definite rule.
\\
\\ For postorder, similarly but from the end so v+1..n are postorder and v
\\ is to have as its parent v+1 or an ancestors of v+1 or 0 for new root.
\\ In the code, p is the previous v-1 or v+1 and the search goes up from it.
\\
\\ Each vertex is 1 deeper and each search up goes 1 shallower, so the
\\ search steps are total #vpar - depth[final vertex], so linear in #vpar.
\\ (They go like the 0s in vpar_to_balanced_binary().)
\\
vpar_INTERNAL_is_porder(vpar,start,end,step) =
{
my(p=0);
forstep(v=start,end,step,
while(vpar[v]!=p,
p || return(0); \\ at root, vpar[v] not ancestor of prev v
p=vpar[p]);
p=v);
1;
}
vpar_is_preorder(vpar) = vpar_INTERNAL_is_porder(vpar,1,#vpar,1);
{
addhelp(vpar_is_preorder,
"bool = vpar_is_preorder(vpar)
Return 1 if vpar is labelled in pre-order sequence, or return 0 if not.
This means vpar_seq_preorder(vpar) is simply 1..#vpar.
The test is vpar[1]==0 root then each non-root v must have its vpar[v] =
either v-1 or an ancestor of v-1 (vpar_is_ancestor(vpar,v-1,vpar[v])).
Pre-order is a canonical form for an ordered tree so the number of preorder
vpar is vpar_count_ordered() (the Catalan numbers).");
}
vpar_is_postorder(vpar) = vpar_INTERNAL_is_porder(vpar,#vpar,1,-1);
{
addhelp(vpar_is_postorder,
"bool = vpar_is_postorder(vpar)
Return 1 if vpar is labelled in post-order sequence, or return 0 if not.
This means vpar_seq_postorder(vpar) is simply 1..#vpar.
The test is vpar[#vpar]==0 root then each non-root v must have as its parent
either v+1 or an ancestor of v+1 (vpar_is_ancestor(vpar,v+1,vpar[v])).
Post-order is a canonical form for an ordered tree so the number of preorder
vpar is vpar_count_ordered() (the Catalan numbers).");
}
\\-----------------------------------------------------------------------------
\\ Upascend Sequence (Prufer, Neville)
\\ Prufer, "Neuer Beweis Eines Satzes ber Permutationen",
\\ Arch. Math. Phys. 2, 742-744, 1918.
vpar_seq_upascend(vpar,ahead=0) =
{
my(num_children=vpar_INTERNAL_num_children_vecsmall(vpar),
seq=vectorsmall(#vpar),
upto=0);
for(i=1,#vpar,
my(v=i);
while((ahead || v<=i) && num_children[v]==0,
seq[upto++]=v; \\ pre-increment
num_children[v]=-1; \\ no revisit "ahead"
(v=vpar[v]) || break; \\ to parent or stop at root
num_children[v]--));
seq;
}
{
addhelp(vpar_seq_upascend,
"seq = vpar_seq_upascend(vpar,ahead=0)
Return a Vecsmall of the vertex numbers 1..#vpar in upwards ascending
sequence. The return is Vecsmall since it is a permutation of the integers
1..#vpar.
seq[1] is the smallest number childless vertex and it is then considered
removed from the tree. seq[2] is the smallest number childless in the
remaining and then considered removed, and so on.
This is an oriented version of the vertex order for Prufer's canonical free
tree representation, and Neville's first representation, and appears in
Knuth volume 1 section 2.3.4.4.
Optional parameter \"ahead\" can be 1 for a variation taking parents
immediately if possible. When v is added to the sequence and considered
removed, if its parent p becomes childless then it is taken next, even if
not the smallest childless. This is Neville's third form.
In both cases these are upwards sequences (vpar_seq_is_upwards()) since
children precede their parent.
An equivalent definition is to sort vertex numbers in ascending order of
their maximum descendant (inclusive of self), and among equal maximum
descendant by decreasing depth (so up the tree). For ahead=1 consider only
childless descendants.");
}
vpar_from_upascend(vec,ahead=0) =
{
\\ This code is in the manner of Cho et al,
\\
\\ Cho, Kim, Seo, Shin, "Coloured Pruufer Codes For K-Edge Coloured
\\ Trees", Electronic Journal of Combinatorics, volume 11, #N10, 2004.
\\ http://mathsci.kaist.ac.kr/~dskim/
\\ http://mathsci.kaist.ac.kr/~dskim/papers/Tree.pdf
\\ (Seo http://sites.google.com/view/shyunseo/ )
\\
\\ and given also in
\\
\\ Seo and Shin, "A Generalized Enumeration of Labelled Trees and
\\ Reverse Prufer Algorithm", 2005.
\\ http://arxiv.org/abs/math/0601009
\\ Journal of Combinatorial Theory Series A, volume 114, number 7,
\\ 2007, pages 1357-1361.
\\ http://www.sciencedirect.com/science/article/pii/S0097316507000210
\\
\\ The loop goes high to low. Vertex vec[i] gets parent vec[i+1].
\\ If vec[i]==0 or vec[i] already has a parent then look from #vec
\\ downwards for the biggest vertex which doesn't yet have a parent
\\ decided, and it gets parent vec[i+1].
\\
\\ For ahead=1 option, look downwards for the biggest childless vertex
\\ which doesn't yet have a parent decided.
\\
\\ vpar[] is initialized to -1 meaning parent not yet decided.
\\ For ahead=1 option, set -2 for a vertex with any children.
\\ This means the "search downwards" of "upto" is looks for -1.
\\
\\ As Paulden and Smith note,
\\
\\ Tim Paulden and David K. Smith, "Developing New Locality Results for
\\ the Prufer Code Using a Remarkable Linear-Time Decoding Algorithm",
\\ The Electronic Journal of Combinatorics, volume 14, 2007, research
\\ paper R55.
\\ http://www.combinatorics.org/ojs/index.php/eljc/article/view/v14i1r55
\\
\\ the algorithm is "online" in the sense that it only needs a pairs of
\\ values vec[i] and vec[i+1] successively, not random access to vec. The
\\ attraction here too is no workspace needed, only the output vpar array.
\\
\\ MAYBE: Perhaps the ahead=1 case could build a linked list of childless,
\\ so don't have to search "upto". Suspect would be the same or more work
\\ to create.
\\
\\ For comparision, test_from_upascend_by_num_children() is some code
\\ based on building num_children.
\\ First linear time algorithm in "Combinatorial Algorithms" 1978 exercise
\\ ...
\\ print("vpar_from_upascend() ahead="ahead" vec="vec);
my(vpar=vector(#vec,v,-1));
if(#vpar,
if(ahead, \\ set to childless -1, have children -2
for(i=1,#vec, if(vec[i], vpar[vec[i]]=-2)));
my(upto=#vpar+1); \\ ready for pre-decrement
forstep(i=#vpar-1,1,-1,
my(v=vec[i]);
\\ print(" at i="i" v="v" upto="upto" vpar="vpar);
vpar[if(!v || vpar[v]>=0,
while(vpar[upto--]!=-1, ); upto, \\ next smaller
v)] = vec[i+1]);
\\ print("final upto="upto" "vpar);
while(vpar[upto--]!=-1, ); \\ next smaller
vpar[upto] = vec[1]);
vpar;
}
{
addhelp(vpar_from_upascend,
"vpar = vpar_from_upascend(vec,ahead=0)
Return vpar from a vector of parents in upascend order, as from
vec=vecextract(vpar, vpar_seq_upascend(vpar,ahead)).
Optional parameter \"ahead\" is for a vec which is a vecextract of a
corresponding ahead=1 vpar_seq_upascend().");
}
\\------------------------------------------------------------------------------
\\ Upqueue Sequence (Deo and Micikevicius)
\\ L leaves by vertex number
\\ F h=1 by biggest child
\\ M h=2 by F order last
\\
\\ Also used in Geir Agnarsson, Narsingh Deo, Paulius Micikevicius, "On the
\\ Expected Number of Level-i Nodes in a Random Labeled Tree".
vpar_seq_upqueue(vpar) =
{
\\ print("vpar_seq_upqueue() "vpar);
my(num_children=vpar_INTERNAL_num_children_vecsmall(vpar),
seq=vectorsmall(#vpar),
qhead=0,
qtail=0);
for(v=1,#vpar,
if(num_children[v]==0,
seq[qhead++]=v));
while(qtail>1, \\ print("swap "prev+j" and "upto-j);
my(t=seq[prev+j]); seq[prev+j]=seq[upto-j]; seq[upto-j]=t);
prev=upto+1);
);
seq;
}
{
addhelp(vpar_seq_successive_paths,
"seq = vpar_seq_successive_paths(vpar,down=0)
Return a Vecsmall of vertex numbers 1..#vpar in sequence of paths up to the
root or vertex already seen. The return type is Vecsmall since it is a
permutation of the integers 1..#vpar.
The first path is 1 up to the root, inclusive. The next path is 2 up to the
highest vertex not yet seen. Or if 2 is already seen on the first path then
nothing for it. Then 3 upwards vertices not yet seen, etc. vpar can be a
forest, in which case each vertex up to whichever root or unseen in their
component tree.
An equivalent definition is to sort vertex numbers in ascending order of
their smallest descendant, and among equal smallest descendant by descending
depth so upwards to the root (or ascending subtree height the same).
Optional parameter \"down\" can be 1 to take each path downwards instead.
So first path root down to 1. Then the highest unseen ancestor of 2 down to
2, and so on. This downwards form is the vertex order of
Tamas Fleiner, \"On Prufer Codes\", Egervary Research Group Technical
Report TR-2005-16. http://web.cs.elte.hu/egres/tr/egres-05-16.pdf
down=1 of a forest is a downwards sequence per vpar_seq_is_downwards().
down=0 is neither upwards nor downwards, in general.
vpar can contain cycles. A path up marks successive vertices as seen, so
around a cycle stop when already seen. For down=1, the upward path is
reversed so goes down from the cycle stop.");
}
vpar_from_successive_paths(vec,down=0) =
{
\\ See also vpar_make_random() which uses down=1 with vec[] entries
\\ generated by random().
down = if(down,1,-1);
my(vpar = vector(#vec,v,-1),
upto = 0);
for(i=1,#vec,
my(j=i+down);
my(v=if(j>=1&&j<=#vec,vec[j],0));
while(!v || vpar[v]>=0, v=upto++);
vpar[v]=vec[i]);
vpar;
}
{
addhelp(vpar_from_successive_paths,
"vpar = vpar_from_successive_paths(vec,down=0)
Return the vpar from a vector of parents in successive paths order, ie. as
from vec = vecextract(vpar, vpar_seq_successive_paths(vpar,down)).
Optional parameter \"down\" should be the same as the seq from which vec was
created.
down=1 is a downwards sequence (parent before child), so root r =
vpar_root_of_vertex(vpar,1) is first and so vec[1]=0. If vpar is a tree
n>=1 then a child of r is next so vec[2]=r. Fleiner shows that these and
the rest of vec any 1..n for a tree or 0..n for a forest are one-to-one with
the labelled trees or forests of n vertices.
Tamas Fleiner, \"On Prufer Codes\", Egervary Research Group Technical
Report TR-2005-16. http://web.cs.elte.hu/egres/tr/egres-05-16.pdf
down=0 is also one-to-one, but the form taken by vec is not so simple.
Vertex 1 is first so vec[1] is the parent of 1. Parents are after children
in the upwards paths, but parents precede children when go to another
path.");
}
\\------------------------------------------------------------------------------
\\ Sequence Paths Childless to Root
vpar_seq_childless_paths(vpar,down=0) =
{
my(num_children=vpar_INTERNAL_num_children_vecsmall(vpar),
seq = vectorsmall(#vpar),
prev = 1,
upto = 0);
for(i=1,#vpar,
if(num_children[i]==0, \\ leaf
my(v=i,p);
while(v && num_children[v]>=0,
seq[upto++]=v; \\ pre-increment
num_children[v]=-1; \\ mark as seen
v=vpar[v]);
if(down, \\ print("i="i" prev "prev" upto "upto" seq "seq);
for(j=0,(upto-prev-1)>>1, \\ print("swap "prev+j" and "upto-j);
my(t=seq[prev+j]); seq[prev+j]=seq[upto-j]; seq[upto-j]=t);
prev=upto+1));
);
seq;
}
{
addhelp(vpar_seq_childless_paths,
"seq = vpar_seq_childless_paths(vpar,down=0)
Return a Vecsmall of vertex numbers 1..#vpar in sequence of paths up from
each childless vertex. The return type is Vecsmall since it is a
permutation of the integers 1..#vpar.
Suppose x is the smallest number childless vertex. The sequence begins with
x up to root, inclusive. Then the next smallest childless y up to the
top-most not yet in the sequence. And so on for each childless vertex.
vpar can be a forest, in which case still successive childless vertex
numbers, but up to whatever root or yet unseen in their component tree.
An equivalent definition is to sort vertices in ascending order of their
smallest childless descendant, and among those of equal smallest then by
decreasing depth so upwards towards the root.
Optional parameter \"down=1\" takes these paths downwards instead. So root
down to smallest childless x. Then top-most unseen down to next childless
y, etc. This down=1 form is the vertex order described in Knuth volume 1
section 2.3.4.4 exercise 18. The code Vq...Vk+1 etc there is
vecextract(vpar,seq), here keeping initial 0. See
vpar_from_childless_paths() to go from the extract back to vpar.
down=1 is downwards per vpar_seq_is_downwards().
down=0 is neither upwards not downwards in general.");
}
vpar_from_childless_paths(vec,down=0) =
{
\\ vpar[] is initialized to -1 = leaf, -2 = unseen.
\\ Later store vpar[v] >= 0 for v seen.
down = if(down,1,-1);
my(vpar=vector(#vec,v,-1),
upto = 0);
for(i=1,#vec, if(vec[i], vpar[vec[i]]=-2)); \\ childless -1, any child -2
for(i=1,#vec,
my(v, j=i+down);
if(j<1 || j>#vec || !(v=vec[j]) || vpar[v]>=-1, \\ no store seen or leaf
while(vpar[v=upto++]!=-1, )); \\ find next leaf
vpar[v]=vec[i]);
vpar;
}
{
addhelp(vpar_from_childless_paths,
"vpar = vpar_from_childless_paths(vec,down=0)
Return the vpar from a vector of parents in childless paths order, ie. as
from vec = vecextract(vpar, vpar_seq_childless_paths(vpar,down)).
Optional parameter \"down\" should be the same as the seq from which vec was
created.
down=1 is a downwards sequence, so root r of the smallest childless is first
and so vec[1]=0. If vpar is a tree n>=2 then the next vertex is a child of
r so vec[2]=r. This form is per Knuth section 2.3.4.4 exercise 18. The
exercise is to show that these initial values and further vec values 1..n is
one-to-one with the labelled trees of n vertices.
down=0 is also one-to-one, but the form taken by vec is not so simple. The
smallest childless is first so vec[1] is its parent. Parents are after
children in the upwards paths, but parents precede children when go to
another path.");
}
\\-----------------------------------------------------------------------------
\\ Blob Sequence
\\ Per Picciotto, blob code is an explicit bijection arising from Orlin's
\\ proof of Cayley's theorem.
\\
\\ ----
\\ Saverio Caminiti, "On Coding Labeled Trees", Ph.D. Thesis, 2007.
\\ http://wwwusers.di.uniroma1.it/~caminiti/bib/C07th.pdf
\\
\\ ----
\\ Paulius Micikevicius, Saverio Caminiti, Narsingh Deo, "Linear-Time
\\ Algorithms for Encoding Trees as Sequences of Node Labels",
\\ Congressus Numerantium, volume 183, 2006, pages 65-75.
\\ (URLs with vpar_seq_subtree_height() code.)
\\
\\ The code here roughly follows their section 3.4 algorithm "BLOB
\\ ENCODING". Their swap p(v) <-> last swaps downwards so that say at
\\ v=v1 put vpar[v1] <-> last with last=vpar[v2] from the preceding.
\\ This is a swap in the sense that last = vpar[v1] and want the original
\\ vpar[v1] to be the new last.
\\ Start is last=vpar[#vpar] the top vn vertex.
\\ Their algorithm is for root=1 and stops at v=2 so leave vpar[1]=0
\\ which is not part of the blob code output.
vpar_seq_blob(vpar) = \
vpar_INTERNAL_seq_rotate_down(vpar_minmax_ancestors_vector(vpar,max));
{
addhelp(vpar_seq_blob,
"seq = vpar_seq_blob(vpar)
Return a Vecsmall of the vertex numbers 1..#vpar in blob sequence.
The return is Vecsmall since it is a permutation of the integers 1..#vpar.
The sequence is a rotation of vertices which are bigger than all their
ancestors, ie. vpar_minmax_ancestor_of_vertex(vpar,v,max) == v.
Such vertices include the roots since they have no ancestors at all.
The vertices are rotated down 1 place within sequence 1..#vpar, so
r v1 v2 ... vn ascending vertices each v>ancestors
become v1 v2 ... vn r rotate, and others unchanged
Biggest is vn = #vpar since any ancestors it has are ancestors has a root with r=v. A vertex has its parent changed or not
according as its path to root intersects the blob or not. The blob coding
is the new parents. The seq returned here is the sequence of children which
give those new (or unchanged) parent.
Blob code is obtained by vec = vecextract(vpar, vpar_seq_blob(vpar)).
The rotation puts a root last so vec[#vpar]=0 always. Blob code is intended
for a tree with root r=1. In that case the first code entry is vec[1]=1
always. This is since position r=1 gets vertex v1 and it is the smallest
child of root r since any other v>ancestors would have a root child =m which is >u so children in order.
Order among roots is not necessarily ascending though, so have
vpar_seq_is_ordered(vpar,seq) only for a tree.");
}
vpar_from_blob(vec) =
{
\\ As noted in
\\
\\ Paulius Micikevicius, Saverio Caminiti, Narsingh Deo, "Linear-Time
\\ Algorithms for Encoding Trees as Sequences of Node Labels",
\\ Congressus Numerantium, volume 183, 2006, pages 65-75.
\\ (URLs with vpar_seq_subtree_height() code.)
\\
\\ rotation of vertices for blob code sequence makes its vec into a
\\ digraph, but the set of vertex numbers which have v>ancestors is
\\ unchanged (though their parents may now go into cycles).
\\
\\ So the code here is to establish above[v] = biggest ancestor above v,
\\ inclusive of v itself, in a cycle-safe way.
\\ Those with above[v]==v are to be unrotated from what vpar_seq_blob() did.
\\
\\ The sequence would be recovered by
\\ seq = vpar_INTERNAL_seq_rotate_down
\\ (vpar_minmax_ancestors_vector(vec,max))
\\ but want to inverse to use to unpermute vec, so can call
\\ vpar_INTERNAL_seq_rotate_up directly instead of inverting
\\ vpar_INTERNAL_seq_rotate_down()^-1.
vecextract(vec, vpar_INTERNAL_seq_rotate_up
(vpar_minmax_ancestors_vector(vec,max)));
}
{
addhelp(vpar_from_blob,
"vpar = vpar_from_blob(vec)
Return the vpar of a vector of parents in blob code order, ie. as from
vec = vecextract(vpar, vpar_seq_blob(vpar)).
Blob sequence has a root last, so has final vec[#vec]==0 always. Other
entries vec[1..#vec-1] are in the range 1..n for tree or 0..n for forest.
All labelled oriented trees (or forests) of n vertices are in one-to-one
correspondence with vecs of this form.
Trees n>=2 vertices have vec[1]=1 if and only if vertex 1 is the root.
Forests have vec[1] = 0 or 1 if and only if vertex 1 is a root. These are
since a root 1 makes the blob sequence start with either the smallest child
of that root (so parent 1) or another root (so parent 0).");
}
\\ Return sequence of 1..#vpar with those vertices having vec[v]==v
\\ rotated either up or down. Vertices with vec[v]!=v are not moved so
\\ they have seq[v]=v.
\\
vpar_INTERNAL_seq_rotate_down(vec) =
{
\\ seq[] starts as no-change permutation, then rotate so that
\\ r v1 v2 ... vn
\\ becomes v1 v2 ... vn r
\\ The loop goes high to low, storing r and changing r to vn etc downwards.
\\
my(seq=vpar_INTERNAL_identity_perm(#vec),
prev=0);
for(v=1,#seq,
if(vec[v]==v,
if(prev,
seq[v]=seq[prev]; seq[prev]=v);
prev=v));
seq;
}
vpar_INTERNAL_seq_rotate_up(vec) =
{
\\ seq[] starts as no-change permutation, then rotate so that
\\ v1 v2 ... vn r
\\ become r v1 v2 ... vn
\\ The loop goes low to high, storing r and changing r to vn etc downwards.
\\
my(seq=vpar_INTERNAL_identity_perm(#vec),
prev=0);
forstep(v=#vec,1,-1,
if(vec[v]==v,
if(prev,
seq[v]=seq[prev]; seq[prev]=v);
prev=v));
seq;
}
\\ r v1 v2 ... vn each v>ancestors
\\ given parent p max ancestor m
\\ some children p
\\ v2 = smallest >p
\\ are any v1+1 .. v2-1 also children of p ?
\\
\\ 2 8 v1
\\ | / \
\\ 3 7 9
\\ v0 v2
\\
\\ 2, 3, 8, 9
\\ if v2 not a root, then preceding v1 is an ancestor of v2, possibly the root
\\ so other children of p are < that v1 so rotating v2 down to v1 does not
\\ put it before other children
\\------------------------------------------------------------------------------
\\ Sequence General Purpose
vpar_seq_is_upwards(vpar,seq) =
{
my(seen=vectorsmall(#vpar), v);
for(i=1,#seq,
seen[v=seq[i]]=1;
if((v=vpar[v]) && seen[v], return(0))); \\ if parent already seen
1;
}
{
addhelp(vpar_seq_is_upwards,
"bool = vpar_seq_is_upwards(vpar,seq)
Return 1 if sequence seq goes upwards in vpar, so children are somewhere
before their parent. Return 0 if not.
Any seq through an empty vpar or vpar of all singletons is upwards since
there are no children at all.");
}
\\ PENDING: Should a self-loop be upwards? v its own parent. v in seq
\\ before v ...
\\
\\ vpar can contain cycles. If it contains a cycle length >= 2 then there
\\ are no upwards sequences at all since around the cycle there will be
\\ somewhere a parent smaller than child.
vpar_seq_is_downwards(vpar,seq) =
{
my(seen=vectorsmall(#vpar), v);
for(i=1,#seq,
seen[v=seq[i]]=1;
if((v=vpar[v]) && !seen[v], return(0))); \\ if parent unseen
1;
}
{
addhelp(vpar_seq_is_downwards,
"bool = vpar_seq_is_downwards(vpar,seq)
Return 1 if sequence seq goes downwards in vpar, so a parent is somewhere
before any of its children. Return 0 if not.
Any seq through an empty vpar or vpar of all singletons is downwards since
there are no children at all.");
}
\\ PENDING
\\ vpar can contain cycles. If it contains a cycle length >= 2 then there
\\ are no downwards sequences at all since around the cycle there will be
\\ somewhere a parent bigger than child.
vpar_seq_is_ordered(vpar,seq,down=0) =
{
\\ last[p+1] is the most recent child seen under vertex p, or for p=0 the
\\ more recent root seen.
\\ Each vertex must be > the last child seen under its parent. Initial
\\ last[p]=0 which is a dummy smaller than actual vertex v.
\\ For down=1 must be < the last child, and dummy #vpar+1 bigger than all.
\\ print("vpar_seq_is_ordered() "vpar" seq "seq" down="down);
down=if(down,#vpar+1); \\ up=0, down=#vpar+1 bigger than all
my(last=vectorsmall(#vpar+1,v,down));
down=if(down,1,-1); \\ up=-1, down=1
for(i=1,#seq,
my(v=seq[i], p=vpar[v]+1);
\\ print(" v="v" parent p+1="p" p="p-1" last "last[p]);
if(lex(v,last[p])==down,
\\ print(" out of order");
return(0));
last[p]=v);
\\ print(" is ordered");
1;
}
{
addhelp(vpar_seq_is_ordered,
"bool = vpar_seq_is_ordered(vpar,seq,down=0)
Return 1 if seq goes through vpar with siblings in ascending order of vertex
number. Return 0 if not.
Optional parameter \"down\" can be 1 to test for siblings in descending order.
For a forest, the roots are reckoned as siblings of each other.");
}
\\------------------------------------------------------------------------------
\\ Forvec Iteration
vpar_forvec_from(n,forest=0) =
{
\\ b = 1 for trees, 0 for forests, done with "<" operator to catch
\\ mistakenly passing a symbol 'forest or similar
my(b=(forest<1));
vector(n,i, if(i==n, [0,0], \\ last entry 0 always
forest==-1&&i==n-1, [1,1], \\ second last for root=1
[b,n]));
}
{
addhelp(vpar_forvec_from,
"vec = vpar_forvec_from(n,forest=0)
Return a vector of ranges suitable for use with forvec().
These ranges give forvec() looping over vecextracts occurring from an upward
sequence, so that vpar trees of n vertices can by iterated like
(and see examples/forvec-trees.gp for a complete program)
forvec(vec=vpar_forvec_from(4), \\\\ n=4 vertex trees
my(vpar=vpar_from_upascend(vec));
print(vpar));
Any of the upward vpar_from_...() can be used (giving trees in various
different orders). vpar_from_upascend(), vpar_from_successive_paths(),
vpar_from_childless_paths() and vpar_from_upqueue() have similar efficiency.
Optional parameter \"forest\" can be 1 for ranges for forests, or it can be
-1 for ranges of trees root=1.
The vec return gives the extract forms described in the vpar.gp introduction
comments. An upward sequence has last entry 0 so vec[n]=[0,0]. Other vec
elements are [1,n] for tree or [0,n] for forest. The 0 for forest gives
other roots in addition to the final [0,0]. For root=1 trees the
second-last is vec[n-1]=[1,1] since in an upwards sequence that is the root
vertex number.");
}
\\------------------------------------------------------------------------------
\\ Forest Components
\\ For roots=0, return the number of cycles.
\\ For roots=1, return the number of components, being cycles plus roots.
vpar_INTERNAL_num_cycles_or_components(vpar,roots) =
{
\\ At vertex i, search upwards looking for a vertex already seen or a root.
\\ While searching set seen[v]=i. If find seen[v]!=i then v is a vertex
\\ already searched up. If find seen[v]==i later then v is in a cycle.
\\ In both seen cases do a next(2) to skip the vpar[v]==0 loop end.
\\ vpar[v]==0 is a root not already seen, and is counted or not by adding
\\ the "roots" parameter.
my(seen=vectorsmall(#vpar),
ret=0);
for(i=1,#vpar,
my(v=i);
until(!(v=vpar[v]),
if(seen[v], ret+=(seen[v]==i); next(2));
seen[v]=i);
ret += roots);
ret;
}
vpar_num_components(vpar) = vpar_INTERNAL_num_cycles_or_components(vpar,1);
{
addhelp(vpar_num_components,
"num = vpar_num_components(vpar)
Return the number of connected components in vpar. vpar can have cycles.
The top of each component is either a root or a cycle so
vpar_num_components(vpar) == vpar_num_roots(vpar) + vpar_num_cycles(vpar)
vpar with no cycles (the usual case) is num components == num roots.");
}
vpar_compnum_vector(vpar) =
{
\\ compnum[v] = component number containing v, or 0 if not yet determined
\\ Follow v=1 up through ancestors to its root. This is component 1.
\\ Re-traverse filling that component number.
\\ For further vertices the search up stops on reaching a compnum[]
\\ already determined, and that fills downwards. If reach another root
\\ then "upto" counts the component number.
my(compnum=vector(#vpar),
upto=0);
for(i=1,#vpar,
if(!compnum[i],
my(v=i);
until(!(v=vpar[v]) || compnum[v], compnum[v]=-1); \\ search
my(c=if(v && compnum[v]>0, compnum[v], upto++)); \\ component number
v=i;
until(!(v=vpar[v]) || compnum[v]>0, compnum[v]=c))); \\ fill
compnum;
}
{
addhelp(vpar_compnum_vector,
"vec = vpar_compnum_vector(vpar)
Return a vector where each vec[v] is the component tree number containing v.
vpar can contain cycles.
Component trees are numbered from 1 up to vpar_num_components(vpar)
inclusive, in order of their smallest contained vertex. So component 1
contains vertex 1, component 2 contains the smallest of the remaining, etc.
When vpar is acyclic, vpar_num_components() == vpar_num_roots().
The number of different compnum vectors occurring over all forests is the
Bell numbers 1,1,2,5,15,52,203,... (A000110), being ways to partition
1..#vpar into sets (any number of sets).");
}
\\ vec is a vector of integers each vec[i] >= lo.
\\ Return a new Vecsmall histogram[1..max] where histogram[h] is the number
\\ of times vec[i] == h-1+lo occurs in vec.
\\ The start histogram[1] is count how many vec[i]==lo, and so on bigger h.
\\ Default lo=1 is histogram[1] count how many vec[i]==1.
\\
vpar_INTERNAL_histogram(vec,lo=1) =
{
lo--; \\ to make vector index starting at 1
my(histogram=vectorsmall(if(#vec,vecmax(vec)-lo)));
for(i=1,#vec, histogram[vec[i]-lo]++);
histogram;
}
vpar_component_sizes(vpar) = \
Vec(vpar_INTERNAL_histogram(vpar_compnum_vector(vpar)));
{
addhelp(vpar_component_sizes,
"vec = vpar_component_sizes(vpar)
Return a vector where each vec[c] is the size of component tree number c.
vpar can contain cycles.
Components are numbered 1 upwards in order of their smallest contained
vertex. The number of components is #vec == vpar_num_components(vpar).
Total sizes are vecsum(vec)==#vpar so vec is a composition of #vpar
(partition with order). All compositions occur by taking consecutive
vertices as component trees of respective sizes. The number of different
compositions is 2^max(0,n-1) = 1,1,2,4,8,... (A011782).
See vpar_component_vertices() for the vertex numbers in each component.
See vpar_num_rootings() for product of component sizes.");
}
\\ GP-DEFINE gf_terms(g,n) = Vec(g + O(variable(g)^n));
\\ GP-Test vector(5,n,n--; 2^max(0,n-1)) == [1,1,2,4,8]
\\ A011782 is offset 0 first term
\\ GP-DEFINE gA011782(x) = (1-x)/(1-2*x);
\\ GP-Test gf_terms((1-x)/(1-2*x),100) == vector(100,n,n--; 2^max(0,n-1))
vpar_component_vertices(vpar) =
{
my(compnum=vpar_compnum_vector(vpar),
component_sizes=vpar_INTERNAL_histogram(compnum),
vec=vector(#component_sizes,c, vector(component_sizes[c])));
forstep(v=#vpar,1,-1,
my(c=compnum[v]);
vec[c][component_sizes[c]] = v;
component_sizes[c]--);
vec;
}
{
addhelp(vpar_component_vertices,
"vec = vpar_component_vertices(vpar)
Return a vector of vectors [ [v1,v2,...], [v3,v4,...] ] which are the
vertex numbers in the component trees of vpar. vpar can contain cycles.
Components are ordered by their smallest vertex. So vec[1] is a vector
containing vertex 1, component vec[2] is a vector containing the smallest of
the rest, etc. In each element the vertices are in ascending order.
Lengths of the component vectors are per vpar_component_sizes().
The number of components is vpar_num_components(vpar).");
}
vpar_component_trees(vpar) =
{
\\ m[v] = new vertex number of v within its returned tree.
\\
my(compnum=vpar_compnum_vector(vpar),
component_sizes=vpar_INTERNAL_histogram(compnum),
ret=vector(#component_sizes,c,
vector(component_sizes[c])),
m=vectorsmall(#vpar));
forstep(v=#vpar,1,-1,
my(c=compnum[v]);
m[v] = component_sizes[c];
component_sizes[c]--);
for(v=1,#vpar,
if(vpar[v],
ret[compnum[v]][m[v]] = m[vpar[v]]));
ret;
}
{
addhelp(vpar_component_trees,
"vec = vpar_component_trees(vpar)
Return a vector of vpars which are the components of the given vpar.
vpar can contain cycles (even though the function name is \"trees\".)
Component vpars in the return vec are in order of the smallest vertex number
they had in the original vpar. The order of vertices in the components are
the same as from vpar, with other vertices collapsed out. The effect is
vertices like vpar_component_vertices() and keep those in each set per
vpar_keep_vertices().
The number of component trees is #vec == vpar_num_components(vpar).
An empty vpar==[] has no components and the return is empty vec=[].
See vpar_concat_forests() to concatenate back to a forest.");
}
vpar_component_minmax(vpar,func) =
{
\\ For max, when vpar=[] empty have func(1,#vpar) = 0 and #vpar-start+1 =
\\ 1 so forstep goes i=0,1 whereas want no loops when empty. Could fiddle
\\ with the start calculation but just as easy to skip entirely #vpar==0.
if(#vpar,
my(compnum=vpar_compnum_vector(vpar),
num_components=vecmax(compnum),
seen=vectorsmall(num_components),
ret=vector(num_components),
start=func(1,#vpar), \\ 1 for min, #vpar for max
step =-func(1,-1), \\ 1 for min, -1 for max
upto =func(0,#ret+1)); \\ position in ret[] for pre-inc by step
forstep(v=start, #vpar-start+1, step,
if(seen[compnum[v]]++==1,
ret[upto+=step] = v));
ret;
,
[]);
}
{
addhelp(vpar_component_minmax,
"vec = vpar_component_minmax(vpar,func)
Return a vector of the minimum or maximum vertex in each component tree of
vpar. vpar can contain cycles. \"func\" parameter is min for minimum or
max for maximum,
vec = vpar_component_minmax(vpar,min)
vec = vpar_component_minmax(vpar,max)
vec length is #vec == vpar_num_components(vpar). vec has the vertices
sorted in ascending order. So for min, vec[1]=1 always then vec[2] is the
smallest excluding the component containing 1, etc. This is the component
order of vpar_component_sizes() etc.
For max, vec again has vertices in ascending order so last entry
vec[#vec]=#vpar always, then vec[#vec-1] the biggest excluding the component
containing #vpar, etc.
Some forests have component mins in the same order as component maxes.
Such components are a \"strongly monotone partition\" of the vertices.
See vpar_count_strongly_monotone_forests() for the number of such forests.");
}
vpar_component_random_vertices(vpar) =
{
my(compnum=vpar_compnum_vector(vpar),
component_sizes=vpar_INTERNAL_histogram(compnum),
ret=vector(#component_sizes,i, -random(component_sizes[i])-1));
for(v=1,#vpar,
my(c=compnum[v]);
if(ret[c]<0, if(ret[c]++==0, ret[c]=v)));
ret;
}
{
addhelp(vpar_component_random_vertices,
"vec = vpar_component_random_vertices(vpar)
Return a vector of a random vertex from each component tree of vpar.
Each vertex is a uniform random choice among the vertices of the component.
vpar can contain cycles.
vec has components ordered by their smallest vertex in vpar. So vec[1] is a
random vertex from the component containing vertex 1, then vec[2] is from
the component containing the smallest of the rest, etc. The effect is the
same as a random vertex from each of vpar_component_vertices().");
}
\\------------------------------------------------------------------------------
\\ Strongly Monotone Forests
vpar_is_strongly_monotone_forest(vpar) =
{
\\ vpar_compnum_vector() gives component numbers ordered by min element.
\\ Check that going by max element is the same.
\\ The loop goes vertices high to low.
\\ c is the component number of biggest vertex. It is the biggest
\\ component number too for strongly montone.
\\ m is the smallest component number above v. A new smallest component
\\ number seen must be m-1, cannot step down by more than 1.
if(#vpar,
my(compnum=vpar_compnum_vector(vpar),
c = compnum[#vpar],
m = c);
forstep(v=#vpar-1,1,-1,
if(compnum[v] > c
|| (compnum[v] < m && compnum[v] != m--), \\ pre-decrement
return(0))));
1;
}
{
addhelp(vpar_is_strongly_monotone_forest,
"bool = vpar_is_strongly_monotone_forest(vpar)
Return 1 if vpar is a strongly monotone forest, or 0 if not.
Strongly monotone forest means the vertex numbers in the component trees are
a strongly monotone partition of 1..#vpar. This means ordering components
by minimum element is the same as ordering by maximum element. Or
equivalently no range nesting, so no component min to max is entirely inside
the min to max of some other component, rather all ranges are partly
overlapping or completely separate.");
}
\\ vpar_INTERNAL_strongly_monotone_transform() takes a vector "a" and
\\ returns a resulting vector of vectors
\\
\\ v = [ [x],
\\ [x,x],
\\ [x,x,x] ]
\\
\\ v[n][m] is count how many strongly monotone partitions of labelled
\\ vertices 1..n with maximum in their first partition block >=m.
\\
\\ Each block has count a[t] multiplied in so that t many vertices is
\\ reckoned as having a[t] arrangements. If all a[t]==1 then the result is
\\ count of strongly monotone partitions, bigger like vpar_count(t) is for
\\ structure within the blocks such as a component trees of a forest.
\\
\\ Blocks are reckoned in order of their minimum vertex, so first block
\\ contains 1, second contains smallest not in the first, etc.
\\
\\ The sum is over t many vertices in the first block. Its maximum is to
\\ be >=m. This is firstly v[n][m+1] for those >=m+1, and then m itself.
\\
\\ For m itself, The sum is over t many vertices in the first block.
\\ If m=1 then t=1 is the only term and it becomes just a[t] ways.
\\
\\ If m>=2 then vertices 1 and m are always in the first block then binomial
\\ to choose which of 2..m-1 are to make up the remaining t-2.
\\ The rest of the vertices are then to be monotone partitions.
\\ The second maximum must be >=m+1, which after reducing by t vertices of
\\ the first block means >=1+m-t among 1..n-t of the rest.
\\
vpar_INTERNAL_strongly_monotone_transform(a) =
{
\\ special case for m==n taking simply a[n] since sum h=m+1 to n would be 0
my(v=vector(#a,i,vector(i)),
b=v);
for(n=1,#a,
v[n][n] = a[n]; \\ m=n first block is entire
forstep(m=n-1,1,-1,
v[n][m] = v[n][m+1] + sum(t=min(2,m), m,
a[t]
* binomial(max(0,m-2),max(0,t-2))
* v[n-t][1+m-t])));
v;
}
vpar_count_strongly_monotone_forests(n) =
{
if(n==0, 1,
vpar_INTERNAL_strongly_monotone_transform(vector(n,i,vpar_count(i)))
[n][1]);
}
{
addhelp(vpar_count_strongly_monotone_forests,
"count = vpar_count_strongly_monotone_forests(n)
Return the number of strongly monotone forests of n vertices.
See vpar_is_strongly_monotone_forest() on strongly monotone.
The counts are 1,1,3,14,97,917,11103,... The current implementation builds
the result from a recurrence on C(n,m) = forests with n vertices and first
component tree maximum >=m. The return is then C(n,1). Trees are always
strongly monotone so count >= vpar_count(n) trees.
See vpar_strongly_monotone_partitions_count() for just strongly monotone
sets, without tree structure in each set.");
}
\\ not in OEIS: 3, 14, 97, 917, 11103
vpar_strongly_monotone_partitions_count(n) =
{
if(n==0, 1,
vpar_INTERNAL_strongly_monotone_transform(vector(n,i,1))
[n][1]);
}
{
addhelp(vpar_strongly_monotone_partitions_count,
"count = vpar_strongly_monotone_partitions_count(n)
Return the number of strongly monotone partitions of elements 1..n.
A strongly monotone partition means the elements arranged in sets so that
the order given by their minimum element is the same as the order given by
their maximum element. Or equivalently no range nesting, so no set min to
max is entirely inside the min to max of some other set (all ranges either
partly overlapping or completely separate).
This count is 1,1,2,4,9,22,58,164,496,... (A092920). The current
implementation builds the result from a recurrence on C(n,m) = partitions
with n vertices and first component tree maximum >=m. The return is then
C(n,1).
See vpar_count_strongly_monotone_forests() for the same on component trees.
It is bigger due to giving tree structures to the component sets.");
}
\\-----------------------------------------------------------------------------
\\ Count Labelled and Rooted
\\ Example program examples/tree-counts.gp not mentioned in the help here as
\\ it applies to various ordered, unlabelled, etc and don't want to put it
\\ everywhere.
vpar_count(n,forest=0) = \
if((n-=(forest<0)) <= 0,1, (n+abs(forest))^(n-1));
{
addhelp(vpar_count,
"count = vpar_count(n,forest=0)
Return the number of tree vpars.
Optional parameter \"forest\" can be 1 for number of forests, or it can be
-1 for number of trees with root=1.
Per Cayley,
trees = if(n==0,1, n^(n-1)) = 1,1,2,9,64,625,... (A000169),
forests = (n+1)^(n-1) = 1,1,3,16,125,1296,... (A000272).
root=1 = if(n==0,1, n^(n-2)) = 1,1,1,3,16,125,... (A000272),
Root=1 is forests of n-1 by adding root=1 above such a forest.
Trees are n * root=1 by choice of root and the rest the same.
The proportion trees/forests is (n/(n+1))^(n-1) -> 1/e = exp(-1)
= 0.367879... (A068985) per the definition of e = lim (1+1/x)^x.
These counts are vpar with parents 1..n (trees) or 0..n (forests) and which
are non-cyclic. Cyclics included would be forests (n+1)^n, or trees
(connected) would be 0 somewhere and rest 1..n for n*n^(n-1).
The usual \"exponential transform\" takes counts of labelled rooted trees
and combines them to make counts of labelled rooted forests. Here trees
count transforms to forests count, but the direct formula for forests is
simpler.");
}
\\ GP-Test vector(6,n,n--; if(n==0,1, n^(n-1))) == [1,1,2,9,64,625]
\\ GP-Test vector(6,n,n--; if(n==0,1, n^(n-2))) == [1,1,1,3,16,125]
\\ GP-Test vector(6,n,n--; (n+1)^(n-1)) == [1,1,3,16,125,1296]
\\ GP-Test floor(exp(-1)*10^6) == 367879
\\------------------------------------------------------------------------------
\\ Count Unlabelled (but Still Rooted)
\\ i=1 c[1] = sumdiv(1,d, d*a[d]) = 1
\\ a[2] = sum(k=1,1, c[1]*a[1]) / 1
\\ vec = vpar_count_unlabelled_vector(n,forest=0)
\\
vpar_INTERNAL_count_unlabelled_vector(n,forest,s) =
{
\\ Counts are formed in the usual way, by what is effectively Euler
\\ transform, asking what combinations of trees (rooted unlabelled)
\\ totalling n-1 vertices can be put under a new root to make new tree n
\\ vertices (likewise rooted unlabelled).
\\
\\ s=0 for count all unlabelled.
\\ s=1 for count asymmetric unlabelled.
\\
\\ Asymmetrics is c[i] = -sumdiv(i,d, (-1)^(i/d)*d*a[d]). The alternating
\\ sign chooses combinations of trees without repeats. Could get the sign
\\ with some bit-twiddling
\\
\\ if(s && !bitand(bitxor(d,d-1),i), -1, 1)
\\
\\ i/d is odd if d has all factors of 2 which i has, or i/d is even when d
\\ has fewer than i. bitxor(d,d-1) is bit mask of d lowest 1 and all bits
\\ below. If bitand with i is non-zero then d has all of i's 2s so i/d
\\ odd and sign according to s. If bitand is zero then d has fewer and
\\ want factor 1. Think just division i/d should be faster in practice
\\ due to overhead of the interpreter etc.
\\
\\ For the forest case, might like to store to a[] return in the final
\\ desired places rather than chop off its initial a[1]. But the sumdiv()
\\ is cleanest with a[] numbered for a tree so that there is an a[1]==1
\\ when d==1. If going d*a[d-forest] would have to watch for d==1. Could
\\ skip it in the loop and outside +s. Then something similar again for
\\ the plain sum().
if(n,
my(a=vector(n+forest),
c=vector(#a-1));
a[1]=1;
for(i=1,#a-1,
c[i] = sumdiv(i,d, a[d] * if(s && bitnegimply(1,i/d), -d, d));
a[i+1] = sum(k=1,i, c[k]*a[i+1-k]) / i);
a[1+forest .. #a];
,
[]);
\\ print("n="n);
\\ if(n,
\\ my(a=vector(n+forest),
\\ c=vector(#a-1));
\\ a[1]=1;
\\ for(i=1,#a-1,
\\ c[i] = sumdiv(i,d, a[d] * if(s && !bitand(bitxor(d,d-1),i), -d, d));
\\ a[i+1] = sum(k=1,i, c[k]*a[i+1-k]) / i);
\\ a[1+forest .. #a];
\\ ,
\\ []);
}
vpar_count_unlabelled_vector(n,forest=0) = \
vpar_INTERNAL_count_unlabelled_vector(n,forest,0);
{
addhelp(vpar_count_unlabelled_vector,
"vec = vpar_count_unlabelled_vector(n,forest=0)
Return a vector of the counts of unlabelled trees with 1..n vertices.
This is vec[i] = vpar_count_unlabelled(i,forest) but calculated all together.
See vpar_count_unlabelled() for formula and notes.
Optional parameter \"forest\" can be 1 to count forests. Forests count is
one bigger than trees count, so the effect is just to shift values down 1
place in the vector (still vector length n return).");
}
vpar_count_unlabelled(n,forest=0) =
{
\\ n==0 treated as n=1, for 1 tree of 0 vertices
\\ forest 1 later always
n += (forest || !n);
vpar_count_unlabelled_vector(n)[n];
}
{
addhelp(vpar_count_unlabelled,
"count = vpar_count_unlabelled(n,forest=0)
Return the number of rooted unlabelled trees, being vpar trees up to
rooted isomorphism.
Optional parameter \"forest\" can be 1 for number of unlabelled forests.
Unlabelled rooted trees are T(n) = 1,1,1,2,4,9,20,48,115,... (A000081) and
forests one later F(n) = T(n+1) since a tree is a forest with new root above.
Forests are counted by the usual \"Euler transform\" which is combinations
of trees totalling n vertices, with duplication allowed, so that these
values are their own Euler transform, offset by 1.
See vpar_count_unlabelled_vector() for a vector of counts.
See vpar_count_free() for unlabelled unrooted.");
}
\\-----------------------------------------------------------------------------
\\ Count Unrooted (but Still Labelled)
vpar_count_unrooted(n,forest=0) =
{
if(n<=1 || forest<=0, vpar_count(n,-1),
\\ Lajos Takacs, lightly massaged
my(a = n!);
sum(j=0,n>>1,
(-1)^j * a
/ ((j! * (n-2*j)!) << j)
* (2*j+1) * (n+1)^(n-2*j-1)));
}
{
addhelp(vpar_count_unrooted,
"count = vpar_count_unrooted(n,forest=0)
Return the number of unrooted labelled trees.
Optional parameter \"forest\" can be 1 to count forests.
Unrooted means vpar are reckoned equivalent when they are equal after some
rerooting (vpar_reroot_forest()), such as putting all roots to the minimum
of their component tree (or maximum, per vpar_reroot_minmax()).
Unrooted labelled trees correspond to labelled trees with root=1, which
is vpar_count(n,-1).
Unrooted labelled forests are 1,1,1,2,7,38,291,2932,... (A001858).
The current code uses a formula from
Lajos Takacs, \"On the Number of Distinct Forests\", SIAM Journal of
Discrete Mathematics, volume 3, 1990, pages 574-581.
http://epubs.siam.org/doi/abs/10.1137/0403050
which is a little wild but has an interpretation as building trees per
David Callan, \"A Combinatorial Derivation of the Number of Labelled
Forests\", Journal of Integer Sequences, volume 6, 2003.
http://cs.uwaterloo.ca/journals/JIS/VOL6/Callan/callan10.pdf");
}
\\-----------------------------------------------------------------------------
\\ Count Free Trees
\\ vpar_INTERNAL_Euler_transform(a) takes vector a[] where a[n] is count of
\\ how many unlabelled connected graphs of n vertices have some property.
\\ The return is vector b where b[n] is how many graphs, connected or not,
\\ of n vertices have all connected components with the property from a[].
\\
\\ b[n] assembles components from a[]. Component sizes sum to n and
\\ duplication of components from a[] is allowed but there is no ordering
\\ among the components. For example, the simplest would be all a[n]=1 (one
\\ thing of each size n) and the effect is number of partitions of integer n
\\
\\ vpar_INTERNAL_Euler_transform(vector(10,n,1)) \
\\ == vector(10,n, numbpart(n)) /* (A000041) */
\\
\\ The uses here are a[n] count some sort of unlabelled component tree and
\\ the result b[n] count unlabelled forests made up of those trees.
\\
\\ s=0 (the default) is allow duplication of components from a[] (as
\\ described above).
\\
\\ s=1 is no duplication of components from a[].
\\ This is the "weigh transform". Signs +/-1 alternate with quotient k/d.
\\
\\ The product with previous b[] and sum over divisors of a[] here is the
\\ usual form for the Euler transform. c[] is all the sum over divisors
\\ required by b[]. This c[] is sometimes seen written out with a sumdiv
\\ each time in the sum(k=...) here, but it's more efficient to do each
\\ required sumdiv once and hold them in c[].
\\
vpar_INTERNAL_Euler_transform(a,s=0) =
{
my(c=vector(#a,k, sumdiv(k,d, a[d] *if(s && bitnegimply(1,k/d), -d, d))),
b=vector(#a));
for(n=1,#a, b[n] = (c[n] + sum(k=1,n-1, c[k]*b[n-k]))/ n);
b;
}
\\ vpar_INTERNAL_rooted_vec_to_free(a,n,dup) takes vector a[] where
\\ a[i] = count of unlabelled rooted trees of i vertices of some property.
\\ Return count (an integer) of free trees with n vertices having that
\\ property. Must have #a >= n.
\\
\\ The calculation is Otter's method of counting free trees from rooted
\\ unlabelled trees. Firstly for uni-centroidal free trees, reckon the root
\\ of a rooted tree n as the centroid. Some of the rooted trees might have
\\ unbalanced arms, so that the root is not in fact the centroid. This is
\\ when one of its children > n/2 vertices. The number of these is trees of
\\ sizes i < n/2 times child subtrees of size n-i > n/2.
\\
\\ Secondly, for n even, can have bi-centroidal free trees by choosing 2
\\ rooted trees size n/2 each. Parameter "dup" is 1 if duplication is
\\ allowed for those, or 0 if not. binomial(x,2) is without duplication.
\\ binomial(x+1,2) is with duplication. The latter is an extra choice +1
\\ which when selected means repeat the first.
\\
vpar_INTERNAL_rooted_vec_to_free(a,n,dup) =
{
a[n] \\ unicentroidal root=centroid
- sum(i=1,n>>1, a[i]*a[n-i]) \\ less unbalanced combinations in n
+ if(bitnegimply(1,n), \\ when n even,
binomial(a[n>>1]+dup,2)); \\ bicentroidals
\\ print("- "i" * "n-i" = "a[i]" * "a[n-i]);
\\ print("binomial a["n/2"]="a[n/2]" = "binomial(a[n/2]+1,2));
}
\\ A000055 unlabelled free trees
\\ A005195 unlabelled free forests of n vertices
\\ Euler transform of A000055 free trees
\\ A005196 number of trees in those forests
\\ A095133 table n vertices, k trees
\\
vpar_count_free(n,forest=0) =
{
\\ "forest" is tested by >0 to catch mistakenly passing symbol 'forest or
\\ similar
if(n,
my(a=vpar_count_unlabelled_vector(n));
if(forest>0,
vpar_INTERNAL_Euler_transform
(vector(n,n, vpar_INTERNAL_rooted_vec_to_free(a,n,1)))[n]
,
vpar_INTERNAL_rooted_vec_to_free(a,n,1));
,
1); \\ 1 free tree or forest of n=0 vertices
}
{
addhelp(vpar_count_free,
"count = vpar_count_free(n,forest=0)
Return the number of free trees of n vertices, being vpar trees up to free
isomorphism so any relabelling and rerooting.
Optional parameter \"forest\" can be 1 to count forests.
Free trees are 1,1,1,1,2,3,6,11,23,47,... (A000055).
Free forests are 1,1,2,3,6,10,20,37,76,153,... (A005195).
Free trees are calculated in the usual way starting from unlabelled but
rooted trees (vpar_count_unlabelled()). Unicentroidal free trees are by
reckoning the root as centroid and subtract rooted trees with unbalanced
arms since their root is not centroid. For even n there are also
bicentroidal free trees by two n/2 rooted trees joined at the root.
Free forests are counted by \"Euler transform\" of the free trees counts,
being combinations of trees collected to make n vertices total.
See vpar_count_unlabelled_vector() for a vector of counts.");
}
vpar_count_free_vector(n,forest=0) =
{
\\ "forest" is tested by >0 to catch mistakenly passing symbol 'forest or
\\ similar
my(a=vpar_count_unlabelled_vector(n));
a=vector(n,n, vpar_INTERNAL_rooted_vec_to_free(a,n,1));
if(forest>0, vpar_INTERNAL_Euler_transform(a), a);
}
{
addhelp(vpar_count_free_vector,
"vec = vpar_count_free_vector(n,forest=0)
Return a vector of the counts of free trees with 1..n vertices.
This is vec[i] = vpar_count_free(i,forest) but calculated all together.
See vpar_count_free() for formula and notes.
Optional parameter \"forest\" can be 1 to count forests (Euler transform of
the trees count vector).");
}
\\ cf proportion trees/forests = A000055 / A005195
\\ = A095131 / A095132 in least terms
\\------------------------------------------------------------------------------
\\ Count of Heights
\\ maybe vpar_count_height_le_matrix of [n,h]
vpar_INTERNAL_count_height(n,h,forest,eq) =
{
\\ Could work binomial into mpt.
\\ Entries h>=n become full count (n+1)^(n-1) could be by power.
\\ mpt maybe count trees <= h ?
if(n<0 || h<-1, return(0));
if(h==-1, return(n==0));
if(n==0, return(1));
\\ tree as choose root and rest n-1,h-1 forest
\\ tree root=1 as rest n-1,h-1 forest, no choice of root
if(forest<=0,
if(n==1,return(h>=0));
if(h==0,return(n<=1));
n--;h--);
h=min(h+1,n);
my(m=matrix(2,n+1, i,j, 1),
v=1,p=2);
for(j=2,h,
\\ print("col "m[p,]" "v" "p);
m[v,j] = m[p,j];
my(mpt=vector(n,t, m[p,t] * t));
\\ for(t=2,n, m[p,t] *= t); \\ for t*m[p,t] in sum
\\ print("col*t "m[p,]" "v" "p);
for(i=j,n,
m[v,i+1] = sum(t=1,i,
\\ print(" t="t" "t * binomial(i-1,t-1)" "t-1" "i-t);
binomial(i-1,t-1)
\\ * t * m[p,t]
* mpt[t]
* m[v,i+1-t]));
\\ print("col "m[p,]);
\\ print("mpt "mpt);
v=3-v; p=3-p);
\\ print("final prev "m[v,]);
\\ print(" this "m[p,]);
(m[p,n+1] - if(eq,m[v,n+1])) * if(forest,1,n+1);
}
vpar_count_height(n,h,forest=0) =
{
if(h>=n, 0,
h==1 && forest<0, n>=2,
h==0, n==1 || forest==1,
!forest && h==1, n,
vpar_INTERNAL_count_height(n,h,forest,1));
}
{
addhelp(vpar_count_height,
"count = vpar_count_height(n,h,forest=0)
Return the number of vpar trees with height = h.
Optional parameter \"forest\" can be 1 for number of forests, or it can be
-1 for number of root=1 trees.
Heights are reckoned per vpar_height(), so empty tree n=0 has height -1.
Otherwise trees n>=1 have 0 <= height <= n-1, and any h outside that range
is count 0.
Per Riordan, count ==h is formed as count <=h less count <=h-1. Those
counts are the recurrence shown in vpar_count_height_le(). Taking that
column-wise gives h and h-1 in one calculation.
trees n=1 1
n=2 0, 2
n=3 0, 3, 6
n=4 0, 4, 36, 24 (A034855 for 1<=h<=n-1)
forests n=1 1
n=2 1, 2
n=3 1, 9, 6
n=4 1, 40, 60, 24 (A235595 for 0<=h<=n-1)
h=0 is attained only by all singletons, so tree n=1 and 1 forest n>=2.
h=1 tree is a star with root as middle then n choices of root.
h=1 forests are component stars or singletons, so
sum(k=0,n, binomial(n,k)*(n-k)^k) - 1 second column of F
= 0, 0, 2, 9, 40, 195, ... (A235596)
This formula follows from Paul D. Hanna in A000248, choose k roots and
others each under any of those so (n-k)^k for forests height <=1, but
subtract the 1 forest h=0 all singletons.
h=n-1 tree or forest (endmost of each row shown) is attained only by a path,
with n! permutations of its vertices, or (n-1)! for root=1 fixed first.");
}
\\
\\ for(n=1,4, print1(" n="n" "); for(h=0,n-1, print1(" "vpar_count_height(n,h),if(h==n-1,"",","))); print())
\\ for(n=1,4, print1(" n="n" "); for(h=0,n-1, print1(" "vpar_count_height(n,h,1),if(h==n-1,"",","))); print())
\\ vector(5,h, vpar_count_height(6,h))
\\ vector(8,n, vpar_count_height(n,1,1))
vpar_count_height_le(n,h,forest=0) = vpar_INTERNAL_count_height(n,h,forest,0);
{
addhelp(vpar_count_height_le,
"count = vpar_count_height_le(n,h,forest=0)
Return the number of vpar trees with height <= h.
Optional parameter \"forest\" can be 1 for number of forests, or it can be
-1 for number of root=1 trees.
Heights are reckoned per vpar_height(), so empty tree n=0 has height -1 and
otherwise trees n>=1 have 0 <= height <= n-1.
Per Riordan, for forests the count is a recurrence
F(n,h) = sum(t=1,n, t*binomial(n-1,t-1) * F(t-1,h-1) * F(n-t,h))
h = -1 0 1 2 3
n=0 1
n=1 1
n=2 1, 3
n=3 1, 10, 16
n=4 1, 41, 101, 125 (A210725)
This sum is over the component containing v=1 as t many vertices, binomial
choose its t-1 others, one of the t as the root of the component, forest t-1
height <= h-1 under it, and remaining n-t vertices further forest height
<= h.
Root=1 trees are F(n-1,h-1) by forest under the fixed root=1. Trees of any
root are n*F(n-1,h-1) choosing the root then forest below,
T(n,h) = if(n==0,(h>=-1), n*F(n-1,h-1));
h = -1 0 1 2 3
n=0 1
n=1 1
n=2 0, 2
n=3 0, 3, 9
n=4 0, 4, 40, 64 (A236396)
In all cases heights <= h=n-1 is all trees or forests so
vpar_count_height_le(n,n-1,forest) == vpar_count(n,forest)
Height <= 1 forests is the second column of F above,
F(n,1) = sum(k=0,n, binomial(n,k)*(n-k)^k)
= 1, 1, 3, 10, 41, 196, ... (A000248)
This formula is per Paul D. Hanna in A000248. It is choose k roots and
others under any of those roots so (n-k)^k.");
}
\\ Tables shown above:
\\ for(n=0,4, print1(" n="n" "); for(h=min(0,n-1),n-1, print1(" "vpar_count_height_le(n,h,1),if(h==n-1,"",","))); print())
\\ for(n=1,4, print1(" n="n" "); for(h=0,n-1, print1(" "vpar_count_height_le(n,h),if(h==n-1,"",","))); print())
\\-----------------------------------------------------------------------------
\\ Count Contiguous Forests
\\ Usual invert transform.
\\ a is a vector, return vector of its invert transform.
\\ a is treated as an ordinary generating function gA=sum a[i]*x^i for i=1..#a.
\\ The return is a vector b with gB = -1 + 1/(1-gA), for the same length #b==#a
\\ and terms b[i] coeff of x^i.
\\
vpar_INTERNAL_invert_transform(a) =
{
my(b=a);
for(i=2,#a, b[i] += sum(t=1,i-1, a[t]*b[i-t]));
b;
}
vpar_count_contiguous_forests(n) =
{
if(n==0,1,
vpar_INTERNAL_invert_transform(vector(n,i, vpar_count(i)))[n]);
}
{
addhelp(vpar_count_contiguous_forests,
"count = vpar_count_contiguous_forests(n)
Return the number of vpar forests which have contiguous vertex numbers for
each component tree. So first component all of 1..x, second all of x+1..y,
etc. A tree is a 1-component forest so always contiguous and is included in
the count.
This is equivalent to counting those vpar which have vpar_compnum_vector()
increasing with vertex v.
A component tree of t>=1 vertices is T(t) = vpar_count(t) = t^(t-1) ways, so
a contiguous forest is such a tree appended to the rest forest n-t. This is
\"invert transform\" of the tree counts.
C(n) = if(n==0,1, sum(t=1,n, t^(t-1) * C(n-t))); \\\\ forests
= 1, 1, 3, 14, 93, 837, 9742, ... (A088342)
The effect is successive component tree sizes form a composition of n
(partition with order), and product T(t) of the composition terms in each.
Ratio C(n)/T(n) -> 1 as n->infinity. Always have C(n)>=T(n) since trees are
included in C. An upper bound C(n) <= T(n)*(1+2/n) can be found by
induction (see comments in the code).
The proportion of contiguous forests out of all forests is then C(n)/F(n) ->
T(n)/F(n) and that ratio -> 1/e = exp(-1) as noted in vpar_count().");
}
\\ To see C(n) <= T(n)*(1+2/n), verify explicitly up to n=6, then for
\\ n>=7 suppose it is true up to C(n-1) and consider terms of the C(n) sum.
\\
\\ Term t=n is T(n)*C(0) = T(n).
\\
\\ Term t=1 is T(1)*C(n-1) <= T(n-1)*(1 + 2/(n-1)).
\\ Term t=n-1 is T(n-1)*C(1) = T(n-1).
\\ This pair is
\\
\\ p1 = T(n-1) * (2 + 2/(n-1))
\\ = (n-1)^(n-2) * 2*n/(n-1)
\\ = (n-1)^(n-3) * 2*n
\\ = n^(n-3) * 2*n * r1
\\ where r1 = (n-1)^(n-3) / n^(n-3)
\\
\\ r1 decreases as n increases, being essentially the inverse of the
\\ defining limit of e = exp(1) which is (n+1)^n / n^n -> e from below.
\\ Here reciprocal so decreasing,
\\
\\ For n>=7 have r1(n) <= 11/20
\\
\\ p1 <= n^(n-3) * 2*n * 11/20
\\ = T(n) * 11/10*1/n
\\
\\ Term t=2 is T(2)*C(n-2) <= 2 * T(n-2)*(1 + 2/(n-2)).
\\ Term t=n-2 is T(n-2)*C(2) = T(n-1) * 3
\\ Total of this pair is
\\
\\ p2 = T(n-2) * (4 + 2/(n-2))
\\ = (n-2)^(n-3) * (4*n-6)/(n-2)
\\ = (n-2)^(n-4) * (4*n-6)
\\ < (n-2)^(n-4) * 4*n
\\ < n^(n-4) * 4*n * r2
\\ where r2 = (n-2)^(n-4) / n^(n-4)
\\
\\ r2 decreases as n increases, similar to r1 above.
\\ For n>=7 have r2 <= 4/10
\\
\\ p2 < T(n) * 16/10 * 1/n^2
\\
\\ Terms t and n-t for t>=3 are pairs
\\
\\ p3(t) <= T(t) * T(n-t) * (1+2/t + 1+2/(n-t))
\\ = T(t) * T(n-t) * (2 + 2/t + 2/(n-t))
\\
\\ Factor 2 + 2/t + 2/(n-t) decreases as t increases, since t <= n/2 means
\\ the 2/t term decreases by more than 2/(n-t) increases.
\\
\\ Product T(t) * T(n-t) decreases as t increases, since ratio to the
\\ preceding is
\\
\\ T(t-1)*T(n-t+1) / (T(t)*T(n-t))
\\ = ((t-1)/t)^(t-2) * ((n-t+1)/(n-t))^(n-t-1) * (n-t+1)/t
\\ >= ((t-1)/t)^(t-2) * ((n-t+1)/(n-t))^(n-t-1) since t<=n/2
\\ >= 1
\\
\\ These two powers are approaching 1/e and e, but the t one smaller
\\ exponent so bigger.
\\
\\ So all p3(t) pairs of terms are <= their preceding. Take n/2 copies of
\\ p2 to exceed p2 and all p3
\\
\\ p2up < n/2 * p2
\\ = T(n) * 8/10 * 1/n
\\
\\ So
\\
\\ C(n) = T(n)*( 1 + p1 + p2up )
\\ < T(n)*( 1 + 11/10*1/n + 8/10*1/n )
\\ = T(n)*( 1 + 19/10 * 1/n )
\\ < T(n)*( 1 + 2/n )
\\
\\ GP-DEFINE T(n) = if(n<=1,1, n^(n-1));
\\ GP-DEFINE C(n) = if(n==0,1, sum(t=1,n, t^(t-1) * C(n-t)));
\\ GP-DEFINE C = memoize(C);
\\ GP-Test vector(7,n,n--; C(n)) == [1, 1, 3, 14, 93, 837, 9742]
\\ GP-Test vector(100,n, C(n) <= T(n)*(1+2/n)) == vector(100,n,1)
\\
\\ GP-Test 2+2/('n-1) == 2*'n/('n-1)
\\ GP-Test C(1) == 1
\\ GP-Test my(n=100); T(n-1)*(2+2/(n-1)) == (n-1)^(n-2) * 2*n/(n-1)
\\ GP-Test my(n=100); T(n-1)*(2+2/(n-1)) == (n-1)^(n-3) * 2*n
\\ GP-DEFINE r1(n) = (n-1)^(n-3) / n^(n-3);
\\ GP-Test vector(3,n,n+=3; r1(n) ) == [3/4, 16/25, 125/216]
\\ GP-Test r1(4) == 3/4
\\ GP-Test r1(5) == 16/25
\\ GP-Test r1(6) == 125/216
\\ GP-Test r1(7) == 1296/2401
\\ GP-Test 3/4 > 16/25
\\ GP-Test 16/25 > 125/216
\\ GP-Test my(n=1000); abs( r1(n) - exp(-1) ) < 1/1000
\\ GP-Test vector(20,n,n+=6; r1(n) <= 11/20) == vector(20,n,1)
\\ GP-Test 3/4 * 2*'n == 3/2*'n
\\
\\ GP-Test vector(20,n, 2 <= (n+1)^n / n^n) == vector(20,n,1)
\\ GP-Test vector(20,n, (n+1)^n / n^n < exp(1)) == vector(20,n,1)
\\ GP-Test my(n=4); n^2/(n-1)^2 == 16/9
\\
\\ GP-Test C(2) == 3
\\ GP-Test (4 + 2/('n-2)) == (4*'n - 6)/('n-2)
\\ GP-DEFINE r2(n) = (n-2)^(n-4) / n^(n-4);
\\ GP-Test r2(4) == 1
\\ GP-Test r2(5) == 3/5
\\ GP-Test r2(6) == 4/9
\\ GP-Test r2(7) == 125/343
\\ GP-Test r2(7) < 4/10
\\ GP-Test my(n=1000); abs( r2(n) - exp(-2) ) < 1/1000
\\ GP-Test vector(20,n,n+=6; r2(n) <= 4/10) == vector(20,n,1)
\\
\\ GP-Test my(n=100,t=15); T(t-1)*T(n-t+1) / (T(t)*T(n-t)) == \
\\ GP-Test ((t-1)/t)^(t-2) * ((n-t+1)/(n-t))^(n-t-1) * (n-t+1)/t
\\
\\ for(n=4,10,print("n="n" "r1(n)*2 + r2(n)*4/2 * 1.0))
\\ GP-Test 11/10 + 8/10 == 19/10
\\
\\ GP-Test vector(100,n, C(n) <= T(n)*(1+1/n)) == \
\\ GP-Test vector(100,n, n==1||n==2 || n>=12)
\\------------------------------------------------------------------------------
\\ Equal to Depths
vpar_is_equaldepths(vpar) =
{
for(k=1,#vpar,
if(vpar[k]!=k-1, \\ k = first not path downwards
for(v=k,#vpar, \\ k and rest must be children of the path downwards
vpar[v]0 provokes an error if mistakenly pass a symbol instead of -1,0,1
n += (forest>0);
1 + sum(k=1,n-1, (k-1)*k^(n-k-1));
}
{
addhelp(vpar_count_equaldepths,
"count = vpar_count_equaldepths(n,forest=0)
Return the number of vpar trees which have vpar vector equal to its own
depths vector. This is how many vpar_is_equaldepths().
Optional parameter \"forest\" can be 1 for count of forests, or can be -1
for count of trees with root=1 (which are the same as all trees).
The form described in vpar_is_equaldepths() gives trees
T(n) = 1 + sum(k=1,n-1, (k-1)*k^(n-k-1))
= 1,1,2,5,14,43,144,... (A047970)
The sum is k many vertices comprising the initial \"path\". The first of
the remaining is not under k so has k-1 choices of parent. The rest have
parent any of k. The fixed +1 is for k=n where all vertices are the path
part. Forests are F(n) = T(n+1) by reckoning a root=1 above the forest.
Among pre-order trees, the rest must be consecutive runs down from the
initial path giving 2^(n-2) trees or 2^(n-1) forests. ");
}
\\ A047970 OFFSET=0 starting 1, 2, 5, 14 would be forests F(n) count.
\\------------------------------------------------------------------------------
\\ Least Child Monk
\\ Chunwei Song thesis:
\\ T(n+1,0) = n^n trees on 0,1,...,n+1 so n+2 vertices with least child monk
\\ T(n+1,p) = (n-p)^(n-p) * (p+1)^(p-1) * binomial(n+1,p)
\\ least child of root has exactly p descendants
\\ Corollary 3.2.5
\\ f(n,0) number of rooted forests on n vertices each component is rootmin
\\ and least child monk
\\ vpar_INTERNAL_exponential_transform(a) takes a vector a[] and returns a
\\ vector of the usual "exponential transform" of a.
\\
\\ The use here is taking counts of labelled rooted trees and transforming
\\ to counts of labelled rooted forests.
\\
\\ Each a[n] = count of how many labelled rooted trees of n vertices have
\\ some property. The return vector is the same length and has each b[n] =
\\ count labelled rooted forests of n vertices where each component tree has
\\ this property. b[n] is formed by collecting combinations of trees with
\\ the property to make total n forest vertices.
\\
\\ The recurrence in the code is a forest as single tree of n vertices, or
\\ instead a tree of t vertices containing vertex 1, binomial choice of t-1
\\ other vertices for it, times b[n-t] ways for a forest of the rest of the
\\ vertices.
\\
\\ These selections of t many making a tree become a composition of n
\\ (partition with order), and multinomial to select which vertices to which
\\ element of that composition. That form is given by Song. See test code
\\ func test_count_leastchildmonk_and_rootmin_forests_by_compositions() for
\\ that here.
\\
vpar_INTERNAL_exponential_transform(a) =
{
my(b=vector(#a));
for(n=1,#a,
b[n] = a[n] + sum(t=1,n-1, binomial(n-1,t-1) * a[t] * b[n-t]));
b;
}
vpar_is_leastchildmonk(vpar) =
{
\\ Non-leastchildmonk is
\\
\\ r root
\\ /
\\ m min child
\\ /
\\ c any child
\\
\\ Could have r,m,c vertex numbers in any order.
\\ At m know it is min child of r, but don't know a child c until after c.
\\ At c know grandchild of root, but don't know m is min until after m.
\\
\\ So cases handle whichever m,c is the last seen of the triplet.
\\ Non-leastchildmonk is identified at max(m,c). If vpar is
\\ leastchildmonk then reach #vpar.
\\
\\ min_child[p] is the same as vpar_minmax_child_of_vertex(vpar,p,min),
\\ once v which is the minimum child of p is reached.
\\ If run to completion (vpar is leastchildmonk) then min_child ==
\\ vpar_minmax_child_vector(vpar,min).
my(min_child=vectorsmall(#vpar));
for(v=1,#vpar,
my(p=vpar[v]);
if(p,
if(!min_child[p],
min_child[p]=v);
my(g=vpar[p]);
if(g,
\\ v=c has parent p=m, p has parent r=g
\\ if g is a root, and p is least child of g, then not monk
if(vpar[g]==0 && min_child[g]==p,
return(0));
,
\\ v=m has parent p=r, p is a root
\\ if v is min child of r, and v has a child, then not monk
if(min_child[p]==v && min_child[v],
return(0)))));
\\ Vec(min_child) == vpar_minmax_child_vector(vpar,min) || error();
1;
}
{
addhelp(vpar_is_leastchildmonk,
"bool = vpar_is_leastchildmonk(vpar)
Return 1 if vpar is \"least child monk\" meaning that the smallest number
child of the root is childless. This is per
Chunwei Song, \"Counting Special Families of Labelled Trees\",
Annals of Combinatorics, volume 10, 2006, pages 271-283,
DOI 10.1007/s00026-006-0287-5
and thesis http://www.math.upenn.edu/grad/dissertations/SongThesis.pdf
If vpar is a forest then all component trees must be least child monk.
An empty tree or tree of a single vertex [0] is reckoned least child monk.
The single vertex case is implicit in Song's count of forests
(vpar_count_leastchildmonk() here), being all 3 n=2 forests. The condition
is effectively taken as prohibiting child under least child of root, so if
no root children or no roots at all then condition ok.
See vpar_count_leastchildmonk() for the number of such trees or forests.");
}
vpar_count_leastchildmonk(n,forest=0) =
{
if(n<=1, 1,
forest<0, (n-2)^(n-2), \\ trees root=1
forest==0, n * (n-2)^(n-2), \\ trees any root
vpar_INTERNAL_exponential_transform
(vector(n,t,vpar_count_leastchildmonk(t)))[n]);
}
{
addhelp(vpar_count_leastchildmonk,
"count = vpar_count_leastchildmonk(n,forest=0)
Return the number of vpar trees which are \"least child monk\".
See vpar_is_leastchildmonk() for description of this.
Chunwei Song, \"Counting Special Families of Labelled Trees\",
Annals of Combinatorics, volume 10, 2006, pages 271-283,
DOI 10.1007/s00026-006-0287-5
and thesis http://www.math.upenn.edu/grad/dissertations/SongThesis.pdf
Optional parameter \"forest\" can be -1 to count trees with root=1, or can
be 1 to count forests with all component trees least child monk,
The counts are
trees if(n<2,1, n*(n-2)^(n-2)) = 1,1,2,3,16,135,... (A000312 n>=2)
root=1 if(n<2,1, (n-2)^(n-2)) = 1,1,1,1,4,27,...
forests exponential transform = 1,1,3,10,53,386,...
The root=1 count is per Song, and trees n* since the root can swap to any
other. Forests are the usual exponential transform of the trees count.");
}
\\ GP-Test vector(6,n,n--; if(n<2,1, n*(n-2)^(n-2))) == [1,1,2,3,16,135]
\\ GP-Test vector(6,n,n--; if(n<2,1, (n-2)^(n-2)) ) == [1,1,1,1,4,27]
\\
\\ trees root=1
\\ vector(10,n,n--; if(n<=2,1, (n-2)^(n-2)))
\\ not in OEIS: 1, 1, 1, 1, 4, 27, 256, 3125, 46656, 823543
\\ not A000312 shown, it has only two initial 1s
\\
\\ trees any
\\ vector(10,n,n--; if(n<2,1, n*(n-2)^(n-2)))
\\ not in OEIS: 3, 16, 135, 1536, 21875, 373248, 7411887
\\
\\ forests
\\ not in OEIS: 3, 10, 53, 386, 3907, 51290, 830377, 15921442
vpar_count_leastchildmonk_and_rootmin(n,forest=0) =
{
if(n<=1,1,
forest<1, vpar_count_leastchildmonk(n,-1),
vpar_INTERNAL_exponential_transform
(vector(n,t, vpar_count_leastchildmonk(t,-1)))[n]);
}
{
addhelp(vpar_count_leastchildmonk_and_rootmin,
"count = vpar_count_leastchildmonk_and_rootmin(n,forest=0)
Return the number of vpar trees which are \"least child monk\" (see
vpar_is_leastchildmonk()), and also have the root of each component tree as
the smallest vertex number in that component (see vpar_is_root_minmax()).
Chunwei Song, \"Counting Special Families of Labelled Trees\",
Annals of Combinatorics, volume 10, 2006, pages 271-283,
DOI 10.1007/s00026-006-0287-5
and thesis http://www.math.upenn.edu/grad/dissertations/SongThesis.pdf
Optional parameter \"forest\" can be 1 to count vpar forests.
For trees this means root=1 and so vpar_count_leastchildmonk() of that case.
Forests follow from it by exponential transform of the tree count,
1,1,2,5,18,93,704,...");
}
\\ vector(10,n,n--; if(n<2,1,(n-2)^(n-2)))
\\ vector(10,n,n--; vpar_count_leastchildmonk_and_rootmin(n))
\\ not in OEIS: 2,5,18,93,704
\\------------------------------------------------------------------------------
\\ Herediary Least Single
vpar_is_hereditaryleastsingle(vpar) =
{
\\ This is similar to vpar_is_leastchildmonk() but without testing that r
\\ is a root.
\\ p g r any, not just root
\\ |
\\ v p m
\\ |
\\ v c
my(min_child=vectorsmall(#vpar));
for(v=1,#vpar,
my(p=vpar[v]);
if(p,
\\ v=c has parent p=m.
\\ If p has parent g and p is min child of g, then p not childless.
if(vpar[p] && min_child[vpar[p]]==p,
return(0));
if(!min_child[p],
\\ v is the least child of p.
\\ If v has a child then not hereditary least single.
if(min_child[v], return(0));
min_child[p]=v)));
\\ Vec(min_child) == vpar_minmax_child_vector(vpar,min) || error();
1;
}
{
addhelp(vpar_is_hereditaryleastsingle,
"bool = vpar_is_hereditaryleastsingle(vpar)
Return 1 if vpar is \"hereditary least single\" meaning at each vertex the
smallest number child has no children. This is per
Chunwei Song, \"Counting Special Families of Labelled Trees\",
Annals of Combinatorics, volume 10, 2006, pages 271-283,
DOI 10.1007/s00026-006-0287-5
and thesis http://www.math.upenn.edu/grad/dissertations/SongThesis.pdf
An empty tree vpar=[] or single vertex vpar=[0] is reckoned as hereditary
least single. The condition can be taken as prohibiting the smallest child
from having any children so if no children or no vertices at all the
condition is ok (similar to the rule in vpar_is_leastchildmonk()).
See vpar_count_hereditaryleastsingle() for the number of such trees or
forests.");
}
vpar_count_hereditaryleastsingle(n,forest=0) =
{
\\ Formula in Chunwei Song's thesis theorem 3.2.12 page 61
\\ http://www.math.upenn.edu/grad/dissertations/SongThesis.pdf
if(n<1, 1,
my(h=vector(n,i,1)); \\ root=1 trees, h[i] count of i vertices
for(n=4,n,
h[n] = (n-1)*h[n-1]
+ sum(i=1,n-2,
i * h[i] * (n-2)! /(i-1)!
* sum(j=1,n-i-1, h[j]*h[n+1-i-j] /(j-1)! /(n-i-j)!)
- 2 * h[n-i] * h[i+1] * binomial(n-2,i-1)));
if(forest>0, for(i=1,n, h[i]*=i); \\ trees any root at each entry
h=vpar_INTERNAL_exponential_transform(h),
forest==0, h[n]*=n); \\ trees any root
h[n]);
}
{
addhelp(vpar_count_hereditaryleastsingle,
"count = vpar_count_hereditaryleastsingle(n,forest=0)
Return the number of vpar trees which are \"hereditary least single\"
meaning at each vertex its smallest number child is childless
(vpar_is_hereditaryleastsingle()). This is per
Chunwei Song, \"Counting Special Families of Labelled Trees\",
Annals of Combinatorics, volume 10, 2006, pages 271-283,
DOI 10.1007/s00026-006-0287-5
and thesis http://www.math.upenn.edu/grad/dissertations/SongThesis.pdf
Optional parameter \"forest\" can be -1 to count trees with root=1, or can
be -1 to count forests with all components hereditary least single.
Per Song the root=1 case is a slightly complicated sum giving
1,1,1,1,4,15,96,665,... Trees of any root are n* since the root can swap to
any other 1,1,2,3,16,75,576,... Forests are then exponential transform of
all trees 1,1,3,10,53,326,2587,...");
}
\\ vector(10,n,n--; vpar_count_hereditaryleastsingle(n,-1))
\\ 1, 1, 1, 1, 4, 15, 96, 665, 6028, 60907
\\ not in OEIS: 4, 15, 96, 665, 6028, 60907
\\ vector(10,n,n--; vpar_count_hereditaryleastsingle(n,0))
\\ vector(10,n,n--; n*vpar_count_hereditaryleastsingle(n,-1))
\\ 1, 1, 2, 3, 16, 75, 576, 4655, 48224, 548163
\\ not in OEIS: 2, 3, 16, 75, 576, 4655, 48224, 548163
\\ vector(10,n,n--; vpar_count_hereditaryleastsingle(n,1))
\\ 1, 1, 3, 10, 53, 326, 2587, 23570, 253353, 3065662
\\ not in OEIS: 3, 10, 53, 326, 2587, 23570, 253353, 3065662
\\-----------------------------------------------------------------------------
\\ Isomorphism
\\ Parameter "none" is what to return for no isomorphism, either 'none or 0.
\\ When isomorphism found, if "none" is 'none then return permutation (so
\\ get perm or 'none), or if none is 0 then return 1 (so boolean 0,1).
\\
vpar_INTERNAL_isomorphism(vpar1,vpar2,none) =
{
\\ See comments in nautyextra-wip.c.
\\ print("vpar1 "vpar1);
\\ print("vpar2 "vpar2);
my(n=#vpar1);
if(n!=#vpar2, return(none));
my(vpars = [vpar1,vpar2],
num_children = apply(vpar_INTERNAL_num_children_vecsmall,vpars),
id = vectorsmall(n,i,1), \\ id[1or2][v]
sub = vectorsmall(n), \\ sub[id] = id of last descent
cumul = sub, \\ cumul[id] = id for descent by sub[id]
seq = [sub,sub],
idv = seq,
chain = seq,
idtree_upto = 1, \\ id=1 for leaf
phase = 1,
upto = 0,
new_upto = [0,0]);
id = [id,id];
for(v=1,n,
for(i=1,2,
if(num_children[i][v]==0, \\ leaf vertices
num_children[i][v]=-1;
seq[i][new_upto[i]++]=v))); \\ pre-increment
\\ print("initial upto ",new_upto);
if(new_upto[1] != new_upto[2], return(none)); \\ different numbers of leaves
upto = new_upto[1];
while(upto=1 helps concat(centres[i]) since concat([]) no good, and
\\ helps final ret[1..n] which is no good for n=0 in gp 2.7.x.
my(vpars = [vpar1,vpar2],
centres = apply(vpar_centres_of_forest,vpars),
num_roots = #centres[1]);
if(num_roots != #centres[2],
return(none)); \\ different num component trees
\\ num_bicentrals[1..2] number of bicentral components in vpar1,vpar2
my(num_bicentrals = apply(vec->sum(c=1,#vec, #vec[c]==2), centres));
if(#num_bicentrals[1]!=#num_bicentrals[2],
return(none)); \\ different num unicentral/bicentral components
for(i=1,2,
vpars[i] = vpar_reroot_forest(vpars[i],concat(centres[i])));
\\ if any bicentrals then a new vertex above the centre of every component
if(num_bicentrals[1],
vpars=apply(vpar->Vec(vpar,n+num_roots), vpars);
for(i=1,2,
for(c=1,#centres[i],
for(j=1,#centres[i][c],
vpars[i][centres[i][c][j]] = n+c)));
\\ print("extended vpar1 "vpars[1]);
\\ print("extended vpar2 "vpars[2]);
);
my(ret=vpar_INTERNAL_isomorphism(vpars[1],vpars[2],none));
if(none && ret,
ret[1..n],
ret);
}
vpar_is_free_isomorphic(vpar1,vpar2) = \
vpar_INTERNAL_free_isomorphism(vpar1,vpar2, 0);
{
addhelp(vpar_is_free_isomorphic,
"bool = vpar_is_free_isomorphic(vpar1,vpar2)
Return 1 if trees vpar1 and vpar2 are isomorphic as free trees (or forests),
or return 0 if not.
Free isomorphic means there exists some relabelling which makes the
neighbours at each vertex the same in both. Or equivalently some
relabelling plus rerooting which can make the two vpar vectors equal.
The current code uses the rooted vpar_is_isomorphic() by the usual approach
of establishing canonical roots. For unicentral components this is the
centre of each. For bicentral components a new vertex is added above each,
and in that case above each unicentral too for consistency of sizes.");
}
vpar_free_isomorphic_perm(vpar1,vpar2) = \
vpar_INTERNAL_free_isomorphism(vpar1,vpar2, 'none);
{
addhelp(vpar_free_isomorphic_perm,
"perm = vpar_free_isomorphic_perm(vpar1,vpar2)
If trees vpar1 and vpar2 are isomorphic as free trees then return a
permutation which maps vpar1 to vpar2. The return is type Vecsmall as usual
for a permutation. This relabelling plus a re-rooting to the same as vpar2
makes vpar1 equal to vpar2.
vpar_reroot_forest(vpar_relabel_perm(vpar1,perm),
vpar_roots(vpar2)) == vpar2
If vpar1,vpar2 are not isomorphic then return symbol 'none. This includes
when different lengths #vpar1!=#vpar2. vpar1,vpar2 can be forests.
perm is not unique, in general, and it's unspecified which of the possible
permutations is returned. The full set of possibles are perm*auto1 where
auto1 is any automorphism permutation of vpar1 (vpar_free_automorphisms()
etc). Or similarly auto2*perm where auto2 is any automorphism of vpar2.
Rerooting in addition to relabelling is since treating the trees as \"free\"
means ignoring the root. After relabelling by the returned perm, any
rerooting which puts the same set of vertex numbers as roots will give vpar1
equal to vpar2.
See vpar_is_free_isomorphic() for notes on the algorithm.");
}
vpar_add_roots_above_centres(vpar) =
{
my(c=vpar_centres_of_forest(vpar),
r=#vpar); \\ new root vertex number, for pre-increment
vpar=Vec(vpar, #vpar+#c);
\\ Re-root as if centre c[i][1] is going to be the root, but instead of
\\ initial p=0 start p as the new vertex to be the new root.
\\ When bicentral set the second centre vertex parent the same.
\\ Must do that last in case had c[i][2] as parent of c[i][1].
for(i=1,#c,
my(v=c[i][1],
p=r++); \\ new root, desired parent for v
while(v, my(new_v=vpar[v]); vpar[v]=p; p=v; v=new_v);
if(#c[i]>1, vpar[c[i][2]]=r));
vpar;
}
{
addhelp(vpar_add_roots_above_centres,
"vpar = vpar_add_roots_above_centres(vpar)
Return vpar with new root vertices added above the centre of each component
tree.
The existing vertex numbers are unchanged and the new vertices are
#vpar+1 .. #vpar+vpar_num_roots(vpar) inclusive, for total
#vpar+vpar_num_roots(vpar) in the return. They are assigned
one to each component tree of vpar. The order of new vertices to components
is unspecified.
In each component, a re-rooting is applied to make the centre(s) the root,
then changed to new vertex as their parent. For a 1-centre component the
new vertex is above it. For a 2-centre component the new vertex is above
and between. In all cases the component trees become 1 bigger, unicentral,
and rooted at that uni-centre.
If vpar==[] empty tree then it is returned unchanged, on the basis that for
it #vpar+vpar_num_roots(vpar) == 0. This means the return never has 1
vertex, only 0 or >=2.
This transformation allows free trees and forests to be compared for
isomorphism using the rooted vpar_is_isomorphic(), since they have a
consistent root in each component.
The new vertices added by this function are needed to balance bicentral
components. If trees (or forests) used are all unicentral then it's enough
to reroot to their centres with no vertices added. For a mixture of
unicentral and bicentral that it's necessary to add above both kinds in
order to prevent a split and grown bicentral becoming identical to what had
been a 1-bigger unicentral.");
}
vpar_num_free_isomorphic(vpar) = \
(#vpar)! / vpar_num_free_automorphisms(vpar);
{
addhelp(vpar_num_free_isomorphic,
"num = vpar_num_free_isomorphic(vpar)
Return the number of different unrooted trees (or forests) which are
vpar_is_free_isomorphic() to vpar (including vpar isomorphic to itself).
Different as unrooted means just considering sets of neighbours, ignoring
whether parent or children under whatever rerooting. This is equivalent to
asking how many vpars with a canonical rooting such as minimum of component
(vpar_is_root_minmax()) are vpar_is_free_isomorphic() to vpar.
num is related to free automorphisms by
num == (#vpar)! / vpar_num_free_automorphisms(vpar)
When a permutation changes vpar to something new, multiplying automorphism
perms give all and only permutations going to that new, up to rerooting,
since can divide out. Each further permutation going to something new
likewise, hence relabellings to different, up to rerooting, are in
automorphism group size blocks.
num ranges 1 <= num <= (#vpar)!. See vpar_num_free_automorphisms() on its
minimum and maximum which become maximum and minimum here.
Each unrooted isomorphic has the same component tree sizes so the same
number of root combinations. The number of different vpar vectors which are
free isomorphic to vpar is thus num * vpar_num_rootings(vpar).");
}
\\-----------------------------------------------------------------------------
\\ Ordered Isomorphism
\\ cf Jenner and Lange
\\ isomorphism order size(S) < size(T)
\\ or size(S) == size(T) and num_children(S) < num_children(T)
\\ or size(S) == size(T) and num_children(S) == num_children(T)
\\ and (S1,...Sk) < (T1,...Tk) lexicographically compare S1,T1 etc
vpar_is_ordered_isomorphic(vpar1,vpar2) = \
vpar_relabel_preorder(vpar1) == vpar_relabel_preorder(vpar2);
{
addhelp(vpar_is_ordered_isomorphic,
"bool = vpar_is_ordered_isomorphic(vpar1,vpar2)
Return 1 if trees vpar1 and vpar2 are ordered isomorphic, or return 0 if not.
Ordered isomorphic means there exists some relabelling (vpar_relabel_perm())
which preserves child order and can make the two vpar vectors equal. So the
same ordered rooted tree structures, just possibly different vertex numbering.
vpar1,vpar2 can be forests.
The current code is a simple comparison of vpar_relabel_preorder() each
tree. If making many ordered isomorphic tests among a group of trees then
canonicalizing to preorder will be more efficient.");
}
vpar_ordered_isomorphic_perm(vpar1,vpar2) =
{
my(perm=vpar_seq_preorder(vpar1) * vpar_seq_preorder(vpar2)^-1);
if(vpar_relabel_perm(vpar2,perm)==vpar1, perm, 'none);
}
{
addhelp(vpar_ordered_isomorphic_perm,
"perm = vpar_ordered_isomorphic_perm(vpar1,vpar2)
If trees vpar1 and vpar2 are ordered isomorphic then return the unique
child-order preserving permutation which maps vpar1 to vpar2, so that
vpar_relabel_perm(vpar1,perm) == vpar2.
The returned perm is type Vecsmall as usual for a permutation.
If vpar1,vpar2 are not ordered isomorphic then return symbol 'none. This
includes when different lengths #vpar1!=#vpar2. vpar1,vpar2 can be forests.
The permutation is formed by the unique permutation sending vpar2 to
pre-order labelling, and then the inverse of the corresponding vpar1 to
pre-order, so the result is vpar2 sent to vpar1. Using postorder gives the
same result too.
See vpar_is_ordered_isomorphic() for further notes.");
}
vpar_relabel_preorder(vpar,down=0) = \
vpar_relabel_seq(vpar,vpar_seq_preorder(vpar,down));
{
addhelp(vpar_relabel_preorder,
"vpar = vpar_relabel_preorder(vpar,down=0)
Return vpar relabelled to pre-order, so the return has the same ordered
rooted tree structure but numbered vpar_is_preorder().
This is vpar_relabel_seq() of vpar_seq_preorder(vpar,down).
Pre-order labelling is a canonical form for treating a vpar as an ordered
rooted structure. Two vpar with the same such structure become equal on
relabelling them to preorder. vpar_is_ordered_isomorphic() tests for
equivalence this way. vpar_relabel_postorder() is also suitable.
Optional parameter \"down\" can be 1 to take siblings in decreasing order,
like vpar_seq_preorder() does. The returned vpar is still preorder, and is
equivalent to first applying a vpar_relabel_reverse().");
}
vpar_relabel_postorder(vpar,down=0) = \
vpar_relabel_seq(vpar,vpar_seq_postorder(vpar,down));
{
addhelp(vpar_relabel_postorder,
"vpar = vpar_relabel_postorder(vpar,down=0)
Return vpar relabelled to post-order, so the return has the same ordered
rooted tree structure but numbered vpar_is_postorder().
This is vpar_relabel_seq() of vpar_seq_postorder(vpar).
Post-order labelling is a canonical form for treating vpar as an ordered
rooted structure. Two vpar with the same such structure become equal on
relabelling them to postorder. vpar_relabel_preorder() is also suitable.
Optional parameter \"down\" can be 1 to take siblings in decreasing order,
like vpar_seq_postorder() does. The returned vpar is still postorder, and
is equivalent to first applying a vpar_relabel_reverse().");
}
\\-----------------------------------------------------------------------------
\\ Automorphisms
\\
\\ Polya: not all groups occur as automorphism groups of trees.
\\ Group class containing trivial group, closed under direct product, and
\\ wreath product with S(n) for each n>1.
\\ Wreath product G1 wr G2 = direct product n copies of G1 and elements of G2
\\ acting on those n copies.
\\ Finite tree as >=1 fixed vertex when unicentral, >=1 fixed edge when
\\ bicentral.
\\
vpar_is_automorphism(vpar,perm) =
{
\\ perm
\\ 2 => 4
\\ ^ ^
\\ | |
\\ 1 => 3
\\
\\ If p!=0 then have edge v to p. Destination perm[v] to perm[p] must be
\\ an edge.
\\ If p==0 then v is a root. Destination perm[v] must be a root too.
\\
\\ Checking root maps to root is not actually necessary, since if all
\\ edges match then that is one-to-one and everything else must be roots
\\ to roots left over. The root check is done here with a vague idea that
\\ it might catch a non-automorphism sooner, and the code is about the
\\ same -- if(p) on the perm[p] vs on the whole line. Circumstances where
\\ it makes much difference are probably rare. Eg. a forest with many
\\ roots in the first vertex numbers 1,2,3,... could be noticed mapping to
\\ non-root.
for(v=1,#vpar,
my(p=vpar[v]);
vpar[perm[v]] == if(p,perm[p]) || return(0));
1;
}
{
addhelp(vpar_is_automorphism,
"bool = vpar_is_automorphism(vpar,perm)
Return 1 if perm is an automorphism of vpar, or return 0 if not.
This means relabelling by perm makes no change to parent and child vertex
numbers, so vpar_relabel_perm(vpar,perm) == vpar.
The identity permutation is always an automorphism. Other unchanged occur
when vpar has siblings of identical subtree structures which a perm can swap
(including say two childless siblings). Perms can swap or permute multiple
sets of such siblings, possibly nested, possibly separate.
See vpar_num_automorphisms() for the number of automorphism permutations.
Automorphisms are a group since permutation product is the two applied
successively so vpar still unchanged. See vpar_automorphism_generators()
for generators of all.");
}
\\ PENDING (if other automorphism/isomorphism things allow cycles too):
\\ vpar can contain cycles.
\\ Pari "small group" generators for symmetric groups S3 and S4.
\\ Both are "normal chain" etc required by the small group facility.
vpar_INTERNAL_S3_generators =
{
[Vecsmall([1,2,0, 4,5,3]), \\ [[1, 2, 3], [4, 5, 6]]
Vecsmall([3,5,4, 0,2,1]) ]; \\ [[1, 4], [2, 6], [3, 5]]
}
vpar_INTERNAL_S4_generators =
{
[Vecsmall([13,12, 18,19, 20,21, 14,15, 16,17, 23,22,
1,0, 6,7, 8,9, 2,3, 4,5, 11,10]),
Vecsmall([10,11, 5,4, 3,2, 9,8, 7,6, 0,1,
22,23, 17,16, 15,14, 21,20, 19,18, 12,13]),
Vecsmall([ 6,7, 22,23, 0,1, 4,5, 18,19, 17,16,
8,9, 3,2, 21,20, 12,13, 10,11, 15,14]),
Vecsmall([12,13, 14,15, 8,9, 18,19, 4,5, 11,10,
0,1, 2,3, 20,21, 6,7, 16,17, 23,22]) ];
}
\\ vpar_INTERNAL_automorphisms() finds automorphisms and based on "flags"
\\ variously counts, tests, makes generators, makes all, etc.
\\
\\ "flags" meanings:
\\ bit= 1 ret=1 initially \ otherwise ret=0 initially
\\ bit= 2 ret=Vecsmall 0s initially /
\\ bit= 4 make prev[p+1]
\\ bit= 8 make rep[p+1] and rep[v+1] (needs prev[] too)
\\ bit= 16 make subtree_chain[] and subtree_tail[] (needs rep[] too)
\\ bit= 32 ret = num generators (needs rep[] too)
\\ bit= 64 make rep_nest[v+1] = max rep below v, not including v
\\ bit= 128 ret = num automorphisms (needs rep[] too)
\\ bit= 256 return all automorphisms
\\ bit= 512 return generators
\\ Combinations:
\\ 0 = vpar_automorphism_ids_vector()
\\ 5 = vpar_is_asymmetric() 1 +4
\\ 13 = vpar_is_automorphisms_le() 1 +4+8
\\ 44 = vpar_num_automorphism_generators() 4+8 +32
\\ 572 = vpar_automorphism_generators() 4+8+16+32 +512
\\ 140 = vpar_num_automorphisms() 4+8 +128
\\ 412 = vpar_automorphisms() 4+8+16 +128+256
\\ 30 = vpar_orbit_forest() 2+4+8+16
\\ 2048 = vpar_num_automorphism_ids()
\\ 4096 = vpar_seq_uplexqueue()
\\ 156 = vpar_automorphism_group() 4+8+16 +128
\\
\\ The basic operation is to classify subtrees id[v] = integer id
\\ representing the subtree type at and below v. These are in the same
\\ manner as vpar_is_isomorphic(), but here just within one tree.
\\ Those ids are returned by vpar_automorphism_ids_vector().
\\
\\ Repeats of ids under a parent, meaning siblings of same id, are noticed
\\ within the main loop. This allows early exit when testing for asymmetric
\\ or limited symmetry.
\\
\\ The return for vpar_orbit_forest() (there's no such public function yet!)
\\ is a forest where each component is the vertices in an orbit, with
\\ unspecified structure. vpar_orbitnum_vector() etc massage this.
\\
\\ "rep_limit" is maximum number of isomorphic siblings. This is enforced
\\ when rep[] is calculated.
\\
\\ "nest_limit" is maximum number of isomorphic siblings when nested, so
\\ when those siblings contain some symmetries. It applies to the top of
\\ nesting and to all sets of isomorphic siblings below there. This is
\\ enforced when rep_nest[] calculated.
\\
\\ prev[v+1] = previous sibling of same id
\\ rep[p+1] = counting repeat ids of the various sets of siblings,
\\ finishes equal to last, not max
\\ rep[v+1] = repeat number, can build from prev[v+1]
\\ rep_nest[v+1] = max rep below v, not including v
\\ children of a given parent are not all in the same phase, so max of
\\ children cannot be held in rep[p+1]
\\
vpar_INTERNAL_automorphisms(vpar,flags,rep_limit=#vpar,nest_limit=#vpar) =
{
\\ ENHANCE-ME: If not going to have vpar_automorphism_ids_vector() then
\\ id[] can start from 0s for no initializer (and
\\ vpar_automorphism_generators() zap to -1 to be different).
\\ But think probably will have ids vector ...
\\ print("vpar_INTERNAL_automorphisms() n="#vpar" flags="flags);
\\ print(" vpar="vpar);
my(num_children = vpar_INTERNAL_num_children_vecsmall(vpar),
id = vector(#vpar,i,1),
seq = vectorsmall(#vpar),
sub = seq, \\ sub[id] = id of last descent
cumul = seq, \\ cumul[id] = id for descent by sub[id]
idtree_upto = 1, \\ id=1 at childless
phase = 1, \\ \ positions within seq[]
upto = 0, \\ /
idv = seq, \\ \ idv[id] and chain[v] for bucket sort by ids
chain = seq, \\ /
\\ prev[p+1] = previous vertex number v seen under its parent p.
\\ v are in ascending id order. p=0 children are the roots.
prev = if(bitand(flags,4), vectorsmall(#vpar+1)), \\ 0s
\\ rep[p+1] = number of repeated ids of prev[p+1].
\\ First time same id under p is rep[p+1]=1, second time is
\\ rep[p+1]=2, etc.
\\ rep[v+1] = repeat number of subtree v. So rep[v+1]=1 when v has one
\\ preceding sibling of same id.
\\ Or flags 64 instead rep[v+1] = maximum number of repeats in the
\\ subtree at and below v.
rep = if(bitand(flags,8), prev), \\ 0s
rep_nest = if(bitand(flags,64), prev), \\ 0s
\\ Vertices of the subtree at and below v in the form of a linked list.
\\ subtree_chain[x+1] = next vertex in the chain, or 0 if no more.
\\ subtree_tail[v+1] = last vertex in the chain,
\\ so that subtree_chain[subtree_tail[v+1]+1]==0
\\ x=0 etc is an imagined vertex 0 which is parent of the roots.
\\
subtree_chain = if(bitand(flags,16), vectorsmall(#vpar+1)),
subtree_tail = if(bitand(flags,16), vectorsmall(#vpar+1,i,i-1)),
num_four = 0, \\ number of sets of 4 isomorphic siblings
ret = if(bitand(flags,2), seq, \\ Vecsmall of 0s
bitand(flags,1))); \\ 1 when bit=1, 0 otherwise
\\ print(" initial ret="ret);
\\ seq[1..upto] = childless vertices
for(v=1,#vpar,
if(num_children[v]==0,
num_children[v]=-1;
seq[upto++]=v)); \\ pre-increment
while(phase<=upto,
\\ print(" phase="phase" to upto="upto" ret="ret);
\\ print(" seq ",Vec(seq[phase..upto]));
\\ next new idtree_upto, so new ids added will be idtree_phase..idtree_upto
my(idtree_phase=idtree_upto+1);
my(new_upto=upto);
for(t=phase,upto,
my(v=seq[t], \\ v accumulating under p
p=vpar[v],
v_id=id[v]);
\\ print(" t="t" v="v" v_id="v_id" p="p, if(p,Str(" p_id="id[p]),""));
if(bitand(flags,4), \\ make prev[]
my(v_prev = prev[p+1],
same = (v_prev && id[v_prev]==v_id));
prev[v+1] = if(same,v_prev);
prev[p+1] = v;
\\ print(" v_prev "v_prev" same="same" "prev);
if(same,
if(flags==5, \\ vpar_is_asymmetric()
return(0);
,
flags==30, \\ vpar_orbit_forest()
my(x=v_prev, y=v);
until(!(x=subtree_chain[x+1]),
ret[y]=x; \\ y parent x in orbit forest
y=subtree_chain[y+1])));
if(bitand(flags,8), \\ make rep[]
\\ r=1 at first repeat, which is when two same
my(r = (rep[p+1] = if(same,rep[p+1]+1))); \\ increment or clear
if(same,
\\ print(" rep r="r" cf rep_limit="rep_limit" below "rep[v+1]" cf nest_limit="nest_limit" and num_four="num_four);
if(r >= rep_limit
|| (bitand(flags,64) && rep_nest[v+1]
&& max(r,rep_nest[v+1]) >= nest_limit)
|| (flags==156 && r==3 && num_four++>1),
return(0)));
\\ print(" set v="v" rep "r);
rep[v+1] = r));
if(bitand(flags,16), \\ build subtree_chain[]
\\ append subtree v to the chain of p
subtree_chain[subtree_tail[p+1]+1] = v;
subtree_tail[p+1] = subtree_tail[v+1];
\\ print(" appended chain "Vec(subtree_chain));
);
if(p,
my(v_id=id[v], p_id=id[p]);
if(sub[p_id]==v_id,
p_id = cumul[p_id]; \\ after p_id accumulates v_id
,
sub[p_id] = v_id;
cumul[p_id] = idtree_upto++; \\ pre-increment, new id
\\ print(" new id "idtree_upto);
p_id = idtree_upto);
id[p] = p_id;
\\ print(" set parent "p" id "p_id);
if(num_children[p]--==0,
seq[new_upto++] = p)));
if(bitand(flags,64), \\ make rep_nest[] as max rep below v, not incl v
for(t=phase,upto,
my(v=seq[t], p=vpar[v]);
rep_nest[p+1] = max(rep_nest[p+1], max(rep_nest[v+1], rep[v+1])));
\\ print(" rep_nest parents " Vec(seq[upto+1..new_upto])) ;
\\ print(" rep_nest parents "apply(p->rep[p+1],Vec(seq[upto+1..new_upto])));
);
\\ up to parents in seq[]
phase = upto+1;
upto = new_upto;
\\ Bucket sort the parents by ascending id, and among those of equal
\\ id by ascending vertex number.
\\ idv[id] = first vertex of id, or 0 if none
\\ chain[v] = next vertex of same id as v, or 0 if no more
\\ print(" parents phase="phase" to upto="upto);
\\ print(" seq ",Vec(seq[phase..upto]));
forstep(t=upto,phase,-1,
my(v=seq[t], v_id=id[v]);
chain[v] = idv[v_id]; idv[v_id] = v);
\\ Re-write seq[phase..upto] with vertices now by ascending id.
\\ print(" ids idtree_phase="idtree_phase" to idtree_upto="idtree_upto);
new_upto=phase-1; \\ ready for pre-increment
for(t=idtree_phase,idtree_upto, \\ the new ids
my(v=idv[t]);
while(v,
seq[new_upto++] = v; \\ pre-increment
v = chain[v])));
\\ print(" final phase="phase);
\\ print(" ids="id);
\\ print(" idv="Vec(idv));
\\ print(" seq="Vec(seq));
\\ print(" prev="Vec(prev));
\\ print(" rep ="Vec(rep));
\\ print(" subtree_chain="subtree_chain);
\\ print(" ret="ret);
\\ zap vectors now unused
sub = cumul = chain = 0;
if(bitand(flags,32), \\ ret = number of generators
\\ print(" num generators");
\\ num_gen[v+1] is the number of generators in the subtree below v.
\\ Does not include any generators between v and its siblings.
\\ 1st and 2nd rep of a sibling is a generator.
\\ Generators below any rep >=1 are not counted (only those below rep=0).
my(num_gen=vectorsmall(#vpar+1));
for(i=1,#seq, \\ upwards
my(v=seq[i], p=vpar[v]);
\\ print(" v="v" p="p" rep "rep[v+1]" prev "prev[v+1]" below "num_gen[v+1]);
if(rep[v+1]==0, num_gen[p+1] += num_gen[v+1],
rep[v+1]==1 || rep[v+1]==2, num_gen[p+1]++));
ret=num_gen[1];
\\ print(" is "ret);
,
bitand(flags,128), \\ ret = number of automorphisms
\\ print(" num automorphisms");
ret=prod(v=2,#rep, rep[v] + 1);
\\ print(" is "ret);
);
if(flags==156, \\ vpar_automorphism_group()
\\ gens[] starts with the order-2 elements, being S2 of asymmetrics and
\\ wreath S2 of nests. Each group element is, in the usual way,
\\ g = g1^e1 * g2^e2 * ... * gn^en
\\ The action of a generator gi on such elements is g*gi.
\\ If i==n then gi just flips exponent en, since all are order 2.
\\ Otherwise must commute gn*gi = gp*gn.
\\ If gi and gn commute then gp=gi, otherwise gp is some other generator.
\\ The wreath gens swap generators below them. gen_perm[j] is that
\\ swap permutation for a wreath generator gj. Non-wreath generators
\\ have gen_perm[j] as the identity, and they commute with everything.
\\ So a gi has its index i permuted by gen_perm[n] through gen_perm[i+1].
\\ The resulting position p is the bit position to flip for map g to g*gi,
\\ and that is the gen Cayley table row entry.
\\
\\ v_to_bitpos[v] is a linked list of bit positions (the index i) of
\\ those bitpos at and below vertex v, or 0 if none.
\\ bitpos_chain[b] is the next in the chain, or 0 if none.
\\ bitpos_tail[b] is the last in the chain starting at b, or 0 if empty.
\\ Direct products are bitpos concatenations in the parent.
\\ Wreath products are the wreath swap plus the concatenations.
\\ Concats are made in id order, so for a wreath the two lists of
\\ positions are in the "same" order, ready to make the gen_perm swap.
\\ print(" vpar_automorphism_group");
my(num_three = -num_four); \\ make num_three = num S3 perms
forstep(i=#seq,1,-1, \\ high to low
my(v=seq[i]);
if(rep[v+1]>=2, \\ count and zap S3, S4
num_three++;
until(!(v=prev[v+1]), rep[v+1]=0)));
\\ print(" num_three "num_three" num_four "num_four);
\\ print(" zapped rep ="Vec(rep));
my(v_to_bitpos=vectorsmall(#vpar+1),
num_two = sum(i=2,#rep, rep[i]),
num_gens = num_two + (num_three<<1) + (num_four<<2),
bitpos_chain=vectorsmall(num_two),
bitpos_tail =bitpos_chain,
gen_perm =vpar_INTERNAL_identity_perm(num_two),
bitpos=0);
gen_perm =vector(num_two,i, gen_perm); \\ vector of identity perms
\\ print(" num_two "num_two" num_gens "num_gens);
for(i=1,#seq,
my(v=seq[i], p=vpar[v]);
if(rep[v+1],
\\ repeat, create swap of v and v_prev = prev[v+1]
bitpos++;
\\ print(" v="v" p="p" bitpos "bitpos);
\\ print(" wreath chains "v_to_bitpos[p+1]" and "v_to_bitpos[v+1]);
\\ print(" tail "if(v_to_bitpos[p+1],bitpos_tail[v_to_bitpos[p+1]])" and "if(v_to_bitpos[v+1],bitpos_tail[v_to_bitpos[v+1]]));
\\ print(" bitpos_chain "bitpos_chain);
\\ print(" bitpos_tail "bitpos_tail);
my(x=v_to_bitpos[v+1],
y=v_to_bitpos[prev[v+1]+1]);
while(x,
gen_perm[bitpos][x]=y; gen_perm[bitpos][y]=x; \\ swap
x=bitpos_chain[x]; y=bitpos_chain[y]);
\\ print(" gen_perm["bitpos"] = "Vec(gen_perm[bitpos]));
\\ print(" prepend new "bitpos" to v, was "v_to_bitpos[v+1]" tail "if(v_to_bitpos[v+1],bitpos_tail[v_to_bitpos[v+1]]));
bitpos_tail[bitpos] = if(bitpos_chain[bitpos] = v_to_bitpos[v+1],
bitpos_tail[v_to_bitpos[v+1]],
bitpos);
v_to_bitpos[v+1] = bitpos;
\\ print(" to bitpos_chain "bitpos_chain);
\\ print(" bitpos_tail "bitpos_tail);
);
\\ prepend v to p
if(v_to_bitpos[v+1],
\\ print(" prepend v "v_to_bitpos[v+1]" to p "v_to_bitpos[p+1]);
if(v_to_bitpos[p+1],
bitpos_chain[bitpos_tail[v_to_bitpos[v+1]]] = v_to_bitpos[p+1];
bitpos_tail[v_to_bitpos[v+1]] = bitpos_tail[v_to_bitpos[p+1]]);
v_to_bitpos[p+1] = v_to_bitpos[v+1];
\\ print(" to bitpos_chain "bitpos_chain);
\\ print(" bitpos_tail "bitpos_tail);
);
);
\\ print(" v_to_bitpos ",Vec(v_to_bitpos));
\\ print(" bitpos_chain ",Vec(bitpos_chain));
\\ print(" bitpos_tail ",Vec(bitpos_tail));
my(gens = vectorsmall(ret), \\ row length = num_automorphisms
orders = vectorsmall(num_gens));
gens=vector(num_gens,i, gens); \\ num_gens many Vecsmall rows
for(g=1,num_two,
orders[g]=2;
for(b=0,ret-1, \\ 0 to num_automorphisms-1 inclusive
my(p=g);
\\ print(" b="b" g="g" p="p);
forstep(k=num_two,g+1,-1,
if(bittest(b,k-1),
\\ print(" k="k" p="p" -> "gen_perm[k][p]);
p=gen_perm[k][p];
));
\\ print(" xor position p="p);
gens[g][b+1] = bitxor(b, 1<<(p-1)) + 1;
));
my(g = num_two,
s = 1<num_three, S=vpar_INTERNAL_S4_generators);
my(d=#S[1]);
\\ print(" S gens "S);
\\ print(" s="s);
for(j=1,#S,
\\ print(" gen "j" at g="g+1);
orders[g++] = if(j==#S-1,3,2);
for(b=0,ret-1, \\ 0 to num_automorphisms-1 inclusive
my(x=(b\s)%d, y=S[j][x+1]);
gens[g][b+1] = b + (y-x)*s + 1;
\\ print(" b="b" x="x" y="y" is "gens[g][b+1]);
));
s *= d);
\\ print(" return gens "gens);
\\ print(" orders "orders);
return([gens, orders]));
if(bitand(flags,768), \\ 256+512
\\ vpar_automorphisms() or vpar_automorphism_generators()
\\ print(" generators or all automorphisms");
\\ print(" subtree_chain "Vec(subtree_chain));
\\ print(" subtree_tail "Vec(subtree_tail));
\\ print(" "rep[2..#rep]);
\\ print(" sum ", sum(i=2,#rep, rep[i]==1 || rep[i]==2));
my(gen_pos = vectorsmall(#vpar+1));
rep = prev = gen_pos;
ret = vector(ret); \\ num automorphisms or num generators
\\ print(" ret length "#ret);
upto=0; \\ position in ret[], ready for pre-increment
if(bitand(flags,256), \\ vpar_automorphisms()
\\ print(" all automorphisms initial identity");
ret[upto++] = vpar_INTERNAL_identity_perm(#vpar));
forstep(i=#seq,1,-1, \\ roots downwards
my(v=seq[i],
p=vpar[v],
prev_v = prev[p+1],
same=(prev_v && id[v] && id[prev_v]==id[v]));
rep[p+1] = if(same,rep[p+1]+1,0);
if(same,
my(r=rep[p+1]);
\\ print(" v="v" prev="prev_v" under p="p" rep="r" gen_pos="gen_pos[p+1]" upto="upto);
\\ print(" ret="ret);
my(pos=if(r<=2 || bitand(flags,256),
\\ generators 1st or 2nd, or always for all automorphisms
ret[upto++] = if(r==1,
vpar_INTERNAL_identity_perm(#vpar),
ret[gen_pos[p+1]]); \\ copy
gen_pos[p+1]=upto;
,
gen_pos[p+1])); \\ apply to second generator
\\ apply swap subtrees x<->y to permutation at ret[pos]
my(x=prev_v, y=v, tail=subtree_tail[x+1]);
\\ print(" swap subtrees "x","y" at ret pos="pos" "ret[pos]);
while(1,
\\ print(" pair "x" <-> "y" (to tail "tail")");
my(t=ret[pos][x]); ret[pos][x]=y; ret[pos][y]=t;
if(x==tail, break);
x=subtree_chain[x+1];
y=subtree_chain[y+1]);
\\ print(" is "Vec(ret[pos])" at pos="pos);
if(bitand(flags,512),
\\ vpar_automorphism_generators()
\\ Only need generators under one of the identical subtrees.
\\ Zap the ids under x
\\ print(" generators zap under x");
x=prev_v;
while(x!=tail, id[x]=0; x=subtree_chain[x+1]);
,
\\ vpar_automorphisms()
\\ Multiply the new ret[pos] against all other automorphisms.
\\ ret[pos] is cyclic of order r+1, so loop c=1..r to multiply
\\ all its powers.
\\ ret[1..N] existing automorphisms
\\ ret[N+1..2*N] new ret[1..N] * ret[pos]
\\ ret[2*N+1..2*N] new ret[N+1..2*N] * ret[pos]
\\ etc
\\ Initially upto==pos then upto is advanced.
\\ The c=1 loop starts at 2 because ret[pos] is at ret[upto]
\\ is already the ret[1]*ret[pos], since ret[1] is the identity.
\\ upto is pre-incremented so first store is after that.
\\
pos==upto || error();
my(start=2, end=upto-1);
for(c=1,r,
\\ print(" c="c" start "start" end "end" is "end-start+1);
for(i=start,end,
ret[upto++] = ret[i]*ret[pos]);
start=end+1;
end=upto);
));
prev[p+1]=v;
);
\\ print(" final ret="ret);
ret
,
flags==0, id,
flags==2048, hammingweight(idv), \\ vpar_num_automorphism_ids()
flags==4096, seq, \\ vpar_seq_uplexqueue()
ret);
}
vpar_automorphisms(vpar) = vpar_INTERNAL_automorphisms(vpar,412);
{
addhelp(vpar_automorphisms,
"vec = vpar_automorphisms(vpar)
Return a vector of all automorphisms in vpar, ie. all perms which are
vpar_is_automorphism(). Each perm in vec is type Vecsmall as usual for a
permutation.
The ordering of perms in vec is unspecified.
vec length is #vec == vpar_num_automorphisms(vpar), which can be large.
See vpar_automorphism_generators() for just a set of generator
automorphisms.");
}
vpar_num_automorphisms(vpar) = vpar_INTERNAL_automorphisms(vpar,140);
{
addhelp(vpar_num_automorphisms,
"num = vpar_num_automorphisms(vpar)
Return the number of automorphism permutations of vpar.
This is how many perms are vpar_is_automorphism(vpar,perm).
The number ranges 1 <= num <= n! where n=#vpar. The identity permutation is
always an automorphism, including empty permutation in the empty tree
vpar==[], so minimum 1. See vpar_is_asymmetric() to test for num==1.
Maximum all n! permutations is every vertex interchangeable, which is solely
a forest of all singletons. For a tree, the maximum is (n-1)! for a star
rooted at its centre so n-1 children interchangeable.
In all cases num is a divisor of n! since automorphisms are a subgroup of
the group of all permutations. Or for a tree, a divisor of (n-1)! since the
root is always unchanged.
The number of automorphisms follows from siblings with the same subtree
structures (isomorphic as rooted unlabelled). Each set of s many such
siblings is factor s! for the total. This rule applies at all sets of
identical siblings, including when below other identical siblings.");
}
vpar_automorphism_generators(vpar) = vpar_INTERNAL_automorphisms(vpar,572);
{
addhelp(vpar_automorphism_generators,
"vec = vpar_automorphism_generators(vpar)
Return a vector of permutations which are generators of the automorphism
group of vpar. Each permutation is a Vecsmall as usual for a permutation.
Each permutation is vpar_is_automorphism(vpar,perm). Products and powers of
them give all the automorphism perms of vpar. The identity permutation is
not in vec. If vpar is asymmetric (vpar_is_asymmetric()), so the identity
is the only automorphism, then the return is an empty vector [].
Various sets of generators are possible and exactly which one is returned is
unspecified. The generators are minimal in the sense that none can be
omitted, but they might not be a minimum possible set. Currently there are
at most ceil(#vpar/2).
The current implementation works downwards from the root. If a set of n
siblings are isomorphic then generators are formed permuting their subtrees.
This is the symmetric group S(n) and as usual it requires 1 generator for
n=2 (swap), or 2 generators for n>=3 (swap and cycle). If the subtrees
contain further isomorphic siblings then generators for them are formed
likewise, but only under the first subtree. That suffices since another can
swap to the first, perm there, and swap back.");
}
\\ PENDING
\\ See vpar_num_automorphism_generators() for just the number of generators.
\\ cf Picard symmetric group S(n) generated by pair of elements, and all
\\ elements are a member of such a pair. Binder any two x1,x2 there is a
\\ single y where x1,y and x2,y are both generating pairs.
\\ cf.
\\ Richard Otter, "The Number of Trees", Annals of Mathematics, series 2,
\\ volume 49, number 3, July 1948, pages 583-599.
\\ Multiplicity = Num occurrences of a subtree of the root, being all at and
\\ below a child of the root.
\\ 2090918^(1/2^4)
\\ 1/(2090918^(1/2^4))
\\ not in OEIS: 2.483253536172601
\\ not in OEIS: 0.402697503671447
\\
vpar_automorphism_ids_vector(vpar) = vpar_INTERNAL_automorphisms(vpar,0);
{
addhelp(vpar_automorphism_ids_vector,
"vec = vpar_automorphism_ids_vector(vpar)
Return a vector where each vec[v]=id is an integer identifier for the
subtree structure at and below v.
Subtrees are compared by isomorphism (vpar_is_isomorphic()), so rooted
unlabelled. When vec[u]==vec[v], it means the subtrees under u and v are
isomorphic. Siblings with same id can have their subtrees swapped by an
automorphism.
Each id is an integer in the range 1 <= id <= #vpar. Childless vertices are
id=1. Ids increase with subtree height, so id[u] <= id[v] means
subtree_height[u] <= subtree_height[v]. There may be gaps (unused numbers)
in the ids. ids cannot be compared between different vpar, not even between
relabellings of the same vpar.
A complete set of ids 1..#vpar occurs for path-n since every vertex has a
different subtree. Anything else uses fewer ids since the childless
vertices (>=2 of them) have the same id=1.");
}
vpar_num_automorphism_ids(vpar) = \
vpar_INTERNAL_automorphisms(vpar,2048) + (#vpar>0);
{
addhelp(vpar_num_automorphism_ids,
"num = vpar_num_automorphism_ids(vpar)
Return the number of different subtrees occurring in vpar.
This is the number of distinct ids in vpar_automorphism_ids_vector(vpar).
Subtrees are everything at and below a vertex v. Subtrees are compared by
isomorphism (vpar_is_isomorphic()), so rooted unlabelled.
The return is in the range vpar_height(vpar)+1 <= num <= #vpar. The minimum
is when every subtree of given height has the same structure. The maximum
is when every vertex has a different subtree, which is only path-n (anything
else has >=2 childless and they are same).");
}
\\ galoisinit()
\\ A4 ends 2,2,3
\\ S4 ends 2,2,3,2
\\ [1..n] is normal subgroup of G, except first of A4 and S4 ends
\\ group_elts() g[1] = id
vpar_automorphism_group(vpar) =
{
my(ret=vpar_INTERNAL_automorphisms(vpar,156,4,2));
if(ret===0, 'none, ret);
}
{
addhelp(vpar_automorphism_group,
"group = vpar_automorphism_group(vpar)
Return a Pari \"small group\" of the automorphism group of vpar,
This is a 2-element vector [ [perm1,perm2,...], Vecsmall([ord1,ord2,...]) ].
group[1] is a vector of generator permutations (each Vecsmall as usual).
group[2] is a Vecsmall of permorder() each generator.
See galoisinit() and the Pari library manual \"Small Groups\" for more.
Pari groups are restricted to \"weakly supersolvable\". For tree and forest
automorphisms, this means at most 2 isomorphic siblings at and below any
nesting, 3 siblings no nesting, and optionally one 4 siblings no nesting.
If vpar is not of this form then return symbol 'none.
Group elements are numbered 1 .. vpar_num_automorphisms(vpar) by mixed-radix
product of the generators. Each group element, and that includes the
generators as returned here, is its row of the Cayley table, so is
num_automorphisms long, which might be big. The ordering of the generators
(and hence element numbering) is unspecified beyond the Pari requirements of
a normal chain and S4 last. Each set of 2, 3 or 4 isomorphic siblings
becomes 1, 2 or 4 generator elements respectively.");
}
vpar_is_automorphisms_le(vpar,m,t=m) = \
vpar_INTERNAL_automorphisms(vpar, 13+if(t=0 || n || n++; \\ root=1 n=0 treated as n=1
vpar_count_unlabelled_asymmetric(n,forest>0) * (n-(forest<0))!;
}
{
addhelp(vpar_count_asymmetric,
"count = vpar_count_asymmetric(n,forest=0)
Return the number of asymmetric vpar trees of n vertices, so how many
vpar_is_asymmetric().
Optional parameter \"forest\" can be 1 for number of forests, or can be -1 for
number of root=1 trees.
Number of trees or forests is simply n! times unlabelled asymmetric of
vpar_count_unlabelled_asymmetric(), since all labellings are different.
T(n) = n! * vpar_count_unlabelled_asymmetric(n) \\\\ trees
= 1,1,2,6,48,360,4320, ... (A228159)
F(n) = n! * vpar_count_unlabelled_asymmetric(n,1) \\\\ forests
= 1,1,2,12,72,720,8640, ...
Root=1 trees are B(n) = F(n-1) = (n-1)!*UnlabelledT(n) = T(n)/n, being
either a fixed root and labelled asymmetric forest below, or an unlabelled
tree with only (n-1)! labellings since root=1 is fixed.");
}
\\ labelled
\\ GP-Test vector(7,n,n--; vpar_count_asymmetric(n)) == [1,1,2,6,48,360,4320]
\\ GP-Test vector(#A228159_samples,n,n--; if(n==0,0,vpar_count_asymmetric(n))) == \
\\ GP-Test A228159_samples
\\ GP-Test vector(7,n,n--; vpar_count_asymmetric(n,1)) == [1,1,2,12,72,720,8640]
\\ GP-Test vector(8,n,n--; vpar_count_asymmetric(n,-1)) == [1,1,1,2,12,72,720,8640]
\\ not in OEIS: 1,1,2,12,72,720,8640
vpar_count_unlabelled_asymmetric_vector(n,forest=0) = \
vpar_INTERNAL_count_unlabelled_vector(n,forest,1);
{
addhelp(vpar_count_unlabelled_asymmetric_vector,
"vec = vpar_count_unlabelled_asymmetric_vector(n,forest=0)
Return a vector of the counts of asymmetric unlabelled trees with 1..n
vertices. This is vec[i] = vpar_count_unlabelled_asymmetric(i,forest) but
calculated all together. See vpar_count_unlabelled_asymmetric() for formula
and notes.
Optional parameter \"forest\" can be 1 to count forests. Forests count is
one bigger than trees count, so the effect is just to shift values down 1
place in the vector (still vector length n return of course).");
}
vpar_count_unlabelled_asymmetric(n,forest=0) =
{
\\ n==0 treated as n=1, for 1 tree of 0 vertices
\\ forest 1 later always
n += (forest || !n);
vpar_count_unlabelled_asymmetric_vector(n)[n];
}
{
addhelp(vpar_count_unlabelled_asymmetric,
"count = vpar_count_unlabelled_asymmetric(n,forest=0)
Return the number of asymmetric rooted unlabelled trees of n vertices.
This is how many vpar_is_asymmetric() within vpar_count_unlabelled().
Optional parameter \"forest\" can be 1 for number of forests.
The count is trees T(n) = 1,1,1,1,2,3,6,12,25,52,... (A004111) and forests
one later F(n) = T(n+1). A tree is a root above an asymmetric forest.
A forest is a collection of asymmetric trees total size n and without
duplication of trees. This is the usual \"weigh transform\". It differs
from the Euler transform of full vpar_count_unlabelled() by not allowing
duplication.
See vpar_count_unlabelled_asymmetric_vector() for a vector of counts.");
}
\\ unlabelled
\\ GP-DEFINE T(n) = if(n<=1,1, 1/(n-1) * sum(k=1,n-1, sumdiv(k,d,-(-1)^(k/d)*d*T(d)) * T(n-k)));
\\ GP-Test vector(10,n,n--; T(n)) == [1,1,1,1,2,3,6,12,25,52]
\\ GP-Test vector(10,n,n--; vpar_count_unlabelled_asymmetric(n)) == [1,1,1,1,2,3,6,12,25,52]
\\ GP-Test vector(9,n,n--; vpar_count_unlabelled_asymmetric(n,1))== [ 1,1,1,2,3,6,12,25,52]
\\ GP-Test vector(#A004111_samples,n,n--; if(n==0,0,vpar_count_unlabelled_asymmetric(n))) == \
\\ GP-Test A004111_samples
\\-----------------------------------------------------------------------------
\\ Free Automorphisms
vpar_is_free_automorphism(vpar,perm) =
{
\\ perm
\\ 1 2
\\ ^ => ^
\\ | |
\\ 2 1
\\
\\ If p!=0 then have edge v to p and destination permuted perm[v] to
\\ perm[p] must be an edge.
\\ It can be either perm[v] as child and perm[p] as parent, the same as
\\ vpar_is_automorphism(), or other way perm[v] parent and perm[p] child.
\\
\\ In vpar_is_automorphism(), if v is a root then perm[v] must be a root,
\\ but that's not the case here. Rerooting can mean there are the same
\\ edges at perm[v] but one of them as a parent so perm[v] not a root.
for(v=1,#vpar,
my(p=vpar[v]);
if(p
&& vpar[perm[v]] != if(p,perm[p])
&& perm[v] != vpar[perm[p]],
return(0)));
1;
}
{
addhelp(vpar_is_free_automorphism,
"bool = vpar_is_free_automorphism(vpar,perm)
Return 1 if perm is a free automorphism permutation of vpar, or return 0 if
not. A free automorphism makes no change to change to neighbouring vertex
numbers when relabel by vpar_relabel_perm(vpar,perm). So vpar vector either
unchanged or changed only to some rerooting.
The identity permutation is always an automorphism. Other unchanged occur
when a vertex has neighbours of identical structures which a perm can swap.
Perms can do that to multiple sets of such branches, possibly nested,
possibly separate.
Free automorphism relaxes the conditions of vpar_is_automorphism() to allow
neighbours to change from parents to children (rerooting), so a superset
vpar_is_free_automorphism(vpar,perm) >= vpar_is_automorphism(vpar,perm)
See vpar_free_automorphisms() or vpar_num_free_automorphisms() for all
automorphisms. They are a group since a permutation product is the two
applied successively which still leaves vpar unchanged neighbours. See
vpar_free_automorphism_generators() for generators.");
}
\\ PENDING (if other automorphism/isomorphism things allow cycles too)
\\ vpar can contain cycles.
\\ vpar_INTERNAL_for_free_automorphisms(vpar) returns vpar mangled so that
\\ the rooted automorphism funcs can be used to find free automorphisms.
\\
\\ If any component tree is bicentral then add new roots above centres the
\\ same as vpar_add_roots_above_centres() does. This balances bicentrals,
\\ and above the unicentrals ensures increased bicentrals won't look the
\\ same as one of the unicentrals.
\\
\\ If all component trees are unicentral then don't need any new vertices,
\\ just re-root to those uni-centres.
\\
\\ This is what vpar_is_free_isomorphic() does to the two trees considered
\\ there, but here just one tree, and no early exits for mismatches.
\\
vpar_INTERNAL_for_free_automorphisms(vpar) =
{
if(#vpar, \\ concat([]) no good in gp 2.9.x, avoid that
my(centres = vpar_centres_of_forest(vpar));
vpar = vpar_reroot_forest(vpar,concat(centres));
for(i=1,#centres,
if(#centres[i]==2,
\\ have bicentral(s), add a new root above the centre of every
\\ component (both bicental and unicentral)
my(n=#vpar);
vpar=Vec(vpar,n+#centres);
for(c=1,#centres,
for(j=1,#centres[c],
vpar[centres[c][j]] = n+c));
break)));
vpar;
}
\\ {
\\ addhelp(vpar_INTERNAL_for_free_automorphisms,
\\ "vpar = vpar_INTERNAL_for_free_automorphisms(vpar)
\\ Return vpar modified or extended so that all its free automorphisms are
\\ all the rooted automorphisms of the new vpar.
\\
\\ If all component trees are unicentral then re-root to their centres.
\\
\\ If any component tree is bicentral then a new root vertex is added above
\\ every component the same as vpar_add_roots_above_centres() does.
\\ Automorphisms of the new vpar may permute these new vertices, but only
\\ among themselves, so free automorphisms perms of the original vpar are by
\\ truncating to the original size.");
\\ }
\\ vec = vpar_INTERNAL_shrink_perms(vec,n)
\\ vec is a vector of perms (Vecsmall).
\\ Return it with each perm reduced, or possibly reduced, to n vertices
\\ by chopping off the rest.
vpar_INTERNAL_shrink_perms(vec,n) = apply(perm->Vecsmall(perm,n), vec);
vpar_free_automorphisms(vpar) =
{
vpar_INTERNAL_shrink_perms
(vpar_automorphisms(vpar_INTERNAL_for_free_automorphisms(vpar)), #vpar);
}
{
addhelp(vpar_free_automorphisms,
"vec = vpar_free_automorphisms(vpar)
Return a vector of all free automorphism permutations of vpar, so all perms
which are vpar_is_free_automorphism(vpar,perm).
The ordering of perms in vec is unspecified.
vec length is #vec == vpar_num_free_automorphisms(vpar), which can be large.
See vpar_free_automorphism_generators() for just a set of generators.");
}
vpar_num_free_automorphisms(vpar) = \
vpar_num_automorphisms(vpar_INTERNAL_for_free_automorphisms(vpar));
{
addhelp(vpar_num_free_automorphisms,
"num = vpar_num_free_automorphisms(vpar)
Return the number of free automorphisms of vpar, so how many perms are
vpar_is_free_automorphism(vpar,perm).
num ranges 1 <= num <= n! where n=#vpar. The identity permutation is always
an automorphism, including empty permutation in the empty tree vpar==[], so
minimum 1. See vpar_is_free_asymmetric() to test for num==1.
Maximum all n! permutations occurs when every vertex is interchangeable.
This is path-2 or a forest entirely singletons. num is otherwise a divisor
of n! since automorphisms are a subgroup of the group of all permutations.
For a unicentral tree, the maximum reduces to (n-1)! since the centre is
fixed.
For a forest, num is product num automorphisms of its component trees, times
permutations of any sets of component trees which are isomorphic.");
}
vpar_free_automorphism_generators(vpar) =
{
vpar_INTERNAL_shrink_perms
(vpar_automorphism_generators(vpar_INTERNAL_for_free_automorphisms(vpar)),
#vpar);
}
{
addhelp(vpar_free_automorphism_generators,
"vec = vpar_free_automorphism_generators(vpar)
Return a vector of permutations which are generators of the free
automorphism group of vpar. Each permutation is a Vecsmall as usual for a
permutation.
Each permutation is vpar_is_free_automorphism(vpar,perm). Products and
powers of them give all the free automorphism perms of vpar. The identity
permutation is not in vec. If vpar is asymmetric (vpar_is_free_asymmetric()),
so the identity is the only automorphism, then the return is empty vector [].
Various sets of generators may be possible and exactly which one is returned
is unspecified. The generators are a minimal set in the sense that none can
be omitted, but they might not be a minimum possible set.
The current implementation uses the rooted vpar_automorphism_generators()
applied to vpar with suitable canonical rooting ensuring any possibly equal
neighbouring branches appear as siblings for rooted automorphisms.");
}
\\-----------------------------------------------------------------------------
\\ Free Automorphism Orbits
vpar_free_orbitnum_vector(vpar) =
{
Vec(vpar_orbitnum_vector
(vpar_INTERNAL_for_free_automorphisms(vpar)),
#vpar);
}
{
addhelp(vpar_free_orbitnum_vector,
"vec = vpar_free_orbitnum_vector(vpar)
Return a vector of orbit number of each vertex under free automorphisms.
The return has vec[v] = orbit number, where orbits are numbered 1 to
vpar_num_free_orbits(vpar) inclusive. Orbits are numbered in order of their
smallest member vertex; so orbit 1 contains v=1, orbit 2 contains the
smallest vertex not in orbit 1, etc.
A vertex which is fixed in all automorphisms is alone in its orbit. A free
asymmetric tree or forest (vpar_is_free_asymmetric()) has all vertices this
way, giving every vertex its own orbit number vec == [1..#vpar].");
}
vpar_free_orbit_sets(vpar) =
{
\\ Extra vertices added by vpar_INTERNAL_for_free_automorphisms() are
\\ >#vpar and similar only to each other, so can truncate, by Vecsmall.
\\
vpar_component_vertices
(Vecsmall(vpar_INTERNAL_automorphisms \\ vpar_orbit_forest()
(vpar_INTERNAL_for_free_automorphisms(vpar), 30),
#vpar)); \\ truncate
}
{
addhelp(vpar_free_orbit_sets,
"vec = vpar_free_orbit_sets(vpar)
Return a vector of sets which are the free automorphism orbits of vpar.
Each vec[i] is the vertices in an orbit, in ascending order, so a Set().
The number of sets is #vec == vpar_num_orbits(vpar).
vec is vecsort()ed, so is ordered by smallest member of each set (first
element vec[i][1]). This is the same order as vpar_free_orbitnum_vector(),
so set vec[orbitnum] is the vertices in that orbitnum.
A vertex which is fixed by all automorphisms is alone in its orbit set.
A vpar_is_free_asymmetric() tree or forest has no automorphisms so has all
vertices that way.");
}
vpar_free_orbit_sizes(vpar) = \
Vec(vpar_INTERNAL_histogram(vpar_free_orbitnum_vector(vpar)));
{
addhelp(vpar_free_orbit_sizes,
"vec = vpar_free_orbit_sizes(vpar)
Return a vector of the orbit sizes of vpar under free automorphism
permutations.
vec[i] is the size of orbit number i. Orbits are numbered 1 to
vpar_num_free_orbits() in order of the smallest vertex number of the orbit,
the same as vpar_free_orbitnum_vector() and vpar_free_orbit_sets().
Each vertex is in an orbit so that vecsum(vec)==#vpar and vec is a
composition (partition with order) of #vpar (though only some compositions
occur).");
}
vpar_num_free_orbits(vpar) =
{
\\ Extra vertices added by vpar_INTERNAL_for_free_automorphisms() are
\\ >#vpar and similar only to each other, so can truncate, by Vecsmall.
\\
vpar_num_roots(Vecsmall(vpar_INTERNAL_automorphisms \\ vpar_orbit_forest()
(vpar_INTERNAL_for_free_automorphisms(vpar), 30),
#vpar));
}
{
addhelp(vpar_num_free_orbits,
"num = vpar_num_free_orbits(vpar)
Return the number of free automorphism orbits in vpar.
For a tree, this is also the number of unlabelled rootings of vpar, since
each vertex in an orbit sees the same tree structure when it looks outward,
and so gives the same rooted unlabelled tree. Consequently the total
\"num\" over all free trees is the rooted trees count
vpar_count_unlabelled().");
}
\\-----------------------------------------------------------------------------
\\ Free Asymmetric
vpar_is_free_asymmetric(vpar) = \
vpar_is_asymmetric(vpar_INTERNAL_for_free_automorphisms(vpar));
{
addhelp(vpar_is_free_asymmetric,
"bool = vpar_is_free_asymmetric(vpar)
Return 1 if vpar is asymmetric as a free tree, or 0 if not.
Free asymmetric means the only free automorphism permutation
(vpar_is_free_automorphism()) is the identity permutation. Any other
relabelling gives different sets of neighbours than vpar has, so different
vpar vector and different after any rerooting.
This occurs when vpar has no vertex with neighbouring subtrees of the same
structure which could be swapped. For example there cannot be two leaf
(degree=1) vertices at the same attachment vertex, since they could be
swapped leaving neighbours unchanged. Bigger branches likewise.");
}
vpar_count_free_asymmetric(n,forest=0) =
{
if(n,
my(a=vpar_count_unlabelled_asymmetric_vector(n));
if(forest>0,
vpar_INTERNAL_Euler_transform
(vector(n,n, vpar_INTERNAL_rooted_vec_to_free(a,n,0)), 1)[n]
,
vpar_INTERNAL_rooted_vec_to_free(a,n,0));
,
1); \\ 1 free tree or forest of n=0 vertices
}
{
addhelp(vpar_count_free_asymmetric,
"count = vpar_count_free_asymmetric(n,forest=0)
Return the number of free trees which are asymmetric, ie. how many
vpar_is_free_asymmetric() among free trees (vpar_count_free()).
Optional parameter \"forest\" can be 1 for number of asymmetric free
forests.
This is trees T(n) = 1,1,0,0,0,0,0,1,1,3,6,15,29,67,... (A000220)
or forests F(n) = 1,1,0,0,0,0,0,1,2,4,9,21,44,96,... (A035056).
Trees are calculated from vpar_count_unlabelled_asymmetric() by putting
together halves like vpar_count_free() does for all free trees, but here the
bicentroidal case is two different halves (duplication would be a symmetry).
Forests are then the usual \"weigh\" transform choosing component trees
without duplication for total n vertices.");
}
\\ GP-Test vector(14,n,n--; vpar_count_free_asymmetric(n)) == [1,1,0,0,0,0,0,1,1,3,6,15,29,67]
\\ GP-Test vector(14,n,n--; vpar_count_free_asymmetric(n,1)) == [1,1,0,0,0,0,0,1,2,4,9,21,44,96]
\\ GP-Test vector(#A000220_samples,n, vpar_count_free_asymmetric(n)) == \
\\ GP-Test A000220_samples
\\ GP-Test vector(#A035056_samples,n,n--; vpar_count_free_asymmetric(n,1)) == \
\\ GP-Test A035056_samples
\\-----------------------------------------------------------------------------
\\ Pre-Order Max-Depths Canonical for Unlabelled
\\ vpar_INTERNAL_premax()
\\
\\ This code brings a tree to preorder max-depths canonical form using, more
\\ or less, the algorithm of
\\
\\ Aho, Hopcroft and Ullman, "The Design and Analysis of Computer
\\ Algorithms", Addison-Wesley, 1974, pages 84-85.
\\
\\ A discussion of which can be found for instance in
\\
\\ Douglas M. Campbell and David Radford, "Tree Isomorphism Algorithms:
\\ Speed vs. Clarity", Mathematics Magazine, volume 64, number 4,
\\ October 1991, pages 252-261. http://www.jstor.org/stable/2690833
\\
\\ Pre-order max-depths is the same as pre-order max-vpar so the name is
\\ "premax".
\\
\\ The code works upwards by rows. Integer ids are assigned to vertices in
\\ ascending order of their subtrees. Subtrees are ordered by the depths
\\ vector which would result from them relabelled to preorder max-depths.
\\
\\ For comparing subtrees in a row, each vertex gets a key which is a vector
\\ [id1, id2, ...] of ids of its children, with those ids in descending
\\ order. Those keys are sorted lexicographically (by vecsort()). Doing so
\\ puts them in premax order. New ids are assigned to the row vertices in
\\ that order, with those of same ids vector getting same new id.
\\
\\ Flags from vpar_seq_premax() cause a linked list of vertices to be made
\\ of each subtree in premax order. Each parent is a pre-order visit to
\\ itself followed by traverse child subtrees with those subtrees in id
\\ order. The code goes by lex biggest subtree first and appends to the
\\ linked list by maintaining a "tail" which is the list end. A Vecrev on
\\ the row vertex numbers ensures that equal id siblings are visited by
\\ ascending vertex number.
\\
\\ Flags from vpar_to_unlabelled() cause the return to be a perm which
\\ vpar_to_unlabelled() will use with vpar_relabel_perm(). This is a perm
\\ which is the inverse permuatation of the seq made for vpar_seq_premax().
\\ The perm is made from the same linked list of vertices, but setting perm
\\ as the inverse of what seq would be.
\\
\\ vpar_to_free() passes a vpar tree with new root above a bicentral pair of
\\ vertices. row[2] is that bicentral pair. That row is put into reverse
\\ order so the second subtree is the bigger premax. When the first is
\\ dropped to 1 greater depth level that gives the free primary form. When
\\ called with a tree there's just #row[2] == 2 vertices to worry about
\\ there, but the code has an attempt at generality ready perhaps for premax
\\ free forest.
\\
\\ For comparison, vpar_INTERNAL_automorphisms() instead works upwards by
\\ subtree height. Going subtree height doesn't emit ids in ascending tree
\\ order. Ids are distinct subtrees, which suffices for automorphisms, but
\\ they don't give order between subtrees.
\\
\\ flags bits 0 = return ids (currently unused)
\\ 1 = seq vpar_seq_premax = 2
\\ 2 = perm vpar_to_unlabelled = 4
\\ 4 = vary row 2 vpar_to_free 4 or 4+2
\\
vpar_INTERNAL_premax(vpar,flags) =
{
\\ ENHANCE-ME: First order[] = 1..#rows[#rows] could be specific code
\\ instead of making array for the general case.
\\
\\ ENHANCE-ME: Tickle the signs of ids and the order sorteds are
\\ processed, rather than Vecrev on the rows.
\\ print("vpar_INTERNAL_premax() n="#vpar" flags="flags"\n vpar="vpar);
#vpar || return(if(flags,Vecsmall([]), []));
my(num_children = vpar_INTERNAL_num_children_vecsmall(vpar),
subtree_chain = if(flags,vectorsmall(#vpar+1)),
subtree_tail = if(flags,vpar_INTERNAL_identity_perm(#vpar+1)),
id = vectorsmall(#vpar,i,1), \\ id[v] of vertex
rows = apply(Vecrev,vpar_row_vertices_vector(vpar)),
rowpos = vectorsmall(#vpar), \\ like vpar_rowpos_vector() but +1
order = vpar_INTERNAL_identity_perm(#rows[#rows]),
r = #rows); \\ row number acting on
while(
\\ print(" row r="r" "if(r,rows[r]));
\\ print(" order="Vec(order)" is row "vecextract(rows[r],order));
if(flags,
\\ print(" subtree chain");
forstep(i=#order,1,-1,
my(v=rows[r][order[i]], p=vpar[v]);
\\ print(" v="v" p="p);
\\ for vpar_to_free, bicentral split has the bigger
\\ subtree second, so prepend
if(r==2 && bitand(flags,4), \\ free row at depth=1, below roots
my(t=rowpos[p]);
\\ print(" set rowpos["p"] = "v" for next");
rowpos[p]=v;
if(t,
\\ print(" second bicentral chain="subtree_chain[t+1]);
if(subtree_chain[t+1],
\\ print(" prepend to t="t" chain "subtree_chain[t+1]" tail "subtree_tail[t+1]-1);
subtree_chain[subtree_tail[v+1]] = subtree_chain[t+1];
subtree_chain[t+1] = v;
next;
,
subtree_tail[p+1]==subtree_tail[t+1],
subtree_tail[p+1] = subtree_tail[v+1]);
p=t);
);
\\ append
\\ print(" append v="v" tail "subtree_tail[v+1]-1" to p="p);
subtree_chain[subtree_tail[p+1]] = v;
subtree_tail[p+1] = subtree_tail[v+1];
\\ print(" now p="p" chain "subtree_chain[p+1]" tail "subtree_tail[p+1]-1);
));
r--; \\ while() loop stop at r=0
,
\\ rowpos[v] = position of v in its row, 1=first
for(i=1,#rows[r], rowpos[rows[r][i]] = i);
\\ keys = [ [id1,id2,...], ... ]
\\ keys[i] is ids vector for vertex v=rows[r][i], each id in keys[i]
\\ is the id for a child of that v.
\\ print(" keys for row ",rows[r]);
my(keys=vector(#rows[r],i,
vectorsmall(num_children[rows[r][i]])));
for(i=1,#order,
my(v=rows[r+1][order[i]],
p=vpar[v]);
\\ print(" v="v" id="id[v]" to parent p="p" pos "num_children[p]);
keys[rowpos[p]][num_children[p]] = id[v];
num_children[p]--);
\\ print(" keys "keys);
\\ for(i=1,#keys, keys[i]==vecsort(keys[i],,4) || error()); \\ descending
order = vecsort(keys,,1);
\\ print(" order ascending "Vec(order));
\\ print(" is sorted keys "vecextract(keys,order));
\\ print(" is row "vecextract(rows[r],order));
\\ Assign new ids at vertices in order of the key vectors.
\\ Same id for vertices with same key vectors.
\\ print(" ids i=1 o="order[1]" v="rows[r][order[1]]" id 1");
id[rows[r][order[1]]] = 1;
my(upto=1);
for(i=2,#order,
id[rows[r][order[i]]] = (upto += (keys[order[i]]!=keys[order[i-1]]));
\\ print(" i="i" o="order[i]" v="rows[r][order[i]]" id "upto);
);
);
if(flags,
\\ print(" subtree_chain "Vec(subtree_chain));
\\ print(" subtree_tail "Vec(subtree_tail));
\\ print(" starting v=0 chain="subtree_chain[1]);
my(v=0, upto=0);
if(bitand(flags,1),
\\ print(" make seq");
while(v=subtree_chain[v+1],
if(vpar[v] || bitnegimply(4,flags), \\ free tree skip roots
rowpos[upto++]=v)); \\ seq
,
\\ print(" make perm");
while(v=subtree_chain[v+1],
if(vpar[v] || bitnegimply(4,flags), \\ free tree skip roots
rowpos[v]=upto++))); \\ perm
while(upto++<=#rowpos,
rowpos[upto]=upto);
\\ print(" final "Vec(rowpos));
rowpos;
,
\\ flags==0
id);
}
vpar_seq_premax(vpar) = vpar_INTERNAL_premax(vpar,1);
{
addhelp(vpar_seq_premax,
"seq = vpar_seq_premax(vpar)
Return a Vecsmall of the vertex numbers 1..#vpar in sequence of pre-order
lex-max. The return type is Vecsmall since it is a permutation of the
integers 1..#vpar.
This is a pre-order traversal with child subtrees in order giving lex()
biggest depths vector (vpar_depths_vector()), and by ascending child vertex
number between same (isomorphic) subtrees. Biggest depths means biggest
vpar vector too after relabelling to this seq, hence name \"premax\".
See vpar_to_unlabelled() for relabelling to this seq.
A \"code\" vec=vecextract(vpar,seq) from this sequence is not reversible, in
general, as various trees get the same extract.
seq taken in reverse is a rooted stabilizing sequence in Holton's sense that
successively deleting vertices in this order (seq last to first) does not
introduce new rooted automorphisms. Reverse seq is by smallest subtrees and
each delete makes its subtrees yet smaller and so not equal to any of the
other subtrees.");
}
vpar_to_unlabelled(vpar) = \
vpar_relabel_perm(vpar, vpar_INTERNAL_premax(vpar,2));
{
addhelp(vpar_to_unlabelled,
"vpar = vpar_to_unlabelled(vpar)
Return vpar relabelled to pre-order lex-max; so vpar_is_preorder() and among
possible pre-orders take the one with lex() biggest vpar vector, which is
also lex() biggest depths vector (vpar_depths_vector()).
This is the unlabelled representative form used by vpar_unlabelled_next(),
and is a canonical form for vpar as a rooted unlabelled tree. Iteration can
proceed from the returned vpar, and two vpar are vpar_is_isomorphic() if and
only if their vpar_to_unlabelled() forms are equal.
The relabelling is to vertex sequence vpar_seq_premax(). It is pre-order in
taking parent followed by child subtrees, and maximum vpar (and depths) is
achieved by sorting siblings according to subtree ids in the manner of Aho,
Hopcroft and Ullman. Sorting means time taken is not linear, but in
practice it barely differs from linear unless or until perhaps a big tree
with many siblings in each row.");
}
\\ 1 L2
\\ ___ / \ \
\\ L1 2 5 7
\\ / \
\\ 3 6
\\ /
\\ 4
vpar_to_free(vpar) =
{
\\ For unicentral, the free primary is rooted at that centre and
\\ everything else premax.
\\
\\ For bicentral, if the two tree halves are size(L1) != size(L2) then
\\ root at the bigger size one and everything else premax.
\\
\\ For bicentral and halves size(L1) = size(L2) exactly half each, then
\\ want L1 as the lex smaller. This is done by putting them as siblings
\\ under a new root and telling premax to order these (at depth=1) with L1
\\ as lex smaller (instead of normally in premax the first sibling would
\\ be lex bigger).
\\
\\ This idea of putting a new root above bicentral for premax is intended
\\ for something on free forests. In a forest there will be an order
\\ among the component trees, then within each component. But don't yet
\\ have a canonical free forest format chosen. For only one tree, the L1
\\ and L2 could be made roots (thus siblings) and tell premax to treat
\\ them appropriately.
\\ print("vpar_to_free() "vpar);
my(n=#vpar,
c=vpar_centres(vpar));
if(#c==1,
\\ print(" unicentral "c);
vpar=vpar_reroot(vpar,c[1]);
,
#c==2,
\\ print(" bicentral "c);
if(vpar[c[2]]==c[1], c=Vecrev(c));
my(s=lex(2*vpar_subtree_size(vpar,c[1]), n)); \\ >0 when c[1] bigger
if(s,
\\ print(" size L1 != L2, reroot to bigger as L2");
vpar=vpar_reroot(vpar,c[if(s>0,1,2)]);
,
\\ print(" size L1 = L2, must compare lex");
vpar=vpar_reroot(vpar,c[2]);
vpar=Vec(vpar,n+1); \\ append new root, vpar[n]=0
vpar[c[1]] = vpar[c[2]] = #vpar;
));
\\ print(" root above ",vpar);
vpar=vpar_relabel_perm(vpar, vpar_INTERNAL_premax(vpar, if(#vpar>n,6,2)));
\\ print(" relabelled "vpar);
\\ delete any added root, reconnect bicentral
my(prev_v=0, prev_p=0);
for(v=1,n,
my(p=vpar[v]);
if(p>n, \\ root above
\\ print(" v="v" p="p" cf prev_p="prev_p" prev_v="prev_v);
vpar[v] = if(p==prev_p, prev_v, 0);
prev_p=p; prev_v=v));
\\ print(" final "Vec(vpar,n));
Vec(vpar,n);
}
{
addhelp(vpar_to_free,
"vpar = vpar_to_free(vpar)
Return vpar relabelled and rerooted to free primary form.
vpar must be a tree, not a forest.
Free primary form is used by vpar_free_next() iteration and is a canonical
form for vpar as a free tree. Two vpar are vpar_is_free_isomorphic() if and
only if their forms here are equal.
See vpar_free_next() for notes on free primary form. It means rerooting to
the centre of a unicentral, or to the bigger half of a bicentral (bigger as
compared by premax). Then relabelling is per vpar_to_unlabelled().");
}
\\
\\ For reference, vpar_to_free() prior to version 20 had a bug where the
\\ root for a bicentral tree was chosen bigger by lex instead of the correct
\\ bigger by size then lex.
\\
\\ Halves of equal size were ok. Halves where the bigger in size is also
\\ the bigger in lex were ok. But unequal size halves where lex order
\\ opposite to size order got the wrong result. Eg.
\\
\\ 2-------1 left half right half
\\ | | \ \ vertices 4 < 5
\\ 3 6 8 9 depths 0,1,2,2 > 0,1,2,1,1
\\ | \ |
\\ 4 5 7
\\
\\ The correct is root at the right half vertex 1 since right half is bigger
\\ in size. The wrong code was root at vertex 2 because it looked only at
\\ lex of depths vectors (and it renumbered throughout accordingly).
\\
\\ One use for vpar_to_free() is to make a canonical form for testing free
\\ tree equality or un-duplicating. The wrong lex-only compare was still
\\ canonical, but different from the free primary of vpar_free_next() etc.
\\-----------------------------------------------------------------------------
\\ Lexicographically Minimum Vpar
\\ roots by descending num children,
\\ so root 1 appears most before root 2, etc
\\ when same num children can choose the order of the roots
\\ choose the one with the most grandchildren of the most children
\\ their children by descending num grandchildren, etc
\\ R1 R2
\\ / \ / \
\\ C1 C1 C2 C2
\\ /|\
\\ G G G
\\ 0 0 1 1 2 2
\\ R1 R2 C1 C1 C2 C2 G G G
\\ 1 2 3 4 5 6
\\ want G children of C1 or C2 whichever has more
\\ want R1 or R2 more likewise
\\ children keys
\\
\\ GP-Test my(func=min); func(1,5) == 1 /* ascending */
\\ GP-Test my(func=max); func(1,5) == 5
vpar_INTERNAL_lexmin(vpar,flags) =
{
\\ print("vpar_seq_lexmin() "vpar);
my(id = vectorsmall(#vpar,i,1)); \\ id[v] of vertex
if(#vpar,
my(num_children = vpar_INTERNAL_num_children_vecsmall(vpar),
rows = vpar_row_vertices_vector(vpar),
rowpos = vectorsmall(#vpar), \\ like vpar_rowpos_vector() but +1
\\ subtree_chain[v+1] = next vertex after v in pre-order traversal
\\ at and below v
\\ subtree_tail[v+1] = last vertex number of subtree at and below v.
subtree_chain = vectorsmall(#vpar+1),
subtree_tail = vpar_INTERNAL_identity_perm(#vpar+1),
order = vpar_INTERNAL_identity_perm(#rows[#rows]),
r = #rows); \\ row number acting on
while(
\\ print(" row r="r" "if(r,rows[r]));
\\ print(" order="Vec(order)" is row "vecextract(rows[r],order));
\\ print(" subtree chain");
for(i=1,#order,
my(v=rows[r][order[i]], p=vpar[v]);
\\ print(" append v="v" tail "subtree_tail[v+1]-1" to p="p);
subtree_chain[subtree_tail[p+1]] = v;
subtree_tail[p+1] = subtree_tail[v+1];
\\ print(" now p="p" chain "subtree_chain[p+1]" tail "subtree_tail[p+1]-1);
);
r--; \\ while() loop stop at r=0
,
\\ rowpos[v] = position in its row, 1=first
for(i=1,#rows[r], rowpos[rows[r][i]] = i);
\\ keys = [ [-len,id1,id2,...], ... ] one key each of rows[r] vertices
\\ len is length of the key vector, negated so biggest len sorts first
\\ id1 etc is each child vertex id
\\ print(" keys");
my(keys=vector(#rows[r],i,
my(c = -(num_children[rows[r][i]]++)); \\ pre-increment
vectorsmall(-c,i,c)));
for(i=1,#order,
my(v=rows[r+1][order[i]],
p=vpar[v]);
\\ print(" v="v" id="id[v]" to p="p" pos "num_children[p]);
keys[rowpos[p]][num_children[p]] = id[v];
num_children[p]--);
\\ print(" keys "keys);
order = vecsort(keys,,1); \\ ascending, so lexmin smallest first
\\ print(" order "Vec(order));
\\ print(" is keys "vecextract(keys,order));
\\ print(" is row "vecextract(rows[r],order));
\\ assign ids, same where equal
\\ print(" ids i=1 o="order[1]" v="rows[r][order[1]]" id 1");
id[rows[r][order[1]]] = 1;
my(upto=1);
for(i=2,#order,
id[rows[r][order[i]]]
= (upto += (keys[order[i]]!=keys[order[i-1]]));
\\ print(" i="i" o="order[i]" v="rows[r][order[i]]" id "upto);
);
);
\\ print(" subtree_chain "Vec(subtree_chain));
\\ print(" subtree_tail "Vec(subtree_tail));
\\ print(" starting v=0 chain="subtree_chain[1]);
my(v=0, d=1);
\\ Make linked lists of the vertices in each row, by following down the
\\ pre-order subtree_chain.
\\ rows[d] = first v in the row, or 0 if none
\\ rowpos[v] = next vertex in the row of v, or 0 if no more
\\ (re-using the rowpos space)
\\ subtree_tail[v+1] = depth of v, starting 1 for roots
\\ (re-using the subtree_tail space)
\\
rows=vectorsmall(#rows);
\\ print(" make row lists");
subtree_tail[1] = 0; \\ depth "above" the roots
while(v=subtree_chain[v+1],
my(d = subtree_tail[v+1] = subtree_tail[vpar[v]+1] + 1);
rowpos[v]=rows[d]; \\ prepend
rows[d]=v);
\\ print(" depths ",Vec(subtree_tail));
\\ Concatenate rows[] linked lists.
\\ They are prepended above, so reverse order to make seq.
\\ seq made re-using the id[] space.
my(upto=#vpar+1);
if(flags,
forstep(d=#rows,1,-1, \\ perm
my(v=rows[d]);
while(v, id[v]=upto--; v=rowpos[v]));
,
forstep(d=#rows,1,-1, \\ seq
my(v=rows[d]);
while(v, id[upto--]=v; v=rowpos[v]));
);
);
\\ print(" final seq "Vec(id));
id;
}
vpar_seq_lexmin(vpar) = vpar_INTERNAL_lexmin(vpar,0);
{
addhelp(vpar_seq_lexmin,
"seq = vpar_seq_lexmin(vpar)
Return a Vecsmall of the vertex numbers 1..#vpar in sequence of lex()
smallest resulting vpar. The return type is Vecsmall since it is a
permutation of the integers 1..#vpar.
This is the vertex sequence vpar_to_lexmin() relabels to. See it for notes
on lexmin. The sequence is downwards (vpar_seq_is_downwards()) by rows,
then within a row by parent's position in the row above, and between
siblings first by most children, and for equal number of children
recursively into subtrees so most children at the first difference.
A code vec=vecextract(vpar,seq) from this sequence is not reversible, in
general, since various trees get the same extract.");
}
vpar_to_lexmin(vpar) = \
vpar_relabel_perm(vpar, vpar_INTERNAL_lexmin(vpar,1));
{
addhelp(vpar_to_lexmin,
"vpar = vpar_to_lexmin(vpar)
Return vpar relabelled to the lex() smallest vpar vector among all its
possible relabellings (vpar_relabel_perm()).
This is a canonical form for vpar as a rooted unlabelled tree (a different
canonical form than vpar_to_unlabelled() gives). Two vpar are isomorphic
(vpar_is_isomorphic()) if and only if their vpar_to_lexmin() are equal.
lex() smallest vpar starts with roots labelled to 1..r so start vpar[1..r] =
[0..0] which is smallest possible. Then children of roots so vpar
[1,1,1,..2,2..,r,r] which are now the smallest possible. Then children of
those etc so rows in order of parents. Among siblings the one with most
children is first so there is most copies of the smallest parent number.
In general, siblings get an \"id\" which is a vector of number of children
then ids of those children in ascending order of those ids. The effect is
to prefer the most children at the first place sibling subtrees differ.
This recursive construction of ids is similar to premax in
vpar_to_unlabelled() and is implemented by upwards sorting in a similar way,
except here ids have number of children first. The sorting means time taken
is not linear, though in practice it barely differs from linear.
See vpar_seq_lexmin() for the vertex sequence relabelled.");
}
\\-----------------------------------------------------------------------------
\\ Unlabelled Tree Iteration
vpar_unlabelled_first(n) = vpar_make_path(n);
{
addhelp(vpar_unlabelled_first,
"vpar = vpar_unlabelled_first(n)
Return the first unlabelled rooted tree representative for iteration by
vpar_unlabelled_next().
This is the path of n vertices the same as vpar_make_path().");
}
vpar_unlabelled_last(n,forest=0) = if(forest>0, vector(n), vpar_make_star(n));
{
addhelp(vpar_unlabelled_last,
"vpar = vpar_unlabelled_last(n,forest=0)
Return the last unlabelled rooted tree representative in the iteration of
vpar_unlabelled_next(). The last stree is the star per vpar_make_star(n).
Optional parameter \"forest\" can be 1 for the last forest, when iterating
forests. The last forest is all singletons, [0,0,...,0].");
}
\\ For vpar_unlabelled_next() and vpar_unlabelled_nextR(), the iteration
\\ rule in terms of vpar vector is to find p which is the last vertex with a
\\ grandparent g (ie. last depth>=2). Its parent is q=vpar[p]. Replace all
\\ vertices p..#vpar with copies of the subtree q..p-1 inclusive. q is the
\\ top of that subtree and the copies go under q's parent g the same as q.
\\ If p..#vpar is not a whole multiple of q..p-1 then a final partial copy
\\ (a partial copy of preorder is still preorder).
\\
\\ For a forest, p is the last with a parent and then the same q=vpar[p] and
\\ copy. q might have parent 0, ie. no parent, and the copies of q..p-1
\\ then get that too, making new component trees.
{
if(iferr(eval("(~x)->0");1,e,0),
\\ have GP >= 2.13 call-by-reference
\\ MY-CHECK-GP-FUNCTION-DEFINITION: vpar_unlabelled_next(vpar,forest=0)
eval("
vpar_unlabelled_next(vpar,forest=0) =
{
my(vpar=vpar); /* only modify copy, needed in GP 2.13.1 */
if(vpar_unlabelled_nextR(~vpar,forest),vpar,'none);
}
");
\\ MY-CHECK-GP-FUNCTION-DEFINITION: vpar_unlabelled_nextR(~vpar,forest=0)
eval("
vpar_unlabelled_nextR(~vpar,forest=0) =
{
/* flip sense of forest variable so 1=tree, 0=forest,
and also provoke an error if forest is not a number */
forest=(forest<1);
my(q);
forstep(p=#vpar,2,-1,
(q=vpar[p]) > forest || next; /* q>0 for forest, q>1 for a tree */
my(g=vpar[q],
offset=p-q);
until(q++; p++>#vpar,
vpar[p] = vpar[q] + if(vpar[q]!=g, offset));
return(1));
0;
}
");
addhelp(vpar_unlabelled_nextR,
"bool = vpar_unlabelled_nextR(~vpar,forest=0)
vpar is a call-by-reference unlabelled rooted tree representative in
pre-order lex-max form. Modify vpar to step to the next such tree and
return 1. Or return 0 if no next tree.
See vpar_unlabelled_next() for further notes. Optional parameter \"forest\"
can be 1 to step a representative forest to the next forest.
The first vpar is vpar_unlabelled_first(). Then loop like the following
(see examples/unlabelled-iteration.gp for a complete program)
n=6; \\\\ number of vertices
my(vpar=vpar_unlabelled_first(n));
until(!vpar_unlabelled_nextR(~vpar),
\\\\ each vpar rooted unlabelled representative
);
Beyer and Hedetniemi show this iteration is constant amortized time, so that
the time taken per tree approaches a constant when averaged across all trees
of a given N vertices. Similarly for forests.");
,
\\ only GP < 2.13 so no call-by-reference
eval("vpar_unlabelled_next(vpar,forest=0) =
{
/* flip sense of forest variable so 1=tree, 0=forest,
and also provoke an error if forest is not a number */
forest=(forest<1);
my(q);
forstep(p=#vpar,2,-1,
(q=vpar[p]) > forest || next; /* q>0 for forest, q>1 for a tree */
my(g=vpar[q],
offset=p-q);
until(q++; p++>#vpar,
vpar[p] = vpar[q] + if(vpar[q]!=g, offset));
return(vpar));
'none;
}
");
);
}
\\ B+H paper http://doi.org/10.1137/0209055 but pdf restricted(?)
{
addhelp(vpar_unlabelled_next,
"vpar = vpar_unlabelled_next(vpar,forest=0)
Return the next unlabelled rooted tree representative in pre-order lex-max
form, or return symbol 'none if no more. This is per
Beyer and Hedetniemi, \"Constant Time Generation of Rooted Trees\",
SIAM Journal of Computing, volume 9, 1980, pages 706-712.
The first vpar is vpar_unlabelled_first(). Then loop like the following
(see examples/unlabelled-iteration.gp for a complete program)
n=6; \\\\ number of vertices
my(vpar=vpar_unlabelled_first(n));
until((vpar=vpar_unlabelled_next(vpar)) == 'none,
\\\\ each vpar rooted unlabelled representative
);
Optional parameter \"forest\" can be 1 to iterate through forests.
The first vpar is the same for trees and for forests.
The returns are unlabelled representatives in the sense that there is one
from each equivalence class of vpar_is_isomorphic(). The number of trees is
vpar_count_unlabelled().
Each tree is pre-order labelled (vpar_is_preorder()) and has the lex()
greatest vpar_depths_vector() of any pre-order tree isomorphic to it.
This also means lex() greatest vpar vector (they compare the same in
preorder). Iteration goes by decreasing such vectors, so
next_vpar = vpar_unlabelled_next(vpar)
lex(vpar, next_vpar) > 0 \\\\ lex decreasing vectors
lex(vpar_depths_vector(vpar), vpar_depths_vector(next_vpar)) > 0
Beyer and Hedetniemi show that an in-place modify step is constant amortized
time, but here copying vpar for the return means time linear in #vpar. See
vpar_unlabelled_nextR() for a call-by-reference in-place modify.");
}
\\ vpar is a tree or forest labelled in preorder, so vpar_is_preorder(vpar).
\\ u,v are any vertices in vpar.
\\ Compare the subtrees at and below u,v lexically and return 1,0,-1 ordering.
\\ This is the same as lex(vpar_subtree(vpar,u), vpar_subtree(vpar,v)),
\\ but done by comparing vpar[] contents with suitable offset.
\\ The comparison is by vertex numbers, and because vpar is preorder it is
\\ the same as comparison by depths vectors.
\\
\\ Parent of u is up=vpar[u]. The first vertex after u subtree has parent
\\ <=up because preorder means it is a child of up or some ancestor of up
\\ (ancestors being | | \ sibling (=q), depth of p
\\ * * * * p increased by 1 by under q,
\\ | | | | those after p minimum
\\ * p-1 * p-1 depths (1 tree, 0 forest)
\\
\\ p is a new vertex at the end of the q subtree. r is the previous sibling
\\ of q. Canonical form demans SubtreeDepths(r) >= SubtreeDepths(q).
\\ Extending q by the addition of p might cause this to be violated.
\\
\\ The code uses vpar_INTERNAL_preorder_subtree_cmp() to check. if new q
\\ form causes r=q and now r=
\\ that. So order flip means r must have had nothing there, so r and q the
\\ same length and same structure.
\\
\\ r and q identical means that any depth increase in its vertices q+1
\\ through to p-1 would make q bigger r=q violated then step to p <- q and
\\ q <- r as the new depth drop to attempt and check.
\\
\\ If r>=q after q extended then that's good, but must go up and also check
\\ parent g against its previous sibling h. This check is necessary because
\\ it's possible had h==g so that increase in g violates h>=g. Such a
\\ violation means must drop g in the same way, and for the same reason, as
\\ described above.
\\
\\ In the code, the outer loop finds the end-most p. The inner loop is the
\\ change to p, and then step p<-q, q<-r. The inner-inner loop is subtree
\\ q>=p for successive previous siblings, and when no more siblings up to
\\ parent and its siblings.
\\
vpar_unlabelled_prev(vpar,forest=0) =
{
\\ print("vpar_unlabelled_prev() "vpar);
my(q);
forstep(p=#vpar,2,-1,
if(q=vpar_INTERNAL_preorder_prev_sibling_of_vertex(vpar,p),
\\ p = biggest vertex with a preceding sibling (= q)
\\ 1=tree, 0=forest, to set vpar of vertices >p changed;
\\ using bitxor() to provoke error if symbol 'forest instead of 0,1.
forest = bitxor(forest,1);
\\ e = last vertex not yet zapped to "forest"
my(e=#vpar);
while(1,
\\ print(" put p="p" under prev sibling q="q);
vpar[p]=q;
for(i=p+1,e, vpar[i]=forest);
e = p;
p = q;
\\ print(" next sibling q=",(q=vpar_INTERNAL_preorder_prev_sibling_of_vertex(vpar,p))," cmp ",if(q,vpar_INTERNAL_preorder_subtree_cmp(vpar,q,p)));
while(!(q=vpar_INTERNAL_preorder_prev_sibling_of_vertex(vpar,p))
|| vpar_INTERNAL_preorder_subtree_cmp(vpar,q,p) >= 0,
\\ print(" up to p="vpar[p]);
(p = vpar[p]) || return(vpar)))));
'none;
}
{
addhelp(vpar_unlabelled_prev,
"vpar = vpar_unlabelled_prev(vpar,forest=0)
Return the previous unlabelled rooted tree representative in pre-order
lex-max sequence, or return symbol 'none if no more.
Optional parameter \"forest\" can be 1 to iterate through pre-order forests.
This is the reverse of vpar_unlabelled_next(). An iteration can run from
vpar_unlabelled_last(n,forest) by successive prev. But going forwards by
next is faster.");
}
\\ vpar_is_unlabelled() tests for an unlabelled forest representative, which
\\ is "premax" pre-order and maximum depths vector (lexicographically).
\\ Suppose vertices 1 to v-1 inclusive (possibly none when v==1) are known
\\ to be premax and v is next considered,
\\
\\ z *
\\ | \
\\ w
\\ | \
\\ u .. v u,v adjacent siblings
\\ | \
\\ v-1
\\ \
\\ v
\\
\\ Like vpar_is_preorder(), v is to have as its parent either v-1, or some
\\ ancestor v-1, or parent 0 if v is a root.
\\
\\ If parent v-1 then v is the first child of v-1. v has no previous
\\ siblings so there is no check needed of subtrees in decreasing lex order.
\\ So v++ to look at the next vertex.
\\
\\ Otherwise, on finding vpar[u] == vpar[v] == w, have u is the previous
\\ sibling of v and want to check that subtrees u >= subtree v.
\\ Depths vector order is the same as vpar vectors order with suitable
\\ offset -(v-u) on each vpar[v+i] to bring them to the same start as u.
\\
\\ Must have v+i <= #vpar or it's the end of vpar. If v+i > #vpar then u
\\ and v are equal as far as v extended, so u >= v which is good. The code
\\ lets this case end the compare loop and lets the outer while() loop end.
\\
\\ Must have vpar[v+i] >= v so v+i has a parent within the v subtree. If
\\ vpar[v+i] < v then u and v are equal as far as v had extended, is good.
\\ The next vertex checked in the outer while() loop is v+1. The preorder
\\ etc checks catch any wildly small vpar[v+i] value (including negatives
\\ etc).
\\
\\ Otherwise, compare vertex parents as an equivalent of vertex depth, with
\\ suitable offset for vpar[v+i] to bring it to the same start point as u.
\\
\\ vpar[u+i] compare vpar[v+i] - (v-u)
\\
\\ If vpar[v+i] bigger then this is bigger depths so u > v, is good. The
\\ next outer loop examines v+i to see it's not wildly too big etc. If
\\ vpar[v+i] smaller then this is u < v, is bad, not premax, return 0.
\\ If equal then keep looking. The compare loop is cast as loop while u<=v
\\ and check and return 0 on u= v already done ensures
\\
\\ vpar[v+i]-(v-u) >= v-(v-u) = u
\\
\\ so that vpar[v+i]-(v-u) is bigger than w < u. Note that vpar[v+i]-(v-u)
\\ without previously checking vpar[v+i]>=v would be in danger of coming out
\\ equal to w if vpar[v+i] is a crazy value: which it may well be since the
\\ compare at this point is the first time vpar[v+i] has been examined.
\\
\\ For time taken, the preorder checking does at most #vpar steps upwards,
\\ as described with vpar_is_preorder(), being up steps of balanced binary.
\\
\\ The comparison loop may pass over some parts of the nested subtrees
\\ multiple times, but this is a good thing. When some of a v subtree is
\\ found to be equal to whatever previous u subtree, no searches up or other
\\ work is needed in that v, so just 1 compare per vertex in that part of v.
\\
\\ Believe the worst case time is a forest of singletons taking 3*#vpar
\\ checks to see good, depending exactly how checks and fetches are costed.
\\ Each v>=2 singleton compares vpar[v]!=v-1, then vpar[v]==vpar[v-1], then
\\ vpar[v+1]#vpar || vpar[v]=q \\ after start of v subtree
&& (c=lex(vpar[u],
vpar[v]-p)) <= 0 \\ u<=v keep comparing
,
if(c<0, \\ u= v, good");
));
1;
}
{
addhelp(vpar_is_unlabelled,
"bool = vpar_is_unlabelled(vpar)
Return 1 if vpar is an unlabelled tree or forest representative per
vpar_to_unlabelled() or vpar_unlabelled_next(), or return 0 if not.
The test is vpar_is_preorder() and then sibling subtrees must be in
lexicographically descending order of their subtree depths vectors.
This is done efficiently by some comparisons using a fixed amount of working
space. The worst case time taken is linear in #vpar but a random vpar ought
to be disproved in just a few steps, most of the time.");
}
\\-----------------------------------------------------------------------------
\\ Free Tree Iteration
vpar_free_first(n) =
{
\\ level sequence [1,2,...,k, 2,3,...,n-k+1] as 1-based, for n>=4
\\ m = vertex number of second child of root
\\ vpar [0,1,...,m-2, 1, m,m+1,...,n-1]
\\ each vertex parent is i-1 predecessor, except at m
\\ k=floor(n/2)+1 in 1-based levels
\\ m = k+1 = floor((n+4)/2)
my(m = (n+4) >> 1);
vector(n,i, if(i==m,1, i-1));
}
{
addhelp(vpar_free_first,
"vpar = vpar_free_first(n)
Return the first free tree representative in the iteration of
vpar_free_next().
This is a pre-order path n with root in the middle so that n odd is two
limbs equal length or n even is first limb 1 bigger.");
}
vpar_free_last(n) = vpar_make_star(n);
{
addhelp(vpar_free_last,
"vpar = vpar_free_last(n)
Return the last free tree representative in the iteration of
vpar_free_next(). This is the star per vpar_make_star(n).");
}
\\ \\ second last
\\ \\ depths [0,1,2, 1,1,1,1 ]
\\ \\ vpar [0,1,2, 1,1,1,1 ]
\\ vpar_free_second_last(n) = vector(n,i, if(i<=3,i-1,1));
\\ The primary form from Wright et al is like
\\
\\ r=1 L2
\\ / \ pre-order, so L1 is vertex v=2
\\ L1 r+1=2 m
\\ / / \
\\ * * q
\\ / | / \
\\ * t p
\\
\\ L1 = the subtree at and below vertex 2.
\\ L2 = the root and everything below m, so all except L1.
\\
\\ The conditions to be primary are whole tree pre-order lex-max (per
\\ vpar_unlabelled_next() per Beyer and Hedetniemi), and then
\\
\\ (ii) height(L1) <= height(L2)
\\
\\ (iii) if height(L1) = height(L2)
\\ then size(L1) <= size(L2) number of vertices
\\
\\ (iv) if height(L1) = height(L2) and size(L1) = size(L2)
\\ then lex L1 <= L2
\\
\\ The whole tree is lex-max, so have height(L1)+1 >= height(L2), ie. L2 at
\\ t doesn't go deeper than L1 in the whole tree. So the height condition
\\ is L2 going down to same depth as L1, or to 1 shorter,
\\
\\ height(L1) + 1 = height(L2) unicentral tree, root=centre
\\ or height(L1) = height(L2) bicentral tree, root,root+1
\\
\\ The two heights are the respective max distances from the root, and so
\\ the two height cases are unicentral or bicentral. The iteration goes
\\ first through height(L2) = height(L1)+1 since that is the bigger t.
\\
\\ The size condition L1<=L2 applies when equal heights. So bicentral trees
\\ have L2 as the bigger size half. For unicentral, the size condition does
\\ not apply and sizes can be either way. The unicentral form is unique
\\ since lex-max means L1,m,... children of the centre are lex descending.
\\
\\ Finally, for height and sizes L1==L2, the halves are lex ordered L1 <= L2.
\\ L1 starts 1 depth lower than L2 so this ordering is not enforced by the
\\ whole tree lex-max.
\\
\\ In the code, p is the prospective change point. The first prospective is
\\ the same as vpar_unlabelled_next(), ie. the last vertex with a grandparent.
\\
\\ "c" is the comparison made so far, based on the primary-ness of the
\\ prospective new L2.
\\
\\ c = lex heights, with -1 meaning L1L2 so new certainly non-primary
\\ otherwise heights equal so go on to:
\\
\\ c = lex sizes, which are unchanged by L2 step
\\ again -1 or 1 are definite primary or non-primary, and both can occur
\\ since don't always have size(L1)<=size(L2), only when equal heights.
\\ otherwise sizes equal so go on to:
\\
\\ c = equality of L1,L2 content, which being both primary pre-order etc
\\ is a compare with offset of the first and second halves of vpar,
\\ but knowing vpar[2] = vpar[m] = 1 under the root are fixed.
\\ The compare goes from high to low guessing common initial parts are
\\ likely.
\\
\\ L1 = 2 .. m-1 is size m-2
\\ L2 = 1,m,...,n is size n-m+2
\\ m-2 < n-m+2
\\ 2*m < n+4 is good
\\ 2*m >= n+4 is bad
\\
vpar_free_next(vpar) =
{
\\ print("vpar_free_next() "vpar);
\\ find p = last vertex with a grandparent
my(p);
forstep(v=#vpar,1,-1,
if(vpar[v] && vpar[vpar[v]],
p=v; break));
if(p<=1,
\\ print(" none");
return('none));
\\ print(" p="p);
\\ L1 height is at first vertex not a descent (ie. v-1 isn't its parent)
\\ m is the second child of the root 1, so second occurrence of vpar[x]==1
my(L1_height, m);
for(v=3,#vpar,
if(vpar[v]!=v-1,
L1_height=v-3;
\\ print(" L1 height preceding v="v", is "L1_height);
\\ m is vpar[m]==1 after L1 vpar[2]==1
for(v=v,#vpar,
if(vpar[v]==1,
\\ print(" m at v="v);
m=v;
break(2)))));
\\ L2 height is at first non-descent after m,
\\ calculated here including reduction if this is attained at p and
\\ therefore reduces if/when vertex p moves up
\\
my(L2_height=p-m); \\ if path m to p, reduced for p move up
for(v=m+1,p-1,
if(vpar[v]!=v-1,
L2_height = v-m;
\\ print(" L2 height preceding v="v" is "L2_height" reduced");
break));
my(c=lex(L1_height,L2_height)); \\ wanting L1_height < L2_height
\\ print(" height compare "L1_height" and "L2_height,if(c<0," => is primary",""));
if(!c,
\\ print(" L1,L2 equal height");
c=lex(m<<1, #vpar+4);
\\ print(" size compare "m-2" and "#vpar-m+2,if(c<0," => is primary",""));
if(!c,
my(o=(#vpar>>1)-1);
\\ print(" L1,L2 equal size, compare contents, o="o);
forstep(v=#vpar,#vpar-o+1,-1,
\\ print(" cmp vpar["v-o"]="vpar[v-o]" and vpar["v"]="vpar[v]);
if(vpar[v-o] != if(vpar[v]==1,2,vpar[v]-o),
c=-1; break)); \\ different, new is primary
);
);
\\ print(" final c="c,if(c<0," => is primary after L2 reduction",""));
my(e=#vpar);
if(c>=0,
\\ print(" skip non-primary");
p=m-1;
\\ print(" set p="p);
\\ level=1 1
\\ |
\\ level=2 *
\\ | p at level > 3 if q has grandparent
\\ level=3 q which means vpar[q] != 1
\\ |
\\ level=4 p
if(vpar[vpar[p]]!=1,
\\ print(" l = m-1 has level > 3");
\\ L1_height is reduced if p is the bottom of the height-attaining
\\ initial path down
if(p==L1_height+2,
L1_height--);
e=#vpar-L1_height));
\\ print(" copy q="q" to p-1="p-1);
my(q=vpar[p],
len=p-q);
until(p++>e, \\ pre-increment
\\ print(" set p="p" to "vpar[p-len] + if((p-q)%len, len));
vpar[p] = vpar[p-len] + if((p-q)%len, len));
if(e<#vpar,
\\ print(" path L2 down e="e);
vpar[e]=1;
for(i=e+1,#vpar, vpar[i]=i-1));
\\ print(" ret "vpar);
vpar;
}
{
addhelp(vpar_free_next,
"vpar = vpar_free_next(vpar)
Return the next free tree representative in \"primary\" pre-order lex-max
sequence, or return symbol 'none if no more. This iteration is per
R.A. Wright, B. Richmond, A. Odlyzko and B.D. McKay, \"Constant Time
Generation of Free Trees\", SIAM Journal of Computing, volume 15, 1986,
pages 540-548.
http://cs.anu.edu.au/~bdm/papers/ConstantTimeTrees.pdf
The first vpar is vpar_free_first(). Then loop like
n=6; \\\\ number of vertices
my(vpar=vpar_free_first(n));
until((vpar=vpar_free_next(vpar)) == 'none,
\\\\ vpar free tree representative
);
The returns are free tree representatives in the sense that there is one
from each vpar_is_free_isomorphic() equivalence class. The number of trees
is vpar_count_free().
The returns are a sub-sequence of vpar_unlabelled_next(). They are in the
same order, but only returning certain \"primary\" trees. The primary trees
are rooted at their centre (or second of a bicentral), have vertex v=2
height <= rest height, and if equal height then certain conditions on size
and lex depths.
Wright et al give conditions for when vpar_unlabelled_next() steps to a
non-primary, and how to skip from there to the next primary. Roughly
speaking, when the second half of the tree reduces to less than the first
then it's time to reduce the first half and restart second as a path (of
first height + 1).
Wright et al show that an in-place calculation is constant amortized time,
but vpar copying, and some recalculating of tree measures, means here the
time is linear in #vpar.");
}
\\------------------------------------------------------------------------------
\\ Preorder Iteration
vpar_preorder_first(n) = vpar_make_path(n);
{
addhelp(vpar_preorder_first,
"vpar = vpar_preorder_first(n)
Return the first pre-order labelled vpar for the iteration of
vpar_preorder_next(). This the path the same as vpar_make_path().");
}
vpar_preorder_last(n,forest=0) = vpar_unlabelled_last(n,forest);
{
addhelp(vpar_preorder_last,
"vpar = vpar_preorder_last(n,forest=0)
Return the last pre-order labelled vpar tree in the iteration of
vpar_preorder_next(). The last tree is the star per vpar_make_star().
Optional parameter \"forest\" can be 1 for the last forest, when iterating
forests with vpar_preorder_next(). The last forest is all singletons.");
}
\\ For vpar_preorder_next() and vpar_preorder_nextR():
\\ In a tree, v is the end-most vertex which has a grandparent g.
\\ In a forest, v is the end-most vertex which has a parent p (with p
\\ allowed to be a root).
\\
\\ g v raise to under its grandparent g, decreasing depth.
\\ | If no grandparent then for forest set v as new root.
\\ p If cannot raise v then look to raise v-1 and set v
\\ / \ and onwards as a path down which is maximum depths
\\ v-1 v for them so smallest depths decrease.
\\
\\ bitor() is used for testing "forest" parameter so as to catch a mistake
\\ like passing symbol 'forest instead of flag 0,1.
{
if(iferr(eval("(~x)->0");1,e,0),
\\ have GP >= 2.13 call-by-reference
\\ MY-CHECK-GP-FUNCTION-DEFINITION: vpar_preorder_next(vpar,forest=0)
eval("
vpar_preorder_next(vpar,forest=0) =
{
my(vpar=vpar); /* only modify copy, needed in GP 2.13.1 */
if(vpar_preorder_nextR(~vpar,forest),vpar,'none);
}
");
\\ MY-CHECK-GP-FUNCTION-DEFINITION: vpar_preorder_nextR(~vpar,forest=0)
eval("
vpar_preorder_nextR(~vpar,forest=0) =
{
/* flip sense of forest variable so 1=tree, 0=forest,
and also provoke an error if forest is not a number */
forest=(forest<1);
my(p);
forstep(v=#vpar,1,-1,
(p=vpar[v]) > forest || next; /* p>0 for forest, p>1 for a tree */
vpar[v] = vpar[p]; /* raise v to be under its grandparent */
for(i=v+1,#vpar, vpar[i]=i-1); /* fill with path down from v-1 */
return(1));
0;
}
");
addhelp(vpar_preorder_nextR,
"bool = vpar_preorder_nextR(~vpar,forest=0)
vpar is a call-by-reference pre-order labelled tree. Modify vpar to step
to the next pre-order tree and return 1. Or return 0 if no next tree.
See vpar_preorder_next() for further notes.
The first vpar is vpar_preorder_first(). Then loop like
n=6; \\\\ number of vertices
my(vpar=vpar_preorder_first(n));
until(!vpar_preorder_nextR(~vpar),
\\\\ each pre-order vpar
);
Optional parameter \"forest\" can be 1 to iterate pre-order forests.
The first is the same vpar_preorder_first().");
,
\\ only GP < 2.13 so no call-by-reference
eval("vpar_preorder_next(vpar,forest=0) =
{
/* flip sense of forest variable so 1=tree, 0=forest,
and also provoke an error if forest is not a number */
forest=(forest<1);
my(p);
forstep(v=#vpar,1,-1,
if((p=vpar[v]) > forest, /* p>0 for forest, p>1 for a tree */
vpar[v] = vpar[p]; /* raise v to be under its grandparent */
return(vpar));
vpar[v]=v-1); /* fill for path down, ready when v is found */
'none;
}
");
);
}
{
addhelp(vpar_preorder_next,
"vpar = vpar_preorder_next(vpar,forest=0)
vpar is a pre-order labelled tree. Return the next pre-order vpar tree, in
lexicographically decreasing order, or return symbol 'none if no more.
The first vpar is vpar_preorder_first(). Then loop like
n=6; \\\\ number of vertices
my(vpar=vpar_preorder_first(n));
until((vpar=vpar_preorder_next(vpar)) == 'none,
\\\\ vpar all pre-order trees
);
Each vpar is pre-order (vpar_is_preorder()), so is one representative from
each equivalence class of ordered trees (vpar_is_ordered_isomorphic()).
The number of such trees is vpar_count_ordered().
Optional parameter \"forest\" can be 1 to iterate pre-order forests.
The first is the same vpar_preorder_first(). That first is a tree and the
iteration later goes variously through forests.
Iteration is in decreasing lex() order of the vpar vectors. This is also
lex() decreasing depths vectors (vpar_depths_vector()). For a given vpar,
the next smaller is by raising by one level the end-most vertex possible.
In a tree this means find v with a grandparent and move it up to under that
grandparent. Vertices >p are set as a path downwards since that is the
biggest there and hence the smallest lex decrease. For a forest, the last
vertex with a parent is moved up. It moves up to under its grandparent if
it has one, or to become a new root otherwise.");
}
\\ vpar is a tree or forest labelled in preorder, so vpar_is_preorder(vpar).
\\ Return the next smaller sibling of v, or return 0 if no smaller sibling.
\\
\\ The general case vpar_next_sibling_of_vertex() must examine each vertex
\\ v-1, v-2, etc, to see if it has same parent as v. Here can go up the
\\ tree v-1, vpar[v-1], vpar[vpar[v-1]] since pre-order means the previous
\\ sibling of v is an ancestor of v-1.
\\
vpar_INTERNAL_preorder_prev_sibling_of_vertex(vpar,v) =
{
my(p=vpar[v]);
if(v-- != p,
while(v && vpar[v]!=p, v=vpar[v]);
v);
}
\\ vpar_preorder_prev() previous preorder is by increasing depth of the last
\\ vertex v=#vpar, if possible.
\\
\\ a a
\\ / \ / v = #vpar last vertex,
\\ p v => * increase its depth by dropping down
\\ | | \
\\ * * v
\\ | |
\\ v-1 v-1
\\
\\ v goes under its preceding sibling p. That p is an ancestor of the
\\ preceding number v-1, so can search up from there as done in
\\ vpar_INTERNAL_preorder_prev_sibling_of_vertex() looking for p parent
\\ vpar[p]==a.
\\
\\ If v-1==a then v doesn't have a preceding sibling and v depth cannot be
\\ increased. The smallest depths increase is instead by increasing depth
\\ of v-1 by one, and putting v to its smallest depth. That smallest depth
\\ is 1 under the root for a tree, or 0 as singleton for a forest.
\\
\\ root
\\ \
\\ a a v
\\ / \ /
\\ p v-1 => p
\\ | \ |\
\\ * v * v-1
\\ | |
\\ v-2 v-2
\\
\\ In the code, the loop finds v with a sibling, so last which is not part
\\ of a final path.
\\
\\ A final v=#vpar which doesn't have a sibling must be child of v-1. That
\\ fixed last means number of such trees is rest vpar_count_ordered(n-1).
\\ vpar_count_ordered() is the Catalan numbers, and their proportion
\\ C(n-1)/C(n) -> 1/4. The mean number of end vertices examined is
\\ therefore 1 + 1/4*(1 + 1/4*(1 + ...)) = 1+1/4+1/4^2+... = 1+1/3.
\\ That fixed limit makes the change here "constant amortized time", but
\\ copying vpar for the return means instead linear time.
\\
vpar_preorder_prev(vpar,forest=0) =
{
\\ bitxor() on "forest" so as to catch a mistake like passing symbol
\\ 'forest instead of flag 0,1.
my(p);
forstep(v=#vpar,2,-1,
if(p=vpar_INTERNAL_preorder_prev_sibling_of_vertex(vpar,v),
\\ print("v="v" drop to under previous sibling p="p);
vpar[v]=p;
forest=bitxor(1,forest); \\ 1 for tree, 0 for forest
for(i=v+1,#vpar, vpar[i]=forest);
return(vpar)));
'none;
}
{
addhelp(vpar_preorder_prev,
"vpar = vpar_preorder_prev(vpar,forest=0)
vpar is a pre-order labelled tree. Return the previous such tree of lex()
decreasing sequence, which means the next bigger lex(), or return symbol
'none if no previous.
Optional parameter \"forest\" can be 1 to iterate through pre-order forests.
This is the reverse of vpar_preorder_next(). The iteration shown with that
function can be run in reverse by a loop like
n=6; \\\\ vertices
forest=1; \\\\ trees or forests
my(vpar=vpar_preorder_last(n,forest));
until((vpar=vpar_preorder_prev(vpar,forest)) == 'none,
\\\\ vpar all pre-order trees or forests
);");
}
vpar_preorder_ith(n,i,forest=0) =
{
\\ Cf Math::NumSeq::BalancedBinary where calculate triangle table by
\\ recurrence rather than individual binomials.
\\ c = binomial((r<<1)+d, r+d) * (d+1) / (r+d+1)
\\
\\ d = depth
\\ p = parent vertex to get depth d
\\ forest starts d=1 allowing it to get p=0 further roots
\\ tree starts d=0 cannot go below 0 so no more roots
\\ print("vpar_preorder_ith() n="n" i="i);
my(vpar=vector(n),
d=(forest>0), \\ and provoke error if symbol 'forest instead of 0,1
p=1); \\ parent vertex
for(v=2,n,
\\ print(" v="v" prev p="p" d="d);
d++;
while(1,
my(c=vpar_INTERNAL_Catalan_triangle(n-v+d,n-v));
\\ print(" d="d" r="r" i="i" cf "c);
if(i<=c,
\\ print(" set v="v" parent "p" depth "d);
vpar[v]=p; p=v; next(2));
d-- || return('none); \\ i too big
i-=c; p=vpar[p]));
\\ print(" final i="i);
if(i==1, vpar, 'none);
}
{
addhelp(vpar_preorder_ith,
"vpar = vpar_preorder_ith(n,i,forest=0)
Return the i'th pre-order tree of n vertices in lexicographically decreasing
sequence. This sequence is per vpar_preorder_next() and i is an index into
it.
Optional parameter \"forest\" can be 1 to get the i'th forest, per the
forest parameter to vpar_preorder_next().
i=1 is vpar_preorder_first(n).
i=vpar_count_ordered(n,forest) is vpar_preorder_last(n,forest).
If i is outside this range then return 'none.
The tree is constructed by considering how many trees there are where the
next vertex is deeper d+1. If i is bigger than this then it is a depth d or
smaller. Subtract the number of d+1 and compare again those with next depth
d, etc, until reaching i smaller than the count and so within that depth and
hence determining the next vertex depth. The number of trees with certain
next depth is given by the Catalan triangle. They're calculated here by
repeated binomial() formula.");
}
vpar_preorder_to_i(vpar,forest=0) =
{
\\ depths[v+1] = depth of vertex v
\\ depths[1] = depth of the v=0 "root", which is 0 for tree or 1 for forest.
\\ Other depths proceed from being under v=0 or under some other v.
\\ The offset implied by depths[1]=forest treats forests as if their roots
\\ are 1 deeper, so an imaginary extra vertex above and the i for that
\\ expanded tree.
\\
\\ ENHANCE-ME: The vpar_INTERNAL_Catalan_triangle() terms are for a range
\\ of depths. Can such a range be calculated more efficiently than binomial?
\\ Maybe Catalan terms by their recurrence, down to the biggest depth needed.
\\ Think have that in Math::NumSeq::BalancedBinary.
my(depths=vectorsmall(#vpar+1),
n=#vpar,
i=1);
depths[1]=forest;
for(v=1,#vpar,
for(d=(depths[v+1] = depths[vpar[v]+1]+1), depths[v],
i += vpar_INTERNAL_Catalan_triangle(n-v+d,n-v)));
i;
}
{
addhelp(vpar_preorder_to_i,
"i = vpar_preorder_to_i(vpar,forest=0)
vpar is a preorder labelled tree (vpar_is_preorder()).
Return its index i among preorder trees in descending lexicographic order.
This is the inverse of vpar_preorder_ith().
Optional parameter \"forest\" can be 1 for index among the preorder forests.
Forests include all trees, but the index of a tree among the forests is
different from its index among just trees.
The return ranges 1 <= i <= vpar_count_ordered(#vpar,forest).");
}
\\ vpar_make_random_preorder() uses the algorithm of
\\
\\ D. B. Arnold and M. R. Sleep, "Uniform Random Generation of Balanced
\\ Parenthesis Strings", ACM Transactions on Programming Languages and
\\ Systems, volume 2, 1980, pages 122-128.
\\
\\ and which is presented in Knuth volume 4A section 7.2.1.6 algorithm W,
\\ page 13 of draft http://www-cs-faculty.stanford.edu/~knuth/fasc4a.ps.gz
\\
\\ In Knuth, a Dyck word (balanced binary) is generated. p is how many
\\ 1-bits (opens) are yet to be generated and q is how many 0-bits (closes)
\\ are yet to be generated. Both start at n for a length 2n string.
\\
\\ Here the vpar output is generated by maintaining a depth which is the
\\ maximum permitted for the vertex v. The first v=1 is always depth 0.
\\ For a forest, v=2 is to be at 0 <= depth <= 1.
\\ For a tree, v=2 is always depth=1. The code effectively treats a tree as
\\ an initial root and then the rest a forest under it.
\\
\\ The loop is random() for whether vertex v is at depth d or not.
\\ The Catalan triangle (vpar_INTERNAL_Catalan_triangle()) is the number of
\\ forests with v permitted to be depth <= d. Among those, the number with
\\ v permitted to be at depth <=d-1 is then how many with v not at depth d.
\\
\\ The Catalan triangle is expressed in terms of n vertex forest with an
\\ initial path n-k vertices down attaining the depth, and k remaining
\\ vertices. So next vertex can be any 0 <= depth <= n-k. Vertex v has
\\ remaining k = n-v+1
\\
\\ Cat(k+d-1,k) v depth <= d-1 where k = n-v+1
\\ Cat(k+d, k) v depth <= d
\\
\\ Working through the Cat binomials is ratio
\\
\\ Cat(k+d-1,k) / Cat(k+d,k) = (d+k+1)*d / ((d+2*k)*(d+1))
\\
\\ So out of denominator (d+2*k)*(d+1) have
\\
\\ (d+2*k)*(d+1) = (d+k+1)*d v not at depth d
\\ + (d+2)*k v at depth d
\\
\\ In terms of Knuth's p,q counts, d = (n-p)-(n-q) = q-p, and k=p, giving
\\ (q+p)*(q-p+1) total and (q+1)*(q-p) not depth d or (q-p+2)*p at depth d.
\\
\\ As Knuth notes, the attraction of these proportions is that they can be
\\ applied using random numbers 0 <= r < n^2+n, rather than unranking a
\\ random full Catalan(n) bignum roughly 4^n. Actually Knuth says "less
\\ than n^2+n+1". Think at most random 0 <= r < n^2+n, and compare against
\\ at most n^2-1, or against floor(n^2/4)+1 if testing the opposite sense.
\\
\\ Other Ways:
\\
\\ Atkinson and Sack "Generating Binary Trees At Random" starts from a
\\ uniform choice of word with equal number 0s,1s and converts to a balanced
\\ binary word. See examples/equal01.gp for some code. Their approach
\\ moves some blocks in the equal01 to the end of the balanced word. That
\\ ought to be no worse than a first pass to find the length and hence end
\\ position to store, but Arnold and Sleep seems easier for now. If making
\\ a binary tree with linked cells then the ordering might be as simple as
\\ which joins to where.
\\
\\ An attraction of Atkinson and Sack is that making a random word of equal
\\ 0s and 1s needs random compares of at most 2n, which could be good for a
\\ single machine word. Think the total amount of randomness used is about
\\ the same, being n many randoms size n^2+n vs 2n many randoms size 2n.
\\ Of course it may be soon that CPUs with 64-bit machine word are the norm
\\ so that up to n=2^32 fits n^2 in a word, which would be an n much bigger
\\ than would want to explicitly make in an array.
\\
\\ See examples/random-decorated.gp for yet another approach this time by
\\ J.L Remy going from a decorated binary tree. It uses 2*n+1 for the
\\ decorated tree and then builds vpar separately so more memory than vpar
\\ alone as here, but again smaller random, 2*n+1 and 2 (one bit).
\\
\\
\\ GP-DEFINE rand_of_dk(d,k) = (d+2*k)*(d+1);
\\ GP-DEFINE comp_of_dk(d,k) = (d+2)*k;
\\ GP-DEFINE comp2_of_dk(d,k) = (d+k+1)*d;
\\ GP-Test rand_of_dk('d,'k) == comp_of_dk('d,'k) + comp2_of_dk('d,'k)
\\ GP-Test my(d=0); rand_of_dk(d,k) == 2*k
\\ GP-Test my(d=0); comp_of_dk(d,k) == 2*k
\\ GP-DEFINE Cat(n,k) = binomial(n+k,n) * (n-k+1)/(n+1);
\\ GP-Test matrix(10,10,k,d, Cat(k+d-1,k) / Cat(k+d,k)) == \
\\ GP-Test matrix(10,10,k,d, comp2_of_dk(d,k) / rand_of_dk(d,k) )
\\ n=k+d
\\ GP-Test matrix(10,10,k,n,n+=k; Cat(n-1,k) / Cat(n,k)) == \
\\ GP-Test matrix(10,10,k,n,n+=k; ((n+1)*(n-k)) / ((n+k)*(n-k+1)) )
\\
\\ GP-Test my(d=q-p,k=p); (d+2*k)*(d+1) == (q+p)*(q-p+1)
\\ GP-Test my(d=q-p,k=p); (d+k+1)*d == (q+1)*(q-p)
\\ GP-Test my(d=q-p,k=p); (d+2)*k == (q-p+2)*p
\\
\\ GP-DEFINE max_rand_and_k(n,func) = {
\\ GP-DEFINE my(m=0,mk=0);
\\ GP-DEFINE for(k=1,n-1,
\\ GP-DEFINE my(d=n-k, t=func(d,k));
\\ GP-DEFINE if(t>=m, m=t;mk=k));
\\ GP-DEFINE [m,mk];
\\ GP-DEFINE }
\\ GP-Test vector(10,n,n++; max_rand_and_k(n,rand_of_dk)) == \
\\ GP-Test vector(10,n,n++; [n^2+n,1]) /* maximum is at k=1, d=n-1 */
\\ GP-Test vector(10,n,n++; my(k=1, d=n-k); rand_of_dk(d,k)) == \
\\ GP-Test vector(10,n,n++; n^2+n)
\\ GP-Test vector(20,n,n++; max_rand_and_k(n,comp_of_dk)) == \
\\ GP-Test vector(20,n,n++; if(n==2,[3,1], n==3,[6,2], \
\\ GP-Test [floor(n^2/4)+n+1, ceil(n/2)+1]))
\\ GP-Test vector(10,n,n++; max_rand_and_k(n,comp2_of_dk)) == \
\\ GP-Test vector(10,n,n++; [n^2-1, 1])
\\
\\
\\ GP-DEFINE rand_of_pq(p,q) = (q+p)*(q-p+1);
\\ GP-DEFINE max_rand_by_pq(n) = {
\\ GP-DEFINE my(m=0,mp=0,mq=0);
\\ GP-DEFINE for(p=0,n,
\\ GP-DEFINE for(q=0,n,
\\ GP-DEFINE my(t = rand_of_pq(p,q));
\\ GP-DEFINE if(t>m, m=t; mp=p;mq=q)));
\\ GP-DEFINE [m,mp,mq];
\\ GP-DEFINE }
\\ GP-Test vector(10,n,n++; max_rand_by_pq(n)) == \
\\ GP-Test vector(10,n,n++; [n^2+n, 0, n])
\\ GP-Test vector(10,n,n++; my(p=0, q=n); rand_of_pq(p,q)) == \
\\ GP-Test vector(10,n,n++; n^2+n)
\\ maximum is at p=0, q=n
\\
vpar_make_random_preorder(n,forest=0) =
{
\\ "forest" parameter is used arithmetically in the for() loop to provoke
\\ an error if mistakenly pass a symbol instead (an unbound variable).
my(vpar=vector(n));
if(n>=2,
vpar[2]=1; \\ v=2 for tree, or overwritten for forest
my(p=2-forest, \\ parent vertex of v if v is set to depth d
d=1);
for(v=3-forest,n,
my(k=n-v+1); \\ how many remaining, including v
while(random((d+(k<<1))*(d+1)) >= (d+2)*k,
p=vpar[p];
d--);
vpar[v]=p;
p=v;
d++));
vpar;
}
{
addhelp(vpar_make_random_preorder,
"vpar = vpar_make_random_preorder(n,forest=0)
Return a random pre-order labelled vpar tree (vpar_is_preorder()).
Optional parameter \"forest\" can be 1 to make a random preorder forest.
This is a uniformly distributed random choice among the pre-order trees or
forests (of which there are vpar_count_ordered(n,forest) many).
The current code uses the algorithm of Arnold and Sleep which uses
successive random tests for a vertex to go up a depth level or not.
The random numbers are up to about n^2 so suitable for big n.");
}
\\ forest=-1 here works and is the same as all trees, though it doesn't
\\ really apply since "ordered" doesn't have a notion of different label for
\\ the tree root.
\\
vpar_count_ordered(n,forest=0) = \
if(n==0,1, vpar_INTERNAL_Catalan_number(n-(forest<1)));
{
addhelp(vpar_count_ordered,
"count = vpar_count_ordered(n,forest=0)
Return the number of ordered rooted trees of n vertices.
Ordered means sibling vertices and their subtrees have an order, but
otherwise unlabelled.
Optional parameter \"forest\" can be 1 to count forests. The roots of a
forest are reckoned siblings so have an order.
The number of forests is the Catalan number (one of its many
interpretations)
forests = C(n) = binomial(2*n,n)/(n+1) = 1,1,2,5,14,42,... (A000108)
and trees is one previous, by taking a tree as a vertex above a forest,
trees = if(n==0,1, C(n-1)) = 1,1,1,2,5,14,...");
}
\\ GP-DEFINE C(n) = binomial(2*n,n)/(n+1);
\\ GP-Test vector(6,n,n--; if(n==0,1, C(n-1))) == [1,1,1,2,5,14]
\\ GP-Test vector(6,n,n--; C(n)) == [1,1,2,5,14,42]
\\------------------------------------------------------------------------------
\\ Upwards and Downwards Trees
vpar_is_upwards(vpar) = \
for(v=1,#vpar, if(vpar[v] && vpar[v]<=v, return(0))); 1;
{
addhelp(vpar_is_upwards,
"bool = vpar_is_upwards(vpar)
Return 1 if vpar is labelled upwards, or return 0 if not.
An upwards labelling has vertex numbers strictly increasing as go upwards in
the tree, so each parent > child.
vpar can contain cycles. Any vpar_is_cyclic() is not upwards since a cycle
has somewhere an upwards decrease, or for a self-loop upwards equal.
The number of upwards trees is
if(n==0,1, (n-1)!) = 1,1,1,2,6,24,120,... (A159333)
since a new first vertex v=1 can be attached under any of the n-1
existing. The number of upwards forests is
n! = 1,1,2,6,24,120,720,... (A000142)
since new v=1 can be under n-1 existing or be a new component so n places.
A tree or forest which is upwards can be switched to downwards by
vpar_relabel_reverse(). Only the forest of singletons vpar=[0,0,...,0] is
both vpar_is_upwards() and vpar_is_downwards().
Various relabellings of a tree can make it upwards. The number of such
relabellings (including no-change if already upwards) is given by the
\"hook length\" formula,
num_perms = n! / product(vpar_subtree_sizes_vector(vpar))");
}
\\ Factorials with extra initial 1:
\\ vector(13,n,n--; if(n==0,1, (n-1)!))
\\ not in OEIS: 1, 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800
\\ not A000142 factorials, want extra initial 1
\\ not A104150 shifted factorials, starts 0 instead of want 1
\\ not A159333 "Roman factorial" shown above, starts -1,1,1,1,... offset -2
\\ not A154659 perms distance 10
\\ not A254523 perms step 11
\\ not A289282 factorials in 4-bytes
\\ not A208529 * 2, it starts only 2,2,4,12 which is two 1s
vpar_is_downwards(vpar) = \
for(v=1,#vpar, vpar[v] parent.
vpar can contain cycles. Any vpar_is_cyclic() is not downwards since a
cycle has somewhere downwards decrease, or for a self-loop downwards equal.
A tree or forest which is downwards can be switched to upwards by
vpar_relabel_reverse(). See vpar_is_upwards() on some counts.");
}
\\
\\ cf "heap ordered tree" which is an order among children and then a further
\\ labelling (so children 1,2 different from 2,1)
vpar_downwards_ith(n,i,forest=0) =
{
my(vpar=vector(n));
i--;
forest=(forest<=0); \\ 1=tree, 0=forest, for offset
forstep(pos=n,2,-1,
i=divrem(i,pos-forest);
vpar[pos] = pos-1-i[2];
i=i[1]);
if(i, 'none, vpar);
}
{
addhelp(vpar_downwards_ith,
"vpar = vpar_downwards_ith(n,i,forest=0)
Return the i'th downwards labelled tree of n vertices (vpar_is_downwards()).
i is by descending lex() order of vpar vectors.
Optional parameter \"forest\" can be 1 for the i'th downwards forest.
i=1 is the first tree or forest. For both it is path [0,1,2,...], the same
as vpar_make_path(n). This is the lex() greatest downwards vpar vector
(either tree or forest).
i=vpar_count_updown(n,forest) is the last. For tree it is star
[0,1,1,1,...] the same as vpar_make_star(n), or for forest it is all
singletons [0,0,...,0]. These are lex() smallest vectors.
Must have i>=1. If i > vpar_count_updown() then the return is symbol 'none.
For a tree, vertex v attaches under any of its v-1 predecessors, so vpar[v]
runs v-1 down to 1. This is digits of a mixed-radix representation of i-1,
running downwards. For a forest, each vertex can be a new root too so down
to 0.
Tree n,i is forest n-1,i with new root=1 above. This is like to
vpar_add_root_above_forest() but it is root #vpar whereas root=1 here.
See also vpar_make_random_downwards().");
}
vpar_downwards_to_i(vpar,forest=0) =
{
my(i=0);
forest=(forest<=0); \\ 1=tree, 0=forest, for offset
for(pos=1,#vpar,
i = i*(pos-forest) + (pos-1-vpar[pos]));
i+1;
}
{
addhelp(vpar_downwards_to_i,
"i = vpar_downwards_to_i(vpar,forest=0)
vpar is a downwards labelled tree. Return its index i among downwards trees
in descending lexicographic order. This is the inverse of
vpar_downwards_ith().
Optional parameter \"forest\" can be 1 for index of a forest the downwards
forests. These include the downwards trees, but the index of a tree among
forests is different from its index among just trees.
The return ranges 1 <= i <= vpar_count_updown(#vpar,forest).");
}
vpar_make_random_downwards(n,forest=0) =
{
\\ tree vpar[1] = 0
\\ vpar[2] = 1
\\ vpar[3] = 1 to 2, etc
\\ forest vpar[1] = 0
\\ vpar[2] = 0 to 1
\\ vpar[3] = 0 to 2, etc
forest=(forest<=0); \\ 1=tree, 0=forest, for offset
vector(n,v, if(v>1, random(v-forest) + forest));
}
{
addhelp(vpar_make_random_downwards,
"vpar = vpar_make_random_downwards(n,forest=0)
Return a random downwards labelled tree of n vertices (vpar_is_downwards()).
Optional parameter \"forest\" can be 1 for a random downwards forest.
This is a uniformly distributed random choice among the downwards trees
(of which there are vpar_count_updown(n,forest) many).");
}
vpar_count_updown(n,forest=0) =
{
\\ n-1 when tree and n!=0, done so as to provoke error if mistakenly pass
\\ symbol 'forest instead of 0,1
(n - (forest<=0 && n))!;
}
{
addhelp(vpar_count_updown,
"count = vpar_count_updown(n,forest=0)
Return the number of upwards labelled tree of n vertices.
The number of downwards labelled trees is the same.
Optional parameter \"forest\" can be 1 for count of upwards labelled
forests, and again downwards labelled is the same.
Upwards and downwards are the same simply by vpar_relabel_reverse().
Downwards count is each vertex under any of its v-1 predecessors so
factorial
T(n) = if(n==0,1, (n-1)!) = 1,1,1,2,6,24,120,... (A159333)
Similarly forests, with each v also able to be a new root so v places,
F(n) = n! = 1,1,2,6,24,120,720,... (A000142)");
}
\\-----------------------------------------------------------------------------
\\ Sets, Mostly
vpar_random_vertex(vpar) = if(#vpar, random(#vpar)+1, 0);
{
addhelp(vpar_random_vertex,
"v = vpar_random_vertex(vpar)
Return a uniformly distributed random vertex of vpar.
This is simply 1 to #vpar inclusive.
vpar can contain cycles.
For empty vpar=[], return 0 since there are no vertices.");
}
vpar_random_flags(vpar) = vector(#vpar,i,random(2));
{
addhelp(vpar_random_flags,
"flags = vpar_random_flags(vpar)
Return a uniformly distributed random vector of flags of vertices in vpar.
This is simply a vector length #vpar with each entry 0 or 1.");
}
vpar_random_set(vpar) = Vec(select(v->random(2), vpar, 1));
{
addhelp(vpar_random_set,
"set = vpar_random_set(vpar)
Return a uniformly distributed random Set() of vertex numbers of vpar.
Each vertex is randomly in the set or not.
For empty vpar=[], there are no vertices and return is always empty set [].");
}
vpar_set_complement(vpar,set) =
{
\\ print("vpar_set_complement() n="#vpar" "set);
my(ret=vector(#vpar-#set, i, i));
if(#set,
my(v=set[1], pos=v-1); \\ ready for pre-increment
for(i=2,#set,
while(v++>1) + u + 5);
\\ print("p-1 = "p-1" pos ",(p-1)*(p-2)/2);
\\ print("u-1 = "u-1);
\\ print("b = "b" "b\6" "b%6);
a[b\6] += 32 >> (b%6))));
concat(vpar_INTERNAL_graph6_size_str(#vpar), Strchr(a));
}
{
addhelp(vpar_to_graph6,
"str = vpar_to_graph6(vpar)
Return a string which is the graph6 coding of vpar.
Graph6 format is per http://cs.anu.edu.au/~bdm/data/formats.txt
Graph6 represents a labelled undirected graph as an NxN upper triangle
adjacency matrix of bits encoded into printable ASCII with 6 bits per
character, so length about (#vpar)^2/12. The string returned is a single
line without final newline so can be written to a file with for example
write(filename,str). The format allows up to 2^36-1 vertices.
vpar can contain cycles. They become cycles in the graph6, except the
format cannot represent self-loops of a vertex to itself. Behaviour is
unspecified if vpar contains any self-loops.
Graph6 has no notion of a root, so vpar is treated as labelled but
unrooted. Any re-rooting gives the same string.");
}
vpar_to_sparse6(vpar) =
{
\\ When emitting sparse6 "set v" for a new to>=v+2, the b[i] bit can be
\\ either 0 or 1. When 1 it means v++ increment, and the x[i]=to is still
\\ >v so set v. The code here follows nauty tools ntos6() and emits
\\ b[i]=1.
\\ "edges" is a vector of pairs [ [to,from], [to,from], ... ]
\\ with to,from 0-based and to>=from
my(edges=vector(vpar_num_edges(vpar)),
pos=0);
for(v=1,#vpar,
my(p=vpar[v]);
if(p, edges[pos++] = [max(v,p)-1, min(v,p)-1])); \\ to,from, 0-based
pos==#edges || error();
edges=vecsort(edges);
\\ print("edges "edges);
\\ "l" is a vector of words [ b+x, b+x, ... ]
\\ where x is k many bits and b is 0 or 1 above those.
\\ The length of l is determined by how many successive "to" are steps
\\ >=+2, starting from v=0 (in 0-based numbering).
my(k=if(#vpar>=2, logint(#vpar-1,2)+1), \\ width bits for 0 to #vpar-1
b=1< if(i==1,1,edges[i-1][1]+1))));
\\ print("l length "#l" k="k" bits b="b);
pos=0;
my(v=0);
for(i=1,#edges,
my(to=edges[i][1], from=edges[i][2]);
\\ print(" to="to" vs v="v", for from="from);
if(to>v+1, l[pos++] = b+to, \\ set v, with b[i]=1
to==v+1, from += b); \\ v increment
l[pos++] = from;
v=to);
\\ print("l "l" pos "pos);
pos==#l || error();
\\ print(l);
\\ "a" is output chars in a Vecsmall.
\\ "p" is integer of "len" many bits waiting to go into a[].
\\ When len>=6 its bits are stored to a[] (high to low of p).
my(w=k+1,
a=vectorsmall((#l*w + 5)\6));
pos=0;
my(b=5, p=0, len=0);
for(i=1,#l,
p = (p << w) + l[i]; len+=w;
while(len>=6,
len-=6;
a[pos++] = bitand(p>>len, 63) + 63);
p = bitand(p, (1<=n not an edge.
\\ When n=2^k and v=n-2 don't have x[i]>v after inc, which would look like
\\ a self-loop. Hence b[i]=0 in that case, since v unincremented will
\\ have then x[i]>v for set, not edge.
if(len,
len = 6-len; \\ pad size
my(add = (1<=w && #vpar==(1<>= 1); \\ don't let pad look like self-loop n-1 to n-1
\\ print("add "add);
a[pos++] = (p<v
my(b = (p-1)*#vpar + v + 5);
a[b\6] += 32 >> (b%6));
if(up, \\ v->p
my(b = (v-1)*#vpar + p + 5);
\\ print(v-1" ",(v-1)*#vpar);
\\ print(p-1);
\\ print(b" "b\6" "b%6);
a[b\6] += 32 >> (b%6))));
concat(concat("&",vpar_INTERNAL_graph6_size_str(#vpar)),
Strchr(a));
}
{
addhelp(vpar_to_digraph6,
"str = vpar_to_digraph6(vpar,down=0)
Return a string which is the digraph6 coding of vpar.
Digraph6 format is per http://cs.anu.edu.au/~bdm/data/formats.txt
Digraph6 represents a directed graph as an NxN adjacency matrix of bits
encoded into printable ASCII with 6 bits per character, so length about
(#vpar)^2/6. The string is a single line. It does not include a final
newline so can be written to a file with say write(filename,str). The
format allows up to 2^36-1 vertices.
The default is to code edges upwards child -> parent. Optional parameter
\"down\" can be 1 to instead downwards parent->child, or can be 2 for edges
both up and down.
vpar can contain cycles. They become cycles in the digraph6, including
self-loops vertex to itself.");
}
\\-----------------------------------------------------------------------------
\\ Cycles
vpar_is_cyclic(vpar) =
{
\\ Search upwards from each vertex i.
\\ seen[v]=i for the vertices traversed.
\\ If reach seen[v]==i already then it is a re-visit so a cycle.
\\ If reach seen[v]= 1.");
}
\\ +-------+
\\ v | always tree underneath cycle
\\ 1-->2-->3
\\ | |
\\ 4 5
vpar_is_cyclic_vertex(vpar,v) =
{
\\ q runs at half speed of p
\\ If get into a cycle then p loops around and catches up to q in at most
\\ the cycle length steps.
\\ Could keep flags of what vertices seen to detect a cycle. But that
\\ would be more memory, and possibly slower if v in small cycle or small
\\ depth so that it is answered quickly by searching.
my(p=v, q=v);
while(p=vpar[p],
if(p==v,return(1));
(p=vpar[p]) || break;
if(p==v,return(1));
if((q=vpar[q]) == p, break));
0;
}
{
addhelp(vpar_is_cyclic_vertex,
"bool = vpar_is_cyclic_vertex(vpar,v)
Return 1 if vertex v is part of a cycle, so that following v upwards through
parents eventually reaches v again. Return 0 if not.");
}
vpar_num_cyclic_vertices(vpar) =
{
\\ seen[v] = 0 when v not yet considered, then seen[v]=key++ successively
\\ incrementing. If following up v=vpar[v] reaches an already seen then
\\ the incremented key less seen[v] is how many were in the cycle leading
\\ back to v. Or if seen[v]start, ret += key - seen[v] + 1);
break))));
ret;
}
{
addhelp(vpar_num_cyclic_vertices,
"num = vpar_num_cyclic_vertices(vpar)
Return the number of vertices which are in cycles in vpar.
This is how many vpar_is_cyclic_vertex().");
}
vpar_cyclic_vertices(vpar) =
{
my(seen=vectorsmall(#vpar),
num=0,
key=0);
for(i=1,#vpar,
if(!seen[i],
my(v=i, start=key);
while(seen[v]=key++; v=vpar[v],
if(seen[v],
if(seen[v]>start,
num += key - seen[v] + 1;
until(seen[v=vpar[v]]<0, seen[v]=-1));
break))));
my(ret=vector(num));
num=0;
for(v=1,#vpar, if(seen[v]<0, ret[num++]=v));
ret;
}
{
addhelp(vpar_cyclic_vertices,
"vec = vpar_cyclic_vertices(vpar)
Return a vector of the vertices which are in cycles in vpar.
This is all the vpar_is_cyclic_vertex() vertices.
vec has the vertices in ascending order, so is a Set().
The length is #vec == vpar_num_cyclic_vertices(vpar).");
}
vpar_component_cycle_lengths(vpar) =
{
\\ seen[v] = -1 when v not yet considered.
\\ Search upwards sets seen[v]=key++ successively incrementing.
\\ If search finds an already seen (!=-1) then the key less seen[v] is how
\\ many were in the cycle leading back to v.
\\ Or if seen[v]=0,
seen[v]=key++;
if(!(v=vpar[v]),
seen[upto++] = 0; \\ this component has a root, cycle 0
next(2)));
if(seen[v]>start,
seen[upto++] = key - seen[v] + 1))); \\ cycle length
\\ Vec(vec,0) returns vec unchanged. Here upto==0 occurs only when no
\\ components, which is no vertices, which is seen==Vecsmall([]) so ok.
Vec(seen,upto);
}
{
addhelp(vpar_component_cycle_lengths,
"vec = vpar_component_cycle_lengths(vpar)
Return the length of the cycle in each connected component of vpar.
Components are in order of their smallest contained vertex, as per
vpar_component_trees() etc.
Each vertex has only one out-edge, so each component has either a cycle or a
root at its top. Component cycle length is vec[c]=0 when a root, or
otherwise vec[c] = number of vertices in the cycle. A self-loop is
vec[c]=1. Total is all cyclic vertices of vpar, vecsum(vec) ==
vpar_num_cyclic_vertices(vpar).
The number of different vec which occur over all vpar of n>=1 vertices is
C(n) = fibonacci(2*n+2) - 1 = 2,7,20,54,143,... (A035508). Each component
top can be p=0 for a root, or some cycle length, and at most n vertices for
these tops. C(n) = if(n==1,2, n+1 + C(n-1) + sum(i=1,n-1,C(n-i))) is one
component n+1 ways, or 1 root or i=1..n-1 cycle and more components.
Working through this gives Fibonacci 2n+2.");
}
\\ GP-DEFINE C(n) = if(n==0,1,n==1,2, 2*C(n-1) + sum(i=2,n-1, C(n-i)) + n+1);
\\ GP-DEFINE C(n) = if(n==1,2, n+1 + C(n-1) + sum(i=1,n-1, C(n-i)));
\\ GP-Test vector(5,n,C(n)) == [2,7,20,54,143]
\\ n=3 i=1 0,1 C(2)=7 = 14 \ first component then more
\\ i=2 2 C(1)=2 = 2 /
\\ 0,1,2,3 = 4 one component only
vpar_is_bipartite(vpar) =
{
\\ For each vertex v, search up from v to find a vertex u already seen, or
\\ a cycle. Vertices seen are set alternately to t=v or t=-v.
\\ If seen[u] == -t is found then that is an odd length cycle up from v.
\\ If seen[u] == t then it's an even cycle which is ok, and go to next v.
\\ Other seen[u] != 0 is somewhere already searched up, so go to next v.
my(seen=vectorsmall(#vpar));
for(v=1,#vpar,
my(u=v, t=v);
while(seen[u]=t; t=-t; u=vpar[u],
if(seen[u],
if(seen[u]==-t, \\ cycle odd length
return(0));
break)));
1;
}
{
addhelp(vpar_is_bipartite,
"bool = vpar_is_bipartite(vpar)
Return 1 if vpar is a bipartite graph, meaning its vertices can be put into
2 classes with edges only between classes, not within. Return 0 if vpar is
not bipartite.
All trees and forests are bipartite. If vpar contains cycles then it is
bipartite when they are all even length. So the return is 1 if vpar has no
odd length cycles, or 0 if one or more odd length cycles.");
}
\\-----------------------------------------------------------------------------
\\ Caterpillars
vpar_make_caterpillar(vec) =
{
if(#vec, concat([0..#vec-1], concat(vector(#vec,v, vector(vec[v],j,v)))),
[]);
}
{
addhelp(vpar_make_caterpillar,
"vpar = vpar_make_caterpillar(vec)
Return a caterpillar comprising a spine of vertices v = 1..#vec, with vec[v]
many leaves attached under each. The leaves are vertex numbers #vec+1
onwards, in order of their parent.");
}
\\ States:
\\ 1 = singleton
\\ 2 = height 2
\\ 3 = end
\\ 4 = middle
\\ 5 = leaf above a middle
\\
vpar_INTERNAL_is_caterpillar_table = {Vecsmall([
2, 3, 3, 5, 0, \\ p=1 row
2, 3, 3, 0, 0, \\ p=2
3, 4, 4, 0, 0, \\ p=3
4, 0, 0, 0, 0, \\ p=4
0, 0, 0, 0, 0 \\ p=5
\\ -------------
\\ v=1 2 3 5 6 as new child under p
])};
\\ MAYBE: flag to allow forest of caterpillars?
\\ MAYBE: flag to restrict to caterpillar rooted at an end of its spine,
\\ which would be accepting states <= 3
\\
vpar_is_caterpillar(vpar) =
{
vpar_INTERNAL_is_state_table(vpar,vpar_INTERNAL_is_caterpillar_table,5,62);
\\ all states accepting:
\\ GP-Test bitneg(0,5)*2 == 62
\\ GP-Test binary(62) == [ 1, 1, 1, 1, 1, 0]
\\ state: 5 4 3 2 1
}
{
addhelp(vpar_is_caterpillar,
"bool = vpar_is_caterpillar(vpar)
Return 1 if vpar is a caterpillar, or return 0 if not.
A caterpillar is a path with zero or more leaves attached anywhere along the
path. Vertex numbering and the root vertex are arbitrary.
An empty vpar==[] is reckoned a caterpillar.
If vpar is a forest then all its component trees must be caterpillars.");
}
\\-----------------------------------------------------------------------------
\\ Arnau Mir, Francesc Rossello, Lucia Rotger, "A New Balance Index for
\\ Phylogenetic Trees", arxiv 1202.1223, 2012.
\\
\\ They conceive the index for phylogenetic binary trees, meaning
\\ leaf-labelled and everywhere 0 or 2 children. They note it applies also
\\ for arbitrary rooted trees and the result is independent of labels.
vpar_total_cophenetic(vpar) =
{
\\ num_childless[v] is the number of childless vertices below v,
\\ or if v is itself childless then -1
\\ total[v] is the total cophenetic index, total depths of common ancestors
\\
\\ For v accumulated under its parent p:
\\ total[v] depths increase by 1 each, which is binomial of num_childless
\\ num_chlidless[v] adds to p, except if p had been childless (-1) then v
\\ replaces count at p
\\ In all cases num_chlidless[v] = -1 becomes +1 by abs.
my(num_childless=vectorsmall(#vpar),
total=vector(#vpar),
seq=vpar_INTERNAL_seq_upwards(vpar),
ret=0, v,p);
for(i=1,#seq,
if((p=vpar[(v=seq[i])]),
total[p] += total[v] + binomial(abs(num_childless[v]),2);
num_childless[p] += max(1,num_childless[v]);
,
ret += total[v]));
ret;
}
{
addhelp(vpar_total_cophenetic,
"num = vpar_total_cophenetic(vpar)
Return the total cophenetic index of vpar (per Mir, Rossello, Rotger).
This is the sum of the depth of the least common ancestor of each pair of
childless vertices within vpar.
vpar can be a forest, in which case the return is the total over all
component trees. Childless vertices in different components have no common
ancestor so such pairs are ignored.");
}
\\-----------------------------------------------------------------------------
\\ PARI/GP
\\ LocalWords: Pari gp abs ceil concat charpoly exp forvec forpart fibonacci
\\ LocalWords: matdiagonal matdet matid matrank sumdiv setunion bitand
\\ LocalWords: Vecsmall Vecsmalls vectorsmall setintersect
\\ LocalWords: vecmin vecmax vecsort vecsum vecextract vecextracts Vecrev
\\ LocalWords: primepi numbpart numtoperm stirling min
\\ LocalWords: poldegree polroots Polrev polcoeff pollead subst
\\ Variables etc
\\ LocalWords: vpar vpars num vec vecs deg ecc len poly seq TW var diam
\\ LocalWords: primecode minmax func compnum gf gF gG gS gT prev ith
\\ LocalWords: siblingpos rowpos OBell Laplacian rStirling permorder
\\ LocalWords: TotS gNS pos NS vn v's q's NT NF NB str UnlabelledT
\\ LocalWords: leastchildmonk rootmin hereditaryleastsingle equaldepths
\\ LocalWords: indset indsets indnum indpoly galoisinit supersolvable
\\ LocalWords: dompoly domset domsets domnum isomorphics polycyclic
\\ LocalWords: matchpoly matchnum matchnums matchsets equimatchable
\\ LocalWords: indomset indomsets indomnum indompoly
\\ LocalWords: totdomset totdomsets totdomnum totdompoly
\\ LocalWords: semitotdomset semitotdomnum
\\ LocalWords: preorder premax upascend upqueue updown downqueue
\\ LocalWords: setnum orbitnum childful horizpos diagdepths diagqueue
\\ LocalWords: protnum protnums
\\ People
\\ LocalWords: Prufer Prufer's Caminiti Wilf Matula Goebel Ryde Neville's
\\ LocalWords: Hosoya Paulius Micikevicius Saverio Caminiti Narsingh Deo
\\ LocalWords: Fubini Cayley Broder Takacs Callan Orlin Picciotto Chunwei
\\ LocalWords: Kreweras Moszkowski Dinitz Itai Rodeh Tutte Alois
\\ LocalWords: Vladeta Jovovic Beyer Cockayne Hedetniemi Prodinger
\\ LocalWords: Tamas Fleiner Riordan Edmonds Doslic Zubac Rothe
\\ LocalWords: Heuberger Zakir Ekim Deniz Tinaz Odlyzko al Mowshowitz
\\ LocalWords: Aho Hopcroft Pollak Holton's Narayana Brouwer Haemers
\\ Journals etc
\\ LocalWords: Arborescences Egervary Congressus Numerantium DOI pdf OEIS
\\ LocalWords: arxiv TR
\\ Mathematics
\\ LocalWords: unicentral bicentral bicentrals Unicentroidal bicentroidal NxN
\\ LocalWords: reroot rerooting rerootings rerooted unrooted rootings
\\ LocalWords: labellings relabellings parens lexmin coeff coeffs
\\ LocalWords: minimized bool cyclics unicyclics multinomials i'th toplevel
\\ LocalWords: lim ary bi bistar egf Factorization factorizing canonicalizing
\\ LocalWords: automorphism automorphisms characterize characterizing
\\ LocalWords: stabilizing unchosens maximals mins randoms reachables
\\ LocalWords: signless Abelian unduplicated undominated asymptotics
\\ Other
\\ LocalWords: pl ie eg pre ok cf nauty nautyextra gentreeg endmost amortized