added manual symmetry breaking for datatypes
authorblanchet
Sun, 01 Aug 2010 15:51:25 +0200
changeset 38126 8031d099379a
parent 38125 b178a63df952
child 38127 9f9f696fc4e8
added manual symmetry breaking for datatypes
src/HOL/Tools/Nitpick/kodkod.ML
src/HOL/Tools/Nitpick/nitpick.ML
src/HOL/Tools/Nitpick/nitpick_kodkod.ML
src/HOL/Tools/Nitpick/nitpick_model.ML
src/HOL/Tools/Nitpick/nitpick_nut.ML
src/HOL/Tools/Nitpick/nitpick_peephole.ML
src/HOL/Tools/Nitpick/nitpick_scope.ML
--- a/src/HOL/Tools/Nitpick/kodkod.ML	Sat Jul 31 22:02:54 2010 +0200
+++ b/src/HOL/Tools/Nitpick/kodkod.ML	Sun Aug 01 15:51:25 2010 +0200
@@ -47,7 +47,7 @@
     Acyclic of n_ary_index |
     Function of n_ary_index * rel_expr * rel_expr |
     Functional of n_ary_index * rel_expr * rel_expr |
-    TotalOrdering of n_ary_index * n_ary_index * n_ary_index * n_ary_index |
+    TotalOrdering of n_ary_index * rel_expr * rel_expr * rel_expr |
     Subset of rel_expr * rel_expr |
     RelEq of rel_expr * rel_expr |
     IntEq of int_expr * int_expr |
@@ -216,7 +216,7 @@
   Acyclic of n_ary_index |
   Function of n_ary_index * rel_expr * rel_expr |
   Functional of n_ary_index * rel_expr * rel_expr |
-  TotalOrdering of n_ary_index * n_ary_index * n_ary_index * n_ary_index |
+  TotalOrdering of n_ary_index * rel_expr * rel_expr * rel_expr |
   Subset of rel_expr * rel_expr |
   RelEq of rel_expr * rel_expr |
   IntEq of int_expr * int_expr |
@@ -316,7 +316,15 @@
    rel_expr_func: rel_expr -> 'a -> 'a,
    int_expr_func: int_expr -> 'a -> 'a}
 
-(** Auxiliary functions on ML representation of Kodkod problems **)
+fun is_new_kodkodi_version () =
+  case getenv "KODKODI_VERSION" of
+    "" => false
+  | s => dict_ord int_ord (s |> space_explode "."
+                             |> map (the o Int.fromString),
+                           [1, 2, 13]) = GREATER
+         handle Option.Option => false
+
+(** Auxiliary functions on Kodkod problems **)
 
 fun fold_formula (F : 'a fold_expr_funcs) formula =
   case formula of
@@ -335,9 +343,9 @@
     fold_rel_expr F (Rel x) #> fold_rel_expr F r1 #> fold_rel_expr F r2
   | Functional (x, r1, r2) =>
     fold_rel_expr F (Rel x) #> fold_rel_expr F r1 #> fold_rel_expr F r2
-  | TotalOrdering (x1, x2, x3, x4) =>
-    fold_rel_expr F (Rel x1) #> fold_rel_expr F (Rel x2)
-    #> fold_rel_expr F (Rel x3) #> fold_rel_expr F (Rel x4)
+  | TotalOrdering (x, r1, r2, r3) =>
+    fold_rel_expr F (Rel x) #> fold_rel_expr F r1 #> fold_rel_expr F r2
+    #> fold_rel_expr F r3
   | Subset (r1, r2) => fold_rel_expr F r1 #> fold_rel_expr F r2
   | RelEq (r1, r2) => fold_rel_expr F r1 #> fold_rel_expr F r2
   | IntEq (i1, i2) => fold_int_expr F i1 #> fold_int_expr F i2
@@ -518,7 +526,10 @@
 fun int_reg_name j = "$i" ^ base_name j
 
 fun tuple_name x = n_ary_name x "A" "P" "T"
-fun rel_name x = n_ary_name x "s" "r" "m"
+fun rel_name new_kodkodi (n, j) =
+  n_ary_name (n, if new_kodkodi then j
+                 else if j >= 0 then 2 * j
+                 else 2 * ~j - 1) "s" "r" "m"
 fun var_name x = n_ary_name x "S" "R" "M"
 fun tuple_reg_name x = n_ary_name x "$A" "$P" "$T"
 fun tuple_set_reg_name x = n_ary_name x "$a" "$p" "$t"
@@ -530,7 +541,8 @@
 fun block_comment "" = ""
   | block_comment comment = prefix_lines "// " comment ^ "\n"
 
-fun commented_rel_name (x, s) = rel_name x ^ inline_comment s
+fun commented_rel_name new_kodkodi (x, s) =
+  rel_name new_kodkodi x ^ inline_comment s
 
 fun string_for_tuple (Tuple js) = "[" ^ commas (map atom_name js) ^ "]"
   | string_for_tuple (TupleIndex x) = tuple_name x
@@ -581,8 +593,8 @@
   | string_for_tuple_assign (AssignTupleSet (x, ts)) =
     tuple_set_reg_name x ^ " := " ^ string_for_tuple_set ts ^ "\n"
 
-fun string_for_bound (zs, tss) =
-  "bounds " ^ commas (map commented_rel_name zs) ^ ": " ^
+fun string_for_bound new_kodkodi (zs, tss) =
+  "bounds " ^ commas (map (commented_rel_name new_kodkodi) zs) ^ ": " ^
   (if length tss = 1 then "" else "[") ^ commas (map string_for_tuple_set tss) ^
   (if length tss = 1 then "" else "]") ^ "\n"
 
@@ -685,6 +697,8 @@
 
 fun write_problem_file out problems =
   let
+    val new_kodkodi = is_new_kodkodi_version ()
+    val rel_name = rel_name new_kodkodi
     fun out_outmost_f (And (f1, f2)) =
         (out_outmost_f f1; out "\n   && "; out_outmost_f f2)
       | out_outmost_f f = out_f f prec_And
@@ -715,9 +729,9 @@
          | Functional (x, r1, r2) =>
            (out ("FUNCTION(" ^ rel_name x ^ ", "); out_r r1 0; out " -> lone ";
             out_r r2 0; out ")")
-         | TotalOrdering (x1, x2, x3, x4) =>
-           out ("TOTAL_ORDERING(" ^ rel_name x1 ^ ", " ^ rel_name x2 ^ ", "
-                ^ rel_name x3 ^ ", " ^ rel_name x4 ^ ")")
+         | TotalOrdering (x, r1, r2, r3) =>
+           (out ("TOTAL_ORDERING(" ^ rel_name x ^ ", "); out_r r1 0; out ", ";
+            out_r r2 0; out ", "; out_r r3 0; out ")")
          | Subset (r1, r2) => (out_r r1 prec; out " in "; out_r r2 prec)
          | RelEq (r1, r2) => (out_r r1 prec; out " = "; out_r r2 prec)
          | IntEq (i1, i2) => (out_i i1 prec; out " = "; out_i i2 prec)
@@ -837,7 +851,7 @@
                             settings) ^
               "univ: " ^ atom_seq_name (univ_card, 0) ^ "\n" ^
               implode (map string_for_tuple_assign tuple_assigns) ^
