\\ 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