(* ========================================================================== *)
(* FLYSPECK - BOOK FORMALIZATION *)
(* *)
(* Lemma: Easy Linear Programs *)
(* Chapter: Tame Hypermap *)
(* Author: Thomas C. Hales *)
(* Date: 2009-06-25 *)
(* ========================================================================== *)
(*
This file treats all but a few "hard" cases (hard_bb).
needs new mktop on platforms that do not support dynamic loading of Str.
ocamlmktop unix.cma nums.cma str.cma -o ocampl
./ocampl
glpk needs to be installed, and glpsol needs to be found in the path.
Execution instructions.
1- Download tame_archive_svn1830.txt from
http://weyl.math.pitt.edu/hanoi2009/Kepler/Kepler.
and save it as /tmp/tame_graph.txt
(* Update: 2011-5-9, This has been added to the project as tame_archive/string_archive.txt *)
2- make body.mod (using Parse_ineq.lpstring) if it doesn't exist already
and place it in the directory glpk/tame_archive/
(* Update: 2012-4-8, This is part of the svn repository now. It doesn't need to be regenerated. *)
3- Load this module and execute the following command.
let (tame_bb,feasible_bb,hard_bb,easy_bb,remaining_easy_bb) = Lpproc.execute();;
*)
#load "str.cma";;
let flyspeck_dir =
(try Sys.getenv "FLYSPECK_DIR" with Not_found -> Sys.getcwd());;
let project_root_dir = (Filename.concat (flyspeck_dir) Filename.parent_dir_name);;
let glpk_dir = Filename.concat project_root_dir "glpk";;
let archive_dir = Filename.concat project_root_dir "graph_generator/output";;
let tame_dir = Filename.concat glpk_dir "tame_archive";;
needs (Filename.concat glpk_dir "glpk_link.ml");;
module Lpproc = struct
open Glpk_link;;
open List;;
(* external files.
The archiveraw file can be downloaded from
http://weyl.math.pitt.edu/hanoi2009/Kepler/Kepler (Archive of Tame Graphs).
body.mod is automatically generated by Parse_ineq.lpstring *)
(* let archiveraw = ref "/tmp/tame_graph.txt";; (* read only *) *)
let archiveraw = ref (Filename.concat archive_dir "string_archive.txt");;
let modelbody = ref (Filename.concat tame_dir "body.mod");;
let model = Filename.concat tame_dir "graph_all.mod";;
(*
let ampl_datafile = Filename.temp_file "ampl_datafile_" ".dat";; (* only used for debugging purposes *)
*)
let glpk_outfile = Filename.temp_file "glpk_outfile_" ".out";;
let make_model() =
(Sys.chdir(tame_dir);
Sys.command("cp head.mod "^model^"; cat "^(!modelbody)^" >> "^
model^"; cat tail.mod >> "^model));;
(* conversion to list. e.g. convert_to_ordered_list pentstring *)
(*
The order in which the faces occur is the order of branch n bound.
The order affects the effectiveness of the branch and bound.
My heuristic is to branch on hexagons, then quads, then pents, then tris.
Within faces of a given size, look for nodes that appear frequently.
*)
let order_list (h,xs) =
let fl = flatten xs in
let count k = length (filter ((=) k) fl) in
let mc rs = maxlist0 (map count rs) in
let sortfn a b = compare (mc b) (mc a) in
let r k = filter (fun x -> length x = k) xs in
let f k = sort sortfn (r k) in
(h,f 6 @ f 4 @ f 5 @ f 3);;
let convert_to_ordered_list s = order_list (convert_to_list s);;
type hint = Triangle_split of int list
| High_low of int
| Edge_split of int list;;
type init = No_data
| File of string*Digest.t
| Hash_tables of (((int,float) Hashtbl.t) * (((int*int),float) Hashtbl.t) * (((int*int),float) Hashtbl.t));;
(* change 4/2012 to make compatible with latest version of GLPK ( >= 4.47).
New versions abort when problem is infeasible, rather than printing value 0.
*)
type lptype = Lp_unset
| Lp_infeasible
| Lp_value of float;;
(* type for holding the parameters for a linear program.
Many of the parameters are not needed for the easy cases that are treated
in this module. They will only be needed for the hard cases (hardid). *)
(*
Fields for the hard cases: hints, diagnostic, node and edge lists
*)
type branchnbound =
{
hypermap_id : string;
mutable lpvalue : lptype;
mutable hints : hint list; (* hints about branching *)
mutable diagnostic : init;
string_rep : string;
(* std_faces is the disjoint union of std_faces_not_super, std56_flat_free, std4_diag3 *)
std_faces_not_super: int list list;
std56_flat_free : int list list;
std4_diag3 : int list list;
std3_big : int list list;
std3_small : int list list;
(* special dart appears first in next ones *)
apex_sup_flat : int list list;
apex_flat : int list list;
apex_A : int list list;
apex5 : int list list;
apex4 : int list list;
(* edge lists *)
d_edge_225_252 : int list list;
d_edge_200_225 : int list list;
(* node lists *)
node_218_252 : int list;
node_236_252 : int list;
node_218_236 : int list;
node_200_218 : int list;
};;
let mk_bb s =
let (h,face1) = convert_to_ordered_list s in
{hypermap_id= h;
lpvalue = Lp_unset;
diagnostic = No_data;
hints = [];
string_rep=s;
std_faces_not_super = face1;
std3_big=[];
std3_small=[];
std4_diag3=[];
std56_flat_free=[];
apex_flat=[];
apex_sup_flat=[];
apex_A=[];
apex4=[];
apex5=[];
d_edge_225_252=[];
d_edge_200_225=[];
node_218_252=[];
node_236_252=[];
node_218_236=[];
node_200_218=[];
};;
let pent_bb = mk_bb pentstring;;
(* conversion to branchnbound. e.g. mk_bb pentstring *)
let modify_bb bb drop1std fields vfields =
let add key xs t = nub ((get_values key xs) @ t) in
let jump_queue key xs vs =
let ys = get_values key xs in
let e = rotation ys in
nub(ys @ (filter (fun t -> not(mem t e)) vs)) in
let std = bb.std_faces_not_super in
let jump_queue_std = jump_queue "jq" fields std in
{
hypermap_id = bb.hypermap_id;
lpvalue = Lp_unset;
diagnostic = No_data;
hints = bb.hints;
string_rep = bb.string_rep;
std_faces_not_super = if drop1std then tl std else jump_queue_std;
std3_big = add "bt" fields bb.std3_big;
std3_small = add "st" fields bb.std3_small;
std56_flat_free = add "flat_free" fields bb.std56_flat_free;
std4_diag3 = add "diag3" fields bb.std4_diag3;
apex_flat = add "ff" fields bb.apex_flat;
apex_sup_flat = add "sf" fields bb.apex_sup_flat;
apex_A = add "af" fields bb.apex_A;
apex4 = add "apex4" fields bb.apex4;
apex5 = add "apex5" fields bb.apex5;
d_edge_225_252 = add "e_225_252" fields bb.d_edge_225_252;
d_edge_200_225 = add "e_200_225" fields bb.d_edge_200_225;
node_218_252 = add "218_252" vfields bb.node_218_252;
node_236_252 = add "236_252" vfields bb.node_236_252;
node_218_236 = add "218_236" vfields bb.node_218_236;
node_200_218 = add "200_218" vfields bb.node_200_218;
};;
(*
Example: move [8;1;6;9] from std to std56_flat_free.
modify_bb pent_bb true ["flat_free",[8;1;6;9];"ff",[9;10;11]] ["200_218",8;"218_252",3;"200_218",7];;
pent_bb;;
modify_bb pent_bb false ["jq",[0;3;5;4];"jq",[10;6;4]] [];;
*)
(* functions on branch n bound *)
let std_faces bb = bb.std_faces_not_super @ bb.std56_flat_free @ bb.std4_diag3;;
(* @ bb.std3_big @ bb.std3_small;; *)
let std_tri_prebranch bb = filter
(let r = rotation (bb.std3_big @ bb.std3_small) in
fun t -> not(mem t r) && (length t = 3)) bb.std_faces_not_super;;
(* should sort faces, so that numbering doesn't change so much when branching *)
let faces bb = (std_faces bb) @ bb.apex_sup_flat @ bb.apex_flat @
bb.apex_A @ bb.apex5 @ bb.apex4;;
let triples w =
let r j = nth w (j mod length w) in
let triple i =
[r i; r (i+1); r(i+2)] in
map triple (up (length w));;
let card_node bb =
1+ maxlist0 (flatten (faces bb));;
let card_face bb = length(faces bb);;
let std_face_of_size bb r=
let f = std_faces bb in
let z = enumerate f in
fst(split (filter (function _,y -> length y=r) z));;
let wheretriplemod xs =
let nth = List.nth in
let t3 = (fun t -> [nth t 0;nth t 1;nth t 2]) in
let ys = map t3 (rotation xs) in
fun x ->
try (
whereis (t3 x) ys) mod (length xs)
with Not_found -> failwith "wheretriplemod";;
(* generate ampl data string of branch n bound *)
let ampl_of_bb outs bb =
let fs = faces bb in
let where3 = wheremod fs in
let number = map where3 in
let list_of = unsplit " " string_of_int in
let mk_faces xs = list_of (number xs) in
let e_dart_raw =
map triples fs in
let e_dart =
let edata_row (i,x) = (sprintf "(*,*,*,%d) " i)^(unsplit ", " list_of x) in
unsplit "\n" edata_row (enumerate e_dart_raw) in
let mk_dart xs = sprintf "%d %d" (hd xs) (wheretriplemod fs xs) in
let mk_darts xs = (unsplit ", " mk_dart xs) in
let p = sprintf in
let j = join_lines [
p"param card_node := %d;" (card_node bb) ;
p"param hypermap_id := %s;" bb.hypermap_id ;
p"param card_face := %d;\n" (card_face bb);
p"set std3 := %s;" (list_of (std_face_of_size bb 3)) ;
p"set std4 := %s;" (list_of (std_face_of_size bb 4) );
p"set std5 := %s;" (list_of (std_face_of_size bb 5)) ;
p"set std6 := %s;\n" (list_of (std_face_of_size bb 6));
p"set e_dart := \n%s;\n" (e_dart);
p"set std56_flat_free := %s;" (mk_faces bb.std56_flat_free);
p"set std4_diag3 := %s;" (mk_faces bb.std4_diag3);
p"set apex_sup_flat := %s;" (mk_darts bb.apex_sup_flat);
p"set apex_flat := %s;" (mk_darts bb.apex_flat);
p"set apex_A := %s;" (mk_darts bb.apex_A);
p"set apex5 := %s;" (mk_darts bb.apex5);
p"set apex4 := %s;" (mk_darts bb.apex4);
p"set d_edge_225_252 := %s;" (mk_darts bb.d_edge_225_252);
p"set d_edge_200_225 := %s;" (mk_darts bb.d_edge_200_225);
p"set std3_big := %s;" (mk_faces bb.std3_big);
p"set std3_small := %s;" (mk_faces bb.std3_small);
p"set node_218_252 := %s;" (list_of bb.node_218_252);
p"set node_236_252 := %s;" (list_of bb.node_236_252);
p"set node_218_236 := %s;" (list_of bb.node_218_236);
p"set node_200_218 := %s;" (list_of bb.node_200_218)] in
Printf.fprintf outs "%s" j;;
(* running of branch in glpsol *)
let solve_branch_verbose addhints bb =
let set_some bb r = (* side effects *)
if (length r = 1) then bb.lpvalue <- Lp_value (float_of_string(hd r)) else () in
let (f,inp) = solve_branch_f model glpk_outfile "lnsum" ampl_of_bb bb in
let _ = if (List.length f =0) then (set_some bb inp) else bb.lpvalue <- Lp_infeasible in
let _ = bb.diagnostic <- File (glpk_outfile,Digest.file glpk_outfile) in
let _ = addhints bb in (* hints for control flow *)
let r = match bb.lpvalue with
| Lp_unset -> "not set"
| Lp_infeasible -> "infeasible"
| Lp_value r -> sprintf "%3.3f" r in
let _ = Sys.command(sprintf "echo %s: %s\n" bb.hypermap_id r) in
bb;;
let solve_f f bb = match bb.lpvalue with
| Lp_unset -> solve_branch_verbose f bb
| Lp_value _ -> bb
| Lp_infeasible -> bb;;
let solve bb = solve_f (fun t -> t) bb;;
(* filtering output *)
let is_feas bb =
let feasible r = (r > 11.9999) in (* relax a bit from 12.0 *)
match bb.lpvalue with
| Lp_unset -> failwith "unexpected unset LP"
| Lp_infeasible -> false
| Lp_value r -> feasible r;;
let filter_feas_f f bbs =
filter is_feas (map (solve_f f) bbs);;
let filter_feas bbs = filter_feas_f (fun t->t) bbs;;
(*
branching on face data:
switch_face does all the branching on the leading std face
*)
let split_flatq xs i = (* {y1,y3} is the new diagonal *)
match rotateL i xs with
| y1::y2::y3::ys -> ([y2;y3;y1],rotateR 1 (y1 :: y3 :: ys))
| _ -> failwith "split_flatq match";;
let asplit_pent xs i = match rotateL i xs with
(* y2,y4 darts of flat; y3 is the point of the A, {y1,y3}, {y3,y5} diags *)
| y1::y2::y3::y4::[y5] -> ([y2;y3;y1],[y3;y5;y1],[y4;y5;y3])
| _ -> failwith "asplit_pent match error";;
let switch3 bb = match std_tri_prebranch bb with
| [] -> failwith ("switch3 empty " ^ bb.hypermap_id)
| fc::_ -> [modify_bb bb false ["bt",fc] [];modify_bb bb false ["st",fc] []];;
let switch4 bb = match bb.std_faces_not_super with
| [] -> failwith ("switch4 empty" ^ bb.hypermap_id)
| fc::_ ->
let mo s (a,b) = modify_bb bb true [s,a;s,b] [] in
let f s i = mo s (split_flatq fc i) in
let g s = modify_bb bb true [s,fc] [] in
[f "ff" 0;f "ff" 1; f "sf" 0; f "sf" 1;g "diag3"];;
let switch5 bb = match bb.std_faces_not_super with
| [] -> failwith ("switch5 empty" ^ bb.hypermap_id)
| fc::_ ->
let mo (a,b) = modify_bb bb true ["ff",a;"apex4",b] [] in
let f i = mo (split_flatq fc i) in
let bbs = map f (up 5) in
let mo (a,b,c) = modify_bb bb true ["ff",a;"af",b;"ff",c] [] in
let f i = mo (asplit_pent fc i) in
let ccs = map f (up 5) in
(modify_bb bb true ["flat_free",fc] []) :: bbs @ ccs ;;
let switch6 bb = match bb.std_faces_not_super with
| [] -> failwith ("switch6 empty" ^ bb.hypermap_id)
| fc::_ ->
let mo (a,b) = modify_bb bb true ["ff",a;"apex5",b] [] in
let f i = mo (split_flatq fc i) in
(modify_bb bb true ["flat_free",fc] []) :: (map f (up 6));;
let switch_face bb = match bb.std_faces_not_super with
| [] -> [bb]
| fc::_ ->
let j = length fc in
let fn = (nth [switch3;switch4;switch5;switch6] (j-3)) in
fn bb;;
let echo bbs = Sys.command (sprintf "echo STACK %d %d" (length bbs) (nextc()));;
let onepass bbs =
let branches = flatten (map switch_face bbs) in
let _ = echo bbs in
filter_feas branches;;
let rec allpass count bbs =
let t = maxlist0 (map (fun b -> length (std_tri_prebranch b)) bbs) in
if t = 0 or count <= 0 then bbs else allpass (count - 1) (onepass bbs);;
let hardid =
[
(* {3,3,3,3,3,3} *) "161847242261";
(* {3,3,3,3,3,3} *) "223336279535";
(* {3,3,3,3,3,3} *) "86506100695";
(* one quad {3,3,4} *) "179189825656";
(* two quad {3,4,4} *) "154005963125";
(* {4,4,4} *) "65974205892";
(* added 2010-06-24 *)
"50803004532";"39599353438";"242652038506";
"88089363170";"75641658977";"34970074286";
(* added 2011-05-18 *)
"164470574315"; "100126458338"; "215863975889"
];;
(*
tame_bb is the entire archive.
feasible_bb are those that pass the first elementary linear program.
hard_bb are those known to be difficult, and easy_bb is the complement.
remaining_easy_bb should be empty; the easy ones should be entirely treated.
*)
(* execute() takes about 2.5 hours to run.
Tested on 2010-06-24, svn 1847.
with graph archive file tame_archive_svn1830.txt (June 17, 2010)
It does not work on older versions of the archive because of differences
in the hash code identifiers of graphs.
Retested 2010-07-28,
with graph archive file tame_archive_svn1830.txt (June 17, 2010)
Digest.file "/tmp/tame_graph.txt";;
val it : Digest.t = "X\221\153\0263Z\241\178\188\211S'\244\148f@"
project svn 1909
body.mod Last Changed Rev: 1849
lpproc.ml Last Changed Rev: 1850
Retested 2011-05-09, after deleting inequality 7676202716 from body.mod.
Runs with 24K cases. All still good.
Retested 2011-05-15 on hex cases, using 0.6 instead of 0.7578=tameTableD[6,0].
All still good. (But a change in the tameTable would create more tame graphs,
and this didn't terminate in reasonable time.)
*)
let execute() =
let _ = make_model() in
let _ = resetc() in
let tame = strip_archive (!archiveraw) in
let tame_bb = map mk_bb tame in
let feasible_bb = filter_feas (map solve tame_bb) in
let (hard_bb,easy_bb) = partition (fun t -> (mem t.hypermap_id hardid)) feasible_bb in
let remaining_easy_bb = allpass 20 easy_bb in
(tame_bb,feasible_bb,hard_bb,easy_bb,remaining_easy_bb);;
end;;