-              implode (map string_for_bound bounds) ^
+              implode (map (string_for_bound new_kodkodi) bounds) ^
               (if int_bounds = [] then
                  ""
                else
@@ -871,22 +885,29 @@
 fun scan_list scan = scan_non_empty_list scan || Scan.succeed []
 val scan_nat = Scan.repeat1 (Scan.one Symbol.is_ascii_digit)
                >> (the o Int.fromString o space_implode "")
-val scan_rel_name = $$ "s" |-- scan_nat >> pair 1
-                    || $$ "r" |-- scan_nat >> pair 2
-                    || ($$ "m" |-- scan_nat --| $$ "_") -- scan_nat
+fun scan_rel_name new_kodkodi =
+  ($$ "s" |-- scan_nat >> pair 1
+   || $$ "r" |-- scan_nat >> pair 2
+   || ($$ "m" |-- scan_nat --| $$ "_") -- scan_nat) -- Scan.option ($$ "'")
+  >> (fn ((n, j), SOME _) => (n, ~j - 1)
+       | ((n, j), NONE) => (n, if new_kodkodi then j
+                               else if j mod 2 = 0 then j div 2
+                               else ~(j - 1) div 2))
 val scan_atom = $$ "A" |-- scan_nat
 val scan_tuple = $$ "[" |-- scan_list scan_atom --| $$ "]"
 val scan_tuple_set = $$ "[" |-- scan_list scan_tuple --| $$ "]"
-val scan_assignment = (scan_rel_name --| $$ "=") -- scan_tuple_set
-val scan_instance = Scan.this_string "relations:" |--
-                    $$ "{" |-- scan_list scan_assignment --| $$ "}"
+fun scan_assignment new_kodkodi =
+  (scan_rel_name new_kodkodi --| $$ "=") -- scan_tuple_set
+fun scan_instance new_kodkodi =
+  Scan.this_string "relations:"
+  |-- $$ "{" |-- scan_list (scan_assignment new_kodkodi) --| $$ "}"
 
-val parse_instance =
+fun parse_instance new_kodkodi =
   fst o Scan.finite Symbol.stopper
             (Scan.error (!! (fn _ =>
                                 raise SYNTAX ("Kodkod.parse_instance",
                                               "ill-formed Kodkodi output"))
-                            scan_instance))
+                            (scan_instance new_kodkodi)))
   o strip_blanks o explode
 
 val problem_marker = "*** PROBLEM "
@@ -897,15 +918,15 @@
   Substring.string o fst o Substring.position "\n\n"
   o Substring.triml (size marker)
 
-fun read_next_instance s =
+fun read_next_instance new_kodkodi s =
   let val s = Substring.position instance_marker s |> snd in
     if Substring.isEmpty s then
       raise SYNTAX ("Kodkod.read_next_instance", "expected \"INSTANCE\" marker")
     else
-      read_section_body instance_marker s |> parse_instance
+      read_section_body instance_marker s |> parse_instance new_kodkodi
   end
 
