source: genesis/nodes/channelga.ml@ 1990

Last change on this file since 1990 was 1990, checked in by lodewijk, 21 years ago

prutsen met channelga.ml

File size: 9.8 KB
Line 
1(**
2 Experimental channel assigning program using an evolutionary algorithm.
3
4 The scoring function is simple:
5 - the score of a combination of two interfaces is 1 if there's two or more
6 channels between them, -1 if there's 1 channel between them, -5 if
7 there's no channels between them (they're on adjacent channels) and -10
8 if they're on the same channel
9 - the score for a node is the sum of the scores of all the combinations of
10 interfaces for that node, minus any node-specific penalties (eg.
11 channels 7, 8 and 9 are unusable and score -1000 on node x), scaled by
12 the number of interfaces to give more weight to larger nodes (the
13 assumption being that larger nodes are more important nodes)
14 - the total score of the network is the sum of the score of the nodes,
15 minus any network-wide penalties (eg. the omni's for node x and node y
16 can see eachother, so they should be apart)
17
18 - install an O'Caml compiler. /usr/ports/lang/ocaml/ in FreeBSD, ocaml in
19 Debian.
20
21 - compile with
22
23 $ ocamlopt -o foo str.cmxa channelga.ml
24
25 - run with
26
27 $ ./foo f
28
29 where f is the result of running prepconf.py on a file with a list of
30 paths to the wleiden.conf's to consider.
31*)
32
33(* a few constants suitable for twiddling *)
34(** How large a population should the system maintain? *)
35let population_size = 100
36(** How long should the system iterate after an improvement? *)
37and max_stagnant_iterations = 10000
38(** What is the chance for an ESSID to channel assignment to mutate to a
39 random channel? *)
40and mutation_rate = 0.1;;
41
42(* the type definitions. note that Caml has trouble with mutually recursive
43 data structures. you can define them, you just can't ever instantiate them.
44 this is why the fields in wi are all loose references by way of strings *)
45type wi = {
46 wi_name: string;
47 wi_nodename: string;
48 wi_essid: string;
49};;
50type group = {
51 group_essid: string;
52 mutable group_wis: wi list;
53};;
54type node = {
55 node_name: string;
56 node_wis: wi list;
57};;
58(** A configuration is an assignment of groups, identified by essid, to a
59 channel, plus a score. The code should be careful not to use the score
60 between mutating an re-evaluating. *)
61type configuration = {
62 mutable score: int;
63 conf: (string, int) Hashtbl.t;
64};;
65type 'a maybe = Nothing | Just of 'a;;
66
67(** The global nodes hash, mapping from node name to node struct. *)
68let nodes = Hashtbl.create 4
69(** The global groups hash, mapping from essid to group struct. *)
70let groups = Hashtbl.create 4
71
72(* some convenience functions *)
73
74(** Function composition. *)
75let compose f g = fun x -> f(g(x))
76let ($) = compose
77(** Turn two individual values into a tuple *)
78let maketuple a b = (a, b)
79(** Shorthand for List.hd *)
80let head = List.hd
81(** Shorthand for List.tail *)
82let tail = List.tl
83let just x = match x with
84 Nothing -> assert false
85 | Just s -> s
86(** Given a hashtable, return all the keys as a list *)
87let keys t = Hashtbl.fold (fun k d a -> k::a) t []
88(** Given a hashtable, return all the values as a list *)
89let values t = Hashtbl.fold (fun k d a -> d::a) t []
90(** Copy one array into the other *)
91let copyarray src dest = Array.blit src 0 dest 0 (Array.length src)
92
93(** Given a list, return a list of pairs with all possible combinations of
94 items from the given list *)
95let rec combinations l =
96 match l with
97 [] -> []
98 | x::xs -> (List.map (maketuple x) xs)@(combinations xs)
99
100(** Given a configuration and two wi's, return the score *)
101let wi_score c wi1 wi2 =
102 let scoretable = [ ((<=) 2, 1);
103 ((==) 2, -1);
104 ((==) 1, -5);
105 ((==) 0, -10) ] in
106 let channel1 = c wi1.wi_essid in
107 let channel2 = c wi2.wi_essid in
108 let diff = abs (channel1 - channel2) in
109 let rec runtable t = match t with
110 [] -> assert false
111 | (cond, s)::xs -> if (cond diff) then s
112 else runtable xs in
113 runtable scoretable;;
114
115(** Given a configuration and a node, return the score. this is simply the sum of
116 the scores of all the combinations of interfaces, written down as a fold for
117 efficiency *)
118let node_score c n =
119 let foldfunc acc (wi1, wi2) = acc + (wi_score c wi1 wi2) in
120 let base_score = List.fold_left foldfunc 0 (combinations n.node_wis) in
121 base_score * (List.length n.node_wis)
122
123(** Given a list of nodes and a configuration, return the score for the whole
124 configuration. This is the sum of the scores for all nodes. *)
125let score_configuration ns c =
126 let foldfunc acc n = acc + (node_score (Hashtbl.find c) n) in
127 let nodescores = List.fold_left foldfunc 0 ns in
128 nodescores
129
130(** Given a filename, return a list of all the lines in the file with the given
131 filename *)
132let snarf_lines fname =
133 let infile = open_in fname in
134 let result = ref [] in
135 try
136 while true do
137 result := (input_line infile)::!result
138 done;
139 !result (* never gets here *)
140 with End_of_file -> !result
141
142(** Given the name of the node currently being parsed, parse the given tuple
143 that consists of a wi name and an essid. *)
144let parse_pair nodename (wname, essid) =
145 let new_wi = { wi_name = wname; wi_nodename = nodename; wi_essid = essid} in
146 let foo = try
147 let group = Hashtbl.find groups essid in
148 group.group_wis <- new_wi::group.group_wis;
149 with Not_found ->
150 let group = { group_essid = essid; group_wis = [ new_wi ] } in
151 Hashtbl.add groups essid group in
152 new_wi
153
154let parse_fields fields =
155 let nodename = head fields in
156 let rec makepairs l = match l with
157 [] -> []
158 | x::[] -> assert false
159 | a::b::xs -> (a, b)::(makepairs xs) in
160 let wis = List.map (parse_pair nodename) (makepairs (tail fields)) in
161 let sorted_wis = List.sort compare wis in
162 let node = { node_name = nodename; node_wis = sorted_wis } in
163 Hashtbl.add nodes nodename node
164
165let parse_file fname =
166 let spacere = Str.regexp " " in
167 List.iter (parse_fields $ (Str.split spacere)) (snarf_lines fname)
168 ;;
169
170(** Return a random configuration. For some reason, if this function accesses
171 the global 'groups' hash instead of getting it passed in from above, that
172 hash is empty. *)
173let random_configuration groups evaluate =
174 let h = Hashtbl.create 30 in
175 Hashtbl.iter (fun k _ -> Hashtbl.add h k (1 + (Random.int 12))) groups;
176 { score = (evaluate h); conf = h }
177
178let print_conf conf =
179 let print_wi wi = wi.wi_name ^ ": " ^ (string_of_int (Hashtbl.find conf wi.wi_essid)) in
180 let wis node = List.fold_left (fun acc wi -> acc ^ " " ^ (print_wi wi)) "" node.node_wis in
181 let sorted_nodes = List.sort (fun a b -> compare (a.node_name) (b.node_name)) (values nodes) in
182 List.iter (fun n -> print_string (n.node_name ^ ":" ^ (wis n) ^ "\n")) sorted_nodes
183
184let parse_nodeclusters fname =
185 let spacere = Str.regexp " " in
186 (* handle a row of fields. the first field is the supernode name, the
187 rest are the subnode names. create a new node for the supernode,
188 stuff all the wi's of the subnodes under it, names prefixed with
189 their original node's names for clarity, and remove the subnodes
190 from the nodes hashtable *)
191 let do_fields f = let nodename = head f in
192 let subnodenames = tail f in
193 let subnodes = List.map (Hashtbl.find nodes) subnodenames in
194 List.iter (Hashtbl.remove nodes) subnodenames;
195 let prefixed_wis n = List.map (fun w -> { w with wi_name = n.node_name ^ "." ^ w.wi_name}) n.node_wis in
196 let wis = List.fold_left (fun a s -> a@(prefixed_wis s)) [] subnodes in
197 let node = { node_name = nodename; node_wis = wis } in
198 Hashtbl.add nodes nodename node in
199 List.iter (do_fields $ (Str.split spacere)) (snarf_lines fname);;
200
201(** Generalized evolutionary algorithm driver.
202 initialize: () -> configuration array
203 recombine:
204 mutate: configuration array -> configuration array
205 evaluate: configuration array -> configuration array
206 select: configuration array -> configuration array
207
208 and the result is the best configuration *)
209let evolutionary_algorithm initialize recombine mutate evaluate select =
210 let population = (evaluate $ initialize) () in
211 let last_high_score = ref population.(0).score in
212 let iterations_since_new_high_score = ref 0 in
213 let generation = ref 0 in
214 let all_nodes = values nodes in
215 let _ = while !iterations_since_new_high_score < max_stagnant_iterations do
216 let newpop = (recombine $ mutate $ evaluate $ select) population in
217 Array.sort (fun a b -> compare b.score a.score) newpop;
218 copyarray newpop population;
219 let high_score = population.(0).score in
220 if high_score > !last_high_score then begin
221 last_high_score := high_score;
222 iterations_since_new_high_score := 0
223 end;
224 assert (!last_high_score >= high_score);
225 if (!generation mod 10) == 0 then begin
226 print_int !generation;
227 print_string ": ";
228 print_int !last_high_score;
229 print_newline();
230 end;
231 incr iterations_since_new_high_score;
232 incr generation
233 done in
234 population.(0);;
235
236let main =
237 parse_file Sys.argv.(1);
238 parse_nodeclusters "coupled.conf";
239 Random.self_init();
240 let all_nodes = values nodes in
241 let evaluate_hash = score_configuration all_nodes in
242 let initialize () = Array.init population_size (fun _ -> random_configuration groups evaluate_hash) in
243 let recombine x = x in
244 let mutate_conf conf =
245 Hashtbl.iter (fun essid _ -> let f = Random.float 1.0 in
246 let group = Hashtbl.find groups essid in
247 let maxchannel = if (List.length group.group_wis) == 1 then 11
248 else 13 in
249 if (f < mutation_rate) then
250 Hashtbl.replace conf essid (1 + (Random.int maxchannel))) conf in
251 let mutate population = let mutants = Array.map (fun c -> let hash = Hashtbl.copy c.conf in
252 mutate_conf hash;
253 { score = evaluate_hash hash;
254 conf = hash}) population in
255 Array.append population mutants in
256 let evaluate population = Array.iter (fun c -> c.score <- evaluate_hash c.conf) population;
257 population in
258 let select p = Array.sub p 0 ((Array.length p) / 2) in
259 let best = evolutionary_algorithm initialize recombine mutate evaluate select in
260 print_conf best.conf;;
261
262main
Note: See TracBrowser for help on using the repository browser.