-fun read_next_outcomes j (s, ps, js) =
+fun read_next_outcomes new_kodkodi j (s, ps, js) =
   let val (s1, s2) = Substring.position outcome_marker s in
     if Substring.isEmpty s2 orelse
        not (Substring.isEmpty (Substring.position problem_marker s1
@@ -917,16 +938,17 @@
         val s = Substring.triml (size outcome_marker) s2
       in
         if String.isSuffix "UNSATISFIABLE" outcome then
-          read_next_outcomes j (s, ps, j :: js)
+          read_next_outcomes new_kodkodi j (s, ps, j :: js)
         else if String.isSuffix "SATISFIABLE" outcome then
-          read_next_outcomes j (s, (j, read_next_instance s2) :: ps, js)
+          read_next_outcomes new_kodkodi j
+                           (s, (j, read_next_instance new_kodkodi s2) :: ps, js)
         else
           raise SYNTAX ("Kodkod.read_next_outcomes",
                         "unknown outcome " ^ quote outcome)
       end
   end
 
-fun read_next_problems (s, ps, js) =
+fun read_next_problems new_kodkodi (s, ps, js) =
   let val s = Substring.position problem_marker s |> snd in
     if Substring.isEmpty s then
       (ps, js)
@@ -936,14 +958,18 @@
         val j_plus_1 = s |> Substring.takel (not_equal #" ") |> Substring.string
                          |> Int.fromString |> the
         val j = j_plus_1 - 1
-      in read_next_problems (read_next_outcomes j (s, ps, js)) end
+      in
+        read_next_problems new_kodkodi
+                           (read_next_outcomes new_kodkodi j (s, ps, js))
+      end
   end
   handle Option.Option => raise SYNTAX ("Kodkod.read_next_problems",
                                         "expected number after \"PROBLEM\"")
 
-fun read_output_file path =
-  (false, read_next_problems (Substring.full (File.read path), [], [])
-          |>> rev ||> rev)
+fun read_output_file new_kodkodi path =
+  (false,
+   read_next_problems new_kodkodi (Substring.full (File.read path), [], [])
+   |>> rev ||> rev)
   handle IO.Io _ => (true, ([], [])) | OS.SysErr _ => (true, ([], []))
 
 (** Main Kodkod entry point **)
@@ -973,6 +999,7 @@
 fun uncached_solve_any_problem overlord deadline max_threads max_solutions
                                problems =
   let
+    val new_kodkodi = is_new_kodkodi_version ()
     val j = find_index (curry (op =) True o #formula) problems
     val indexed_problems = if j >= 0 then
                              [(j, nth problems j)]
@@ -1026,7 +1053,7 @@
                       " > " ^ File.shell_path out_path ^
                       " 2> " ^ File.shell_path err_path)
               val (io_error, (ps, nontriv_js)) =
-                read_output_file out_path
+                read_output_file new_kodkodi out_path
                 ||> apfst (map (apfst reindex)) ||> apsnd (map reindex)
               val js = triv_js @ nontriv_js
               val first_error =
@@ -1063,7 +1090,8 @@
         handle Exn.Interrupt =>
                let
                  val nontriv_js =
-                   read_output_file out_path |> snd |> snd |> map reindex
+                   read_output_file new_kodkodi out_path
+                   |> snd |> snd |> map reindex
                in
                  (remove_temporary_files ();
                   Interrupted (SOME (triv_js @ nontriv_js)))
--- a/src/HOL/Tools/Nitpick/nitpick.ML	Sat Jul 31 22:02:54 2010 +0200
+++ b/src/HOL/Tools/Nitpick/nitpick.ML	Sun Aug 01 15:51:25 2010 +0200
@@ -507,19 +507,22 @@
         val plain_axioms = map (declarative_axiom_for_plain_rel kk) plain_rels
         val sel_bounds = map (bound_for_sel_rel ctxt debug datatypes) sel_rels
         val dtype_axioms =
-          declarative_axioms_for_datatypes hol_ctxt binarize bits ofs kk
-                                           rel_table datatypes
+          declarative_axioms_for_datatypes hol_ctxt binarize datatype_sym_break
+                                           bits ofs kk rel_table datatypes
         val declarative_axioms = plain_axioms @ dtype_axioms
         val univ_card = Int.max (univ_card nat_card int_card main_j0
                                      (plain_bounds @ sel_bounds) formula,
                                  main_j0 |> bits > 0 ? Integer.add (bits + 1))
-        val built_in_bounds = bounds_for_built_in_rels_in_formula debug ofs
-                                  univ_card nat_card int_card main_j0 formula
+        val (built_in_bounds, built_in_axioms) =
+          bounds_and_axioms_for_built_in_rels_in_formulas debug ofs
+              univ_card nat_card int_card main_j0
+              (formula :: declarative_axioms)
         val bounds = built_in_bounds @ plain_bounds @ sel_bounds
                      |> not debug ? merge_bounds
+        val axioms = built_in_axioms @ declarative_axioms
         val highest_arity =
           fold Integer.max (map (fst o fst) (maps fst bounds)) 0
-        val formula = fold_rev s_and declarative_axioms formula
+        val formula = fold_rev s_and axioms formula
         val _ = if bits = 0 then () else check_bits bits formula
         val _ = if formula = KK.False then ()
                 else check_arity univ_card highest_arity
--- a/src/HOL/Tools/Nitpick/nitpick_kodkod.ML	Sat Jul 31 22:02:54 2010 +0200
+++ b/src/HOL/Tools/Nitpick/nitpick_kodkod.ML	Sun Aug 01 15:51:25 2010 +0200
@@ -8,12 +8,9 @@
 signature NITPICK_KODKOD =
 sig
   type hol_context = Nitpick_HOL.hol_context
-  type dtype_spec = Nitpick_Scope.dtype_spec
+  type datatype_spec = Nitpick_Scope.datatype_spec
   type kodkod_constrs = Nitpick_Peephole.kodkod_constrs
   type nut = Nitpick_Nut.nut
-  type nfa_transition = Kodkod.rel_expr * typ
-  type nfa_entry = typ * nfa_transition list
-  type nfa_table = nfa_entry list
 
   structure NameTable : TABLE
 
@@ -25,17 +22,17 @@
   val tuple_set_from_atom_schema : (int * int) list -> Kodkod.tuple_set
   val sequential_int_bounds : int -> Kodkod.int_bound list
   val pow_of_two_int_bounds : int -> int -> Kodkod.int_bound list
-  val bounds_for_built_in_rels_in_formula :
-    bool -> int Typtab.table -> int -> int -> int -> int -> Kodkod.formula
-    -> Kodkod.bound list
+  val bounds_and_axioms_for_built_in_rels_in_formulas :
+    bool -> int Typtab.table -> int -> int -> int -> int -> Kodkod.formula list
+    -> Kodkod.bound list * Kodkod.formula list
   val bound_for_plain_rel : Proof.context -> bool -> nut -> Kodkod.bound
   val bound_for_sel_rel :
-    Proof.context -> bool -> dtype_spec list -> nut -> Kodkod.bound
+    Proof.context -> bool -> datatype_spec list -> nut -> Kodkod.bound
   val merge_bounds : Kodkod.bound list -> Kodkod.bound list
   val declarative_axiom_for_plain_rel : kodkod_constrs -> nut -> Kodkod.formula
   val declarative_axioms_for_datatypes :
-    hol_context -> bool -> int -> int Typtab.table -> kodkod_constrs
-    -> nut NameTable.table -> dtype_spec list -> Kodkod.formula list
+    hol_context -> bool -> int -> int -> int Typtab.table -> kodkod_constrs
+    -> nut NameTable.table -> datatype_spec list -> Kodkod.formula list
   val kodkod_formula_from_nut :
     int Typtab.table -> kodkod_constrs -> nut -> Kodkod.formula
 end;
@@ -52,11 +49,13 @@
 
 structure KK = Kodkod
 
-type nfa_transition = KK.rel_expr * typ
-type nfa_entry = typ * nfa_transition list
-type nfa_table = nfa_entry list
+structure NfaGraph = Typ_Graph
+
+fun pull x xs = x :: filter_out (curry (op =) x) xs
 
-structure NfaGraph = Typ_Graph
+fun is_datatype_good_and_old ({co = false, standard = true, deep = true, ...}
+                              : datatype_spec) = true
+  | is_datatype_good_and_old _ = false
 
 fun flip_nums n = index_seq 1 n @ [0] |> map KK.Num
 
@@ -123,19 +122,16 @@
         aux (iter - 1) (2 * pow_of_two) (j + 1)
   in aux (bits + 1) 1 j0 end
 
-fun built_in_rels_in_formula formula =
+fun built_in_rels_in_formulas formulas =
   let
     fun rel_expr_func (KK.Rel (x as (n, j))) =
-        if x = unsigned_bit_word_sel_rel orelse x = signed_bit_word_sel_rel then
-          I
-        else
-          (case AList.lookup (op =) (#rels initial_pool) n of
-             SOME k => j < k ? insert (op =) x
-           | NONE => I)
+        (j < 0 andalso x <> unsigned_bit_word_sel_rel andalso
+         x <> signed_bit_word_sel_rel)
+        ? insert (op =) x
       | rel_expr_func _ = I
     val expr_F = {formula_func = K I, rel_expr_func = rel_expr_func,
                   int_expr_func = K I}
-  in KK.fold_formula expr_F formula [] end
+  in fold (KK.fold_formula expr_F) formulas [] end
 
 val max_table_size = 65536
 
@@ -202,24 +198,14 @@
   else if m = 0 orelse n = 0 then (0, 1)
   else let val p = isa_zgcd (m, n) in (isa_div (m, p), isa_div (n, p)) end
 
-fun tabulate_suc debug ofs univ_card main_j0 =
-  let
-    val j0s = Typtab.fold (insert (op =) o snd) ofs [main_j0] |> sort int_ord
-    val ks = map (op -) (tl (j0s @ [univ_card]) ~~ j0s)
-  in
-    map2 (fn j0 => fn k =>
-             tabulate_func1 debug univ_card (k - 1, j0) (Integer.add 1))
-         j0s ks
-    |> List.concat
-  end
-
 fun tabulate_built_in_rel debug ofs univ_card nat_card int_card j0
                           (x as (n, _)) =
   (check_arity univ_card n;
    if x = not3_rel then
      ("not3", tabulate_func1 debug univ_card (2, j0) (curry (op -) 1))
    else if x = suc_rel then
-     ("suc", tabulate_suc debug ofs univ_card j0)
+     ("suc", tabulate_func1 debug univ_card (univ_card - j0 - 1, j0)
+                            (Integer.add 1))
    else if x = nat_add_rel then
      ("nat_add", tabulate_nat_op2 debug univ_card (nat_card, j0) (op +))
    else if x = int_add_rel then
@@ -253,16 +239,40 @@
    else
      raise ARG ("Nitpick_Kodkod.tabulate_built_in_rel", "unknown relation"))
 
-fun bound_for_built_in_rel debug ofs univ_card nat_card int_card j0 x =
-  let
-    val (nick, ts) = tabulate_built_in_rel debug ofs univ_card nat_card int_card
-                                           j0 x
-  in ([(x, nick)], [KK.TupleSet ts]) end
+fun bound_for_built_in_rel debug ofs univ_card nat_card int_card main_j0
+                           (x as (n, j)) =
+  if n = 2 andalso j <= suc_rels_base then
+    let val (y as (k, j0), tabulate) = atom_seq_for_suc_rel x in
+      ([(x, "suc")],
+       if tabulate then
+         [KK.TupleSet (tabulate_func1 debug univ_card (k - 1, j0)
+                       (Integer.add 1))]
+       else
+         [KK.TupleSet [], tuple_set_from_atom_schema [y, y]])
+    end
+  else
+    let
+      val (nick, ts) = tabulate_built_in_rel debug ofs univ_card nat_card
+                                             int_card main_j0 x
+    in ([(x, nick)], [KK.TupleSet ts]) end
 
-fun bounds_for_built_in_rels_in_formula debug ofs univ_card nat_card int_card
-                                        j0 =
-  map (bound_for_built_in_rel debug ofs univ_card nat_card int_card j0)
-  o built_in_rels_in_formula
+fun axiom_for_built_in_rel (x as (n, j)) =
+  if n = 2 andalso j <= suc_rels_base then
+    let val (y as (k, j0), tabulate) = atom_seq_for_suc_rel x in
+      if tabulate orelse k < 2 then
+        NONE
+      else
+        SOME (KK.TotalOrdering (x, KK.AtomSeq y, KK.Atom j0, KK.Atom (j0 + 1)))
+    end
+  else
+    NONE
+fun bounds_and_axioms_for_built_in_rels_in_formulas debug ofs univ_card nat_card
+                                                    int_card main_j0 formulas =
+  let val rels = built_in_rels_in_formulas formulas in
+    (map (bound_for_built_in_rel debug ofs univ_card nat_card int_card main_j0)
+         rels,
+     map_filter axiom_for_built_in_rel rels)
+  end 
 
 fun bound_comment ctxt debug nick T R =
   short_name nick ^
@@ -297,11 +307,10 @@
            else
              [KK.TupleSet [],
               if T1 = T2 andalso epsilon > delta andalso
-                 (datatype_spec dtypes T1 |> the |> pairf #co #standard)
-                 = (false, true) then
+                 is_datatype_good_and_old (the (datatype_spec dtypes T1)) then
                 index_seq delta (epsilon - delta)
                 |> map (fn j =>
-                           KK.TupleProduct (KK.TupleSet [Kodkod.Tuple [j + j0]],
+                           KK.TupleProduct (KK.TupleSet [KK.Tuple [j + j0]],
                                             KK.TupleAtomSeq (j, j0)))
                 |> foldl1 KK.TupleUnion
               else
@@ -347,8 +356,6 @@
   map2 (fn j => fn x => KK.DeclOne ((1, j), KK.AtomSeq x))
        (index_seq j0 (length schema)) schema
 
-(* The type constraint below is a workaround for a Poly/ML bug. *)
-
 fun d_n_ary_function ({kk_all, kk_join, kk_lone, kk_one, ...} : kodkod_constrs)
                      R r =
   let val body_R = body_rep R in
@@ -683,7 +690,7 @@
 
 fun nfa_transitions_for_sel hol_ctxt binarize
                             ({kk_project, ...} : kodkod_constrs) rel_table
-                            (dtypes : dtype_spec list) constr_x n =
+                            (dtypes : datatype_spec list) constr_x n =
   let
     val x as (_, T) =
       binarized_and_boxed_nth_sel_for_constr hol_ctxt binarize constr_x n
@@ -692,14 +699,14 @@
   in
     map_filter (fn (j, T) =>
                    if forall (not_equal T o #typ) dtypes then NONE
-                   else SOME (kk_project r (map KK.Num [0, j]), T))
+                   else SOME ((x, kk_project r (map KK.Num [0, j])), T))
                (index_seq 1 (arity - 1) ~~ tl type_schema)
   end
 fun nfa_transitions_for_constr hol_ctxt binarize kk rel_table dtypes
                                (x as (_, T)) =
   maps (nfa_transitions_for_sel hol_ctxt binarize kk rel_table dtypes x)
        (index_seq 0 (num_sels_for_constr_type T))
-fun nfa_entry_for_datatype _ _ _ _ _ ({co = true, ...} : dtype_spec) = NONE
+fun nfa_entry_for_datatype _ _ _ _ _ ({co = true, ...} : datatype_spec) = NONE
   | nfa_entry_for_datatype _ _ _ _ _ {standard = false, ...} = NONE
   | nfa_entry_for_datatype _ _ _ _ _ {deep = false, ...} = NONE
   | nfa_entry_for_datatype hol_ctxt binarize kk rel_table dtypes
@@ -711,7 +718,7 @@
 
 fun direct_path_rel_exprs nfa start_T final_T =
   case AList.lookup (op =) nfa final_T of
-    SOME trans => map fst (filter (curry (op =) start_T o snd) trans)
+    SOME trans => map (snd o fst) (filter (curry (op =) start_T o snd) trans)
   | NONE => []
 and any_path_rel_expr ({kk_union, ...} : kodkod_constrs) nfa [] start_T
                       final_T =
@@ -749,14 +756,164 @@
   nfa |> graph_for_nfa |> NfaGraph.strong_conn
       |> map (fn keys => filter (member (op =) keys o fst) nfa)
 
-fun acyclicity_axiom_for_datatype kk nfa start_T =
-  #kk_no kk (#kk_intersect kk
-                 (loop_path_rel_expr kk nfa (map fst nfa) start_T) KK.Iden)
-fun acyclicity_axioms_for_datatypes hol_ctxt binarize kk rel_table dtypes =
-  map_filter (nfa_entry_for_datatype hol_ctxt binarize kk rel_table dtypes)
-             dtypes
-  |> strongly_connected_sub_nfas
-  |> maps (fn nfa => map (acyclicity_axiom_for_datatype kk nfa o fst) nfa)
+fun acyclicity_axiom_for_datatype (kk as {kk_no, kk_intersect, ...}) nfa
+                                  start_T =
+  kk_no (kk_intersect
+             (loop_path_rel_expr kk nfa (pull start_T (map fst nfa)) start_T)
+             KK.Iden)
+fun acyclicity_axioms_for_datatypes kk nfas =
+  maps (fn nfa => map (acyclicity_axiom_for_datatype kk nfa o fst) nfa) nfas
+
+fun all_ge ({kk_join, kk_reflexive_closure, ...} : kodkod_constrs) z r =
+  kk_join r (kk_reflexive_closure (KK.Rel (suc_rel_for_atom_seq z)))
+fun gt ({kk_subset, kk_join, kk_closure, ...} : kodkod_constrs) z r1 r2 =
+  kk_subset r1 (kk_join r2 (kk_closure (KK.Rel (suc_rel_for_atom_seq z))))
+
+fun constr_ord (({const = (s1, _), delta = delta1, epsilon = epsilon1, ...},
+                 {const = (s2, _), delta = delta2, epsilon = epsilon2, ...})
+                : constr_spec * constr_spec) =
+  prod_ord int_ord (prod_ord int_ord string_ord)
+           ((delta1, (epsilon2, s1)), (delta2, (epsilon2, s2)))
+
+fun datatype_ord (({card = card1, self_rec = self_rec1, constrs = constr1, ...},
+                   {card = card2, self_rec = self_rec2, constrs = constr2, ...})
+                  : datatype_spec * datatype_spec) =
+  prod_ord int_ord (prod_ord bool_ord int_ord)
+           ((card1, (self_rec1, length constr1)),
+            (card2, (self_rec2, length constr2)))
+
+(* We must absolutely tabulate "suc" for all datatypes whose selector bounds
+   break cycles; otherwise, we may end up with two incompatible symmetry
+   breaking orders, leading to spurious models. *)
+fun should_tabulate_suc_for_type dtypes T =
+  case datatype_spec dtypes T of
+    SOME {self_rec, ...} => self_rec
+  | NONE => false
+
+fun lex_order_rel_expr (kk as {kk_implies, kk_and, kk_subset, kk_join, ...})
+                       dtypes sel_quadruples =
+  case sel_quadruples of
+    [] => KK.True
+  | ((r, Func (Atom _, Atom x), 2), (_, Type (_, [_, T]))) :: sel_quadruples' =>
+    let val z = (x, should_tabulate_suc_for_type dtypes T) in
+      if null sel_quadruples' then
+        gt kk z (kk_join (KK.Var (1, 1)) r) (kk_join (KK.Var (1, 0)) r)
+      else
+        kk_and (kk_subset (kk_join (KK.Var (1, 1)) r)
+                          (all_ge kk z (kk_join (KK.Var (1, 0)) r)))
+               (kk_implies (kk_subset (kk_join (KK.Var (1, 1)) r)
+                                      (kk_join (KK.Var (1, 0)) r))
+                           (lex_order_rel_expr kk dtypes sel_quadruples'))
+    end
+    (* Skip constructors components that aren't atoms, since we cannot compare
+       these easily. *)
+  | _ :: sel_quadruples' => lex_order_rel_expr kk dtypes sel_quadruples'
+
+fun has_nil_like_constr dtypes T =
+  case #constrs (the (datatype_spec dtypes T))
+       |> filter_out (is_self_recursive_constr_type o snd o #const) of
+    [{const = (_, T'), ...}] => T = T'
+  | _ => false
+
+fun sym_break_axioms_for_constr_pair hol_ctxt binarize
+       (kk as {kk_all, kk_or, kk_iff, kk_implies, kk_and, kk_some, kk_subset,
+               kk_intersect, kk_join, kk_project, ...}) rel_table nfas dtypes
+       (constr1 as {const = const1 as (_, T1), delta = delta1,
+                    epsilon = epsilon1, ...},
+        constr2 as {const = const2 as (_, T2), delta = delta2,
+                    epsilon = epsilon2, ...}) =
+  let
+    val dataT = body_type T1
+    val nfa = nfas |> find_first (exists (curry (op =) dataT o fst)) |> these
+    val rec_Ts = nfa |> map fst
+    val same_constr = (const1 = const2)
+    fun rec_and_nonrec_sels (x as (_, T)) =
+      index_seq 0 (num_sels_for_constr_type T)
+      |> map (binarized_and_boxed_nth_sel_for_constr hol_ctxt binarize x)
+      |> List.partition (member (op =) rec_Ts o range_type o snd)
+    val sel_xs1 = rec_and_nonrec_sels const1 |> op @
+  in
+    if same_constr andalso null sel_xs1 then
+      []
+    else
+      let
+        val z =
+          (case #2 (const_triple rel_table (discr_for_constr const1)) of
+             Func (Atom x, Formula _) => x
+           | R => raise REP ("Nitpick_Kodkod.sym_break_axioms_for_constr_pair",
+                             [R]), should_tabulate_suc_for_type dtypes dataT)
+        val (rec_sel_xs2, nonrec_sel_xs2) = rec_and_nonrec_sels const2
+        val sel_xs2 = rec_sel_xs2 @ nonrec_sel_xs2
+        fun sel_quadruples2 () = sel_xs2 |> map (`(const_triple rel_table))
+        (* If the two constructors are the same, we drop the first selector
+           because that one is always checked by the lexicographic order.
+           We sometimes also filter out direct subterms, because those are
+           already handled by the acyclicity breaking in the bound
+           declarations. *)
+        fun filter_out_sels no_direct sel_xs =
+          apsnd (filter_out
+                     (fn ((x, _), T) =>
+                         (same_constr andalso x = hd sel_xs) orelse
+                         (T = dataT andalso
+                          (no_direct orelse not (member (op =) sel_xs x)))))
+        fun subterms_r no_direct sel_xs j =
+          loop_path_rel_expr kk (map (filter_out_sels no_direct sel_xs) nfa)
+                           (filter_out (curry (op =) dataT) (map fst nfa)) dataT
+          |> kk_join (KK.Var (1, j))
+      in
+        [kk_all [KK.DeclOne ((1, 0), discr_rel_expr rel_table const1),
+                 KK.DeclOne ((1, 1), discr_rel_expr rel_table const2)]
+             ((if same_constr then kk_implies else kk_iff)
+                 (if delta2 >= epsilon1 then KK.True
+                  else gt kk z (KK.Var (1, 1)) (KK.Var (1, 0)))
+                 (kk_or
+                      (if has_nil_like_constr dtypes dataT andalso
+                          T1 = dataT then
+                         KK.True
+                       else
+                         kk_some (kk_intersect (subterms_r false sel_xs2 1)
+                                               (all_ge kk z (KK.Var (1, 0)))))
+                      (if same_constr then
+                         kk_and
+                             (lex_order_rel_expr kk dtypes (sel_quadruples2 ()))
+                             (if length rec_sel_xs2 > 1 then
+                                kk_all [KK.DeclOne ((1, 2),
+                                                    subterms_r true sel_xs1 0)]
+                                       (gt kk z (KK.Var (1, 1)) (KK.Var (1, 2)))
+                              else
+                                KK.True)
+                       else
+                         kk_all [KK.DeclOne ((1, 2),
+                                 subterms_r false sel_xs1 0)]
+                                (gt kk z (KK.Var (1, 1)) (KK.Var (1, 2))))))]
+      end
+  end
+
+fun sym_break_axioms_for_datatype hol_ctxt binarize kk rel_table nfas dtypes
+                                  ({constrs, ...} : datatype_spec) =
+    let val constrs = sort constr_ord constrs in
+      maps (sym_break_axioms_for_constr_pair hol_ctxt binarize kk rel_table nfas
+                                             dtypes)
+           ((constrs ~~ constrs) @ all_distinct_unordered_pairs_of constrs)
+    end
+
+val min_sym_break_card = 7
+
+fun sym_break_axioms_for_datatypes hol_ctxt binarize datatype_sym_break kk
+                                   rel_table nfas dtypes =
+  if datatype_sym_break = 0 then
+    []
+  else
+    maps (sym_break_axioms_for_datatype hol_ctxt binarize kk rel_table nfas
+                                        dtypes)
+         (dtypes |> filter is_datatype_good_and_old
+                 |> filter (fn {constrs = [_], ...} => false
+                             | {card, ...} => card >= min_sym_break_card)
+                 |> (fn dtypes' =>
+                        dtypes'
+                        |> length dtypes' > datatype_sym_break
+                           ? (sort (rev_order o datatype_ord)
+                              #> take datatype_sym_break)))
 
 fun sel_axiom_for_sel hol_ctxt binarize j0
         (kk as {kk_all, kk_formula_if, kk_subset, kk_no, kk_join, ...})
@@ -805,7 +962,7 @@
       end
   end
 fun sel_axioms_for_datatype hol_ctxt binarize bits j0 kk rel_table
-                            ({constrs, ...} : dtype_spec) =
+                            ({constrs, ...} : datatype_spec) =
   maps (sel_axioms_for_constr hol_ctxt binarize bits j0 kk rel_table) constrs
 
 fun uniqueness_axiom_for_constr hol_ctxt binarize
@@ -830,13 +987,13 @@
                   (kk_rel_eq (KK.Var (1, 0)) (KK.Var (1, 1))))
   end
 fun uniqueness_axioms_for_datatype hol_ctxt binarize kk rel_table
-                                   ({constrs, ...} : dtype_spec) =
+                                   ({constrs, ...} : datatype_spec) =
   map (uniqueness_axiom_for_constr hol_ctxt binarize kk rel_table) constrs
 
 fun effective_constr_max ({delta, epsilon, ...} : constr_spec) = epsilon - delta
 fun partition_axioms_for_datatype j0 (kk as {kk_rel_eq, kk_union, ...})
                                   rel_table
-                                  ({card, constrs, ...} : dtype_spec) =
+                                  ({card, constrs, ...} : datatype_spec) =
   if forall #exclusive constrs then
     [Integer.sum (map effective_constr_max constrs) = card |> formula_for_bool]
   else
@@ -854,11 +1011,20 @@
       partition_axioms_for_datatype j0 kk rel_table dtype
     end
 
-fun declarative_axioms_for_datatypes hol_ctxt binarize bits ofs kk rel_table
-                                     dtypes =
-  acyclicity_axioms_for_datatypes hol_ctxt binarize kk rel_table dtypes @
-  maps (other_axioms_for_datatype hol_ctxt binarize bits ofs kk rel_table)
-       dtypes
+fun declarative_axioms_for_datatypes hol_ctxt binarize datatype_sym_break bits
+                                     ofs kk rel_table dtypes =
+  let
+    val nfas =
+      dtypes |> map_filter (nfa_entry_for_datatype hol_ctxt binarize kk
+                                                   rel_table dtypes)
+             |> strongly_connected_sub_nfas
+  in
+    acyclicity_axioms_for_datatypes kk nfas @
+    sym_break_axioms_for_datatypes hol_ctxt binarize datatype_sym_break kk
+                                   rel_table nfas dtypes @
+    maps (other_axioms_for_datatype hol_ctxt binarize bits ofs kk rel_table)
+         dtypes
+  end
 
 fun kodkod_formula_from_nut ofs
         (kk as {kk_all, kk_exist, kk_formula_let, kk_formula_if, kk_or, kk_not,
--- a/src/HOL/Tools/Nitpick/nitpick_model.ML	Sat Jul 31 22:02:54 2010 +0200
+++ b/src/HOL/Tools/Nitpick/nitpick_model.ML	Sun Aug 01 15:51:25 2010 +0200
@@ -902,8 +902,8 @@
     val all_values =
       all_values_of_type pool wacky_names scope atomss sel_names rel_table
                          bounds
-    fun is_codatatype_wellformed (cos : dtype_spec list)
-                                 ({typ, card, ...} : dtype_spec) =
+    fun is_codatatype_wellformed (cos : datatype_spec list)
+                                 ({typ, card, ...} : datatype_spec) =
       let
         val ts = all_values card typ
         val max_depth = Integer.sum (map #card cos)
@@ -935,7 +935,7 @@
             [setmp_show_all_types (Syntax.pretty_term ctxt) t1,
              Pretty.str oper, Syntax.pretty_term ctxt t2])
       end
-    fun pretty_for_datatype ({typ, card, complete, ...} : dtype_spec) =
+    fun pretty_for_datatype ({typ, card, complete, ...} : datatype_spec) =
       Pretty.block (Pretty.breaks
           (Syntax.pretty_typ ctxt (uniterize_unarize_unbox_etc_type typ) ::
            (case typ of
@@ -950,8 +950,8 @@
                   else [Pretty.str (unrep ())]))]))
     fun integer_datatype T =
       [{typ = T, card = card_of_type card_assigns T, co = false,
-        standard = true, complete = (false, false), concrete = (true, true),
-        deep = true, constrs = []}]
+        standard = true, self_rec = true, complete = (false, false),
+        concrete = (true, true), deep = true, constrs = []}]
       handle TYPE ("Nitpick_HOL.card_of_type", _, _) => []
     val (codatatypes, datatypes) =
       datatypes |> filter #deep |> List.partition #co
--- a/src/HOL/Tools/Nitpick/nitpick_nut.ML	Sat Jul 31 22:02:54 2010 +0200
+++ b/src/HOL/Tools/Nitpick/nitpick_nut.ML	Sun Aug 01 15:51:25 2010 +0200
@@ -721,7 +721,7 @@
 fun choose_rep_for_sels_for_constr scope (x as (_, T)) =
   fold_rev (choose_rep_for_nth_sel_for_constr scope x)
            (~1 upto num_sels_for_constr_type T - 1)
-fun choose_rep_for_sels_of_datatype _ ({deep = false, ...} : dtype_spec) = I
+fun choose_rep_for_sels_of_datatype _ ({deep = false, ...} : datatype_spec) = I
   | choose_rep_for_sels_of_datatype scope {constrs, ...} =
     fold_rev (choose_rep_for_sels_for_constr scope o #const) constrs
 fun choose_reps_for_all_sels (scope as {datatypes, ...}) =
--- a/src/HOL/Tools/Nitpick/nitpick_peephole.ML	Sat Jul 31 22:02:54 2010 +0200
+++ b/src/HOL/Tools/Nitpick/nitpick_peephole.ML	Sun Aug 01 15:51:25 2010 +0200
@@ -23,6 +23,7 @@
   val initial_pool : name_pool
   val not3_rel : n_ary_index
   val suc_rel : n_ary_index
+  val suc_rels_base : int
   val unsigned_bit_word_sel_rel : n_ary_index
   val signed_bit_word_sel_rel : n_ary_index
   val nat_add_rel : n_ary_index
@@ -46,6 +47,8 @@
   val int_for_atom : int * int -> int -> int
   val atom_for_int : int * int -> int -> int
   val is_twos_complement_representable : int -> int -> bool
+  val suc_rel_for_atom_seq : (int * int) * bool -> n_ary_index
+  val atom_seq_for_suc_rel : n_ary_index -> (int * int) * bool
   val inline_rel_expr : rel_expr -> bool
   val empty_n_ary_rel : int -> rel_expr
   val num_seq : int -> int -> int_expr list
@@ -99,30 +102,27 @@
    formula_reg: int,
    rel_reg: int}
 
-(* If you add new built-in relations, make sure to increment the counters here
-   as well to avoid name clashes (which fortunately would be detected by
-   Kodkodi). *)
-val initial_pool =
-  {rels = [(2, 10), (3, 20), (4, 10)], vars = [], formula_reg = 10,
-   rel_reg = 10}
+(* FIXME: needed? *)
+val initial_pool = {rels = [], vars = [], formula_reg = 10, rel_reg = 10}
 
-val not3_rel = (2, 0)
-val suc_rel = (2, 1)
-val unsigned_bit_word_sel_rel = (2, 2)
-val signed_bit_word_sel_rel = (2, 3)
-val nat_add_rel = (3, 0)
-val int_add_rel = (3, 1)
-val nat_subtract_rel = (3, 2)
-val int_subtract_rel = (3, 3)
-val nat_multiply_rel = (3, 4)
-val int_multiply_rel = (3, 5)
-val nat_divide_rel = (3, 6)
-val int_divide_rel = (3, 7)
-val nat_less_rel = (3, 8)
-val int_less_rel = (3, 9)
-val gcd_rel = (3, 10)
-val lcm_rel = (3, 11)
-val norm_frac_rel = (4, 0)
+val not3_rel = (2, ~1)
+val unsigned_bit_word_sel_rel = (2, ~2)
+val signed_bit_word_sel_rel = (2, ~3)
+val suc_rel = (2, ~4)
+val suc_rels_base = ~5 (* must be the last of the binary series *)
+val nat_add_rel = (3, ~1)
+val int_add_rel = (3, ~2)
+val nat_subtract_rel = (3, ~3)
+val int_subtract_rel = (3, ~4)
+val nat_multiply_rel = (3, ~5)
+val int_multiply_rel = (3, ~6)
+val nat_divide_rel = (3, ~7)
+val int_divide_rel = (3, ~8)
+val nat_less_rel = (3, ~9)
+val int_less_rel = (3, ~10)
+val gcd_rel = (3, ~11)
+val lcm_rel = (3, ~12)
+val norm_frac_rel = (4, ~1)
 
 fun atom_for_bool j0 = Atom o Integer.add j0 o int_from_bool
 fun formula_for_bool b = if b then True else False
@@ -139,6 +139,23 @@
 fun is_twos_complement_representable bits n =
   let val max = reasonable_power 2 bits in n >= ~ max andalso n < max end
 
+val max_squeeze_card = 49
+
+fun squeeze (m, n) =
+  if n > max_squeeze_card then
+    raise TOO_LARGE ("Nitpick_Peephole.squeeze",
+                     "too large cardinality (" ^ string_of_int n ^ ")")
+  else
+    (max_squeeze_card + 1) * m + n
+fun unsqueeze p = (p div (max_squeeze_card + 1), p mod (max_squeeze_card + 1))
+
+fun boolify (j, b) = 2 * j + (if b then 0 else 1)
+fun unboolify j = (j div 2, j mod 2 = 0)
+
+fun suc_rel_for_atom_seq (x, tabulate) =
+  (2, suc_rels_base - boolify (squeeze x, tabulate))
+fun atom_seq_for_suc_rel (_, j) = unboolify (~ j + suc_rels_base) |>> unsqueeze
+
 fun is_none_product (Product (r1, r2)) =
     is_none_product r1 orelse is_none_product r2
   | is_none_product None = true
@@ -245,16 +262,18 @@
     fun to_nat j = j - main_j0
     val to_int = int_for_atom (int_card, main_j0)
 
+    val exists_empty_decl = exists (fn DeclOne (_, None) => true | _ => false)
+
     fun s_all _ True = True
       | s_all _ False = False
       | s_all [] f = f
-      | s_all ds (All (ds', f)) = All (ds @ ds', f)
-      | s_all ds f = All (ds, f)
+      | s_all ds (All (ds', f)) = s_all (ds @ ds') f
+      | s_all ds f = if exists_empty_decl ds then True else All (ds, f)
     fun s_exist _ True = True
       | s_exist _ False = False
       | s_exist [] f = f
-      | s_exist ds (Exist (ds', f)) = Exist (ds @ ds', f)
-      | s_exist ds f = Exist (ds, f)
+      | s_exist ds (Exist (ds', f)) = s_exist (ds @ ds') f
+      | s_exist ds f = if exists_empty_decl ds then False else Exist (ds, f)
 
     fun s_formula_let _ True = True
       | s_formula_let _ False = False
--- a/src/HOL/Tools/Nitpick/nitpick_scope.ML	Sat Jul 31 22:02:54 2010 +0200
+++ b/src/HOL/Tools/Nitpick/nitpick_scope.ML	Sun Aug 01 15:51:25 2010 +0200
@@ -18,11 +18,12 @@
      explicit_max: int,
      total: bool}
 
-  type dtype_spec =
+  type datatype_spec =
     {typ: typ,
      card: int,
      co: bool,
      standard: bool,
+     self_rec: bool,
      complete: bool * bool,
      concrete: bool * bool,
      deep: bool,
@@ -34,20 +35,21 @@
      card_assigns: (typ * int) list,
      bits: int,
      bisim_depth: int,
-     datatypes: dtype_spec list,
+     datatypes: datatype_spec list,
      ofs: int Typtab.table}
 
-  val datatype_spec : dtype_spec list -> typ -> dtype_spec option
-  val constr_spec : dtype_spec list -> styp -> constr_spec
-  val is_complete_type : dtype_spec list -> bool -> typ -> bool
-  val is_concrete_type : dtype_spec list -> bool -> typ -> bool
-  val is_exact_type : dtype_spec list -> bool -> typ -> bool
+  val datatype_spec : datatype_spec list -> typ -> datatype_spec option
+  val constr_spec : datatype_spec list -> styp -> constr_spec
+  val is_complete_type : datatype_spec list -> bool -> typ -> bool
+  val is_concrete_type : datatype_spec list -> bool -> typ -> bool
+  val is_exact_type : datatype_spec list -> bool -> typ -> bool
   val offset_of_type : int Typtab.table -> typ -> int
   val spec_of_type : scope -> typ -> int * int
   val pretties_for_scope : scope -> bool -> Pretty.T list
   val multiline_string_for_scope : scope -> string
   val scopes_equivalent : scope * scope -> bool
   val scope_less_eq : scope -> scope -> bool
+  val is_self_recursive_constr_type : typ -> bool
   val all_scopes :
     hol_context -> bool -> (typ option * int list) list
     -> (styp option * int list) list -> (styp option * int list) list
@@ -69,11 +71,12 @@
    explicit_max: int,
    total: bool}
 
-type dtype_spec =
+type datatype_spec =
   {typ: typ,
    card: int,
    co: bool,
    standard: bool,
+   self_rec: bool,
    complete: bool * bool,
    concrete: bool * bool,
    deep: bool,
@@ -85,7 +88,7 @@
    card_assigns: (typ * int) list,
    bits: int,
    bisim_depth: int,
-   datatypes: dtype_spec list,
+   datatypes: datatype_spec list,
    ofs: int Typtab.table}
 
 datatype row_kind = Card of typ | Max of styp
@@ -93,11 +96,11 @@
 type row = row_kind * int list
 type block = row list
 
-fun datatype_spec (dtypes : dtype_spec list) T =
+fun datatype_spec (dtypes : datatype_spec list) T =
   List.find (curry (op =) T o #typ) dtypes
 
 fun constr_spec [] x = raise TERM ("Nitpick_Scope.constr_spec", [Const x])
-  | constr_spec ({constrs, ...} :: dtypes : dtype_spec list) (x as (s, T)) =
+  | constr_spec ({constrs, ...} :: dtypes : datatype_spec list) (x as (s, T)) =
     case List.find (curry (op =) (s, body_type T) o (apsnd body_type o #const))
                    constrs of
       SOME c => c
@@ -438,6 +441,7 @@
     val self_recs = map (is_self_recursive_constr_type o snd) xs
     val (num_self_recs, num_non_self_recs) =
       List.partition I self_recs |> pairself length
+    val self_rec = num_self_recs > 0
     fun is_complete facto =
       has_exact_card hol_ctxt facto finitizable_dataTs card_assigns T
     fun is_concrete facto =
@@ -454,8 +458,8 @@
                                 num_non_self_recs)
                (sort (bool_ord o swap o pairself fst) (self_recs ~~ xs)) []
   in
-    {typ = T, card = card, co = co, standard = standard, complete = complete,
-     concrete = concrete, deep = deep, constrs = constrs}
+    {typ = T, card = card, co = co, standard = standard, self_rec = self_rec,
+     complete = complete, concrete = concrete, deep = deep, constrs = constrs}
   end
 
 fun scope_from_descriptor (hol_ctxt as {ctxt, stds, ...}) binarize deep_dataTs