source: genesis/nodes/channelea.ml@ 2591

Last change on this file since 2591 was 1999, checked in by lodewijk, 21 years ago
  • n-point crossover operator toegevoegd
  • die operator in de 'recombine' functie gehangen, geconstateerd dat de oplossingen er niet beter van werden en het geheel wel een stuk trager werd, dus maar weer weggecommentaard.
File size: 14.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 special cases:
19 - on node x, interfaces y and z are well separated and can live on the
20 same channel
21 - the link between x on y and z on w is very important, make sure it is
22 well separated on both ends of the link
23 - interface x on node y will bother interface z on node w and need to
24 be on separate channels
25 - on node x, channels y, z and w are not usable
26 - node x y and z should be treated as one node
27 - if at all possible, do not put accesspoints above channel 11
28
29 - install an O'Caml compiler. /usr/ports/lang/ocaml/ in FreeBSD, ocaml in
30 Debian.
31
32 - compile with
33
34 $ ocamlopt -o foo str.cmxa channelga.ml
35
36 - run with
37
38 $ ./foo f
39
40 where f is the result of running prepconf.py on a file with a list of
41 paths to the wleiden.conf's to consider.
42
43 Lodewijk Voge <lvoge@cs.vu.nl>
44*)
45
46(* a few constants suitable for twiddling *)
47(** How large a population should the system maintain? *)
48let population_size = 20
49(** How long should the system iterate after an improvement? *)
50and max_stagnant_iterations = 2000
51(** What is the chance for an ESSID-to-channel assignment to mutate to a
52 random channel? *)
53and mutation_rate = 0.1
54(** The most basic score table *)
55and scoretable = [ (<=) 2, 1;
56 (==) 2, -30;
57 (==) 1, -70;
58 (==) 0, -100 ]
59
60(* the type definitions. note that Caml has trouble with mutually recursive
61 data structures. you can define them, you just can't ever instantiate them.
62 this is why the fields in wi are all loose references by way of strings *)
63type wi = {
64 wi_name: string;
65 wi_nodename: string;
66 wi_essid: string
67}
68type group = {
69 group_essid: string;
70 mutable group_wis: wi list
71}
72type node = {
73 node_name: string;
74 node_wis: wi list
75}
76(** A configuration is an assignment of groups, identified by essid, to a
77 channel, plus a score. The code should be careful not to use the score
78 between mutating an re-evaluating. *)
79type configuration = {
80 mutable score: int;
81 conf: (string, int) Hashtbl.t
82}
83type 'a maybe = Nothing | Just of 'a
84
85(** The global nodes hash, mapping from node name to node struct. *)
86let nodes = Hashtbl.create 4
87(** The global groups hash, mapping from essid to group struct. *)
88let groups = Hashtbl.create 4
89
90(* Now the hashes for the special cases *)
91(** Hash mapping from nodename to a list of winame's indicating the wi's that
92 don't interfere with eachother for the given node *)
93let nointerference = Hashtbl.create 4
94(** List of (nodename1, winame1, nodename2, winame2) tuples indicating a very
95 important link that should be well-separated on both ends *)
96let importantlinks = ref []
97(** Hash mapping from nodename to a list of unusable channels for that node *)
98let unusable = Hashtbl.create 4
99(** List of (nodename1, winame1, nodename2, winame2) tuples indicating two
100 interfering interfaces on different nodes *)
101let interference = ref []
102
103(** Run the given diff against the given scoretable and return the score *)
104let rec runtable t diff =
105 match t with
106 [] -> assert false
107 | (cond, s)::xs -> if (cond diff) then s
108 else runtable xs diff
109
110(* some convenience functions *)
111
112(** Function composition. *)
113let compose f g = fun x -> f(g(x))
114let ($) = compose
115(** Turn two individual values into a tuple *)
116let maketuple a b = (a, b)
117(** Shorthand for List.hd *)
118let head = List.hd
119(** Shorthand for List.tail *)
120let tail = List.tl
121let even x = (x mod 2) == 0
122(*let shuffle = Array.sort (fun _ _ -> 1 - Random.int(2))*)
123let just x = match x with
124 Nothing -> assert false
125 | Just s -> s
126(** Given a hashtable, return all the keys as a list *)
127let keys t = Hashtbl.fold (fun k d a -> k::a) t []
128(** Given a hashtable, return all the values as a list *)
129let values t = Hashtbl.fold (fun k d a -> d::a) t []
130(** Copy one array into the other *)
131let copyarray src dest = Array.blit src 0 dest 0 (Array.length src)
132
133(** Is the given element in the given list? uses compare, so it works on
134 strings as well *)
135let in_list l e = try
136 let _ = List.find (fun x -> (compare e x) == 0) l in
137 true
138 with Not_found -> false
139
140(** Given a list, return a list of pairs with all possible combinations of
141 items from the given list *)
142let rec combinations l =
143 match l with
144 [] -> []
145 | x::xs -> (List.map (maketuple x) xs)@(combinations xs)
146
147(** Given a configuration and two wi's, return the score *)
148let wi_score c unusable nointerference wi1 wi2 =
149 let channel1 = c wi1.wi_essid in
150 let channel2 = c wi2.wi_essid in
151 let diff = abs (channel1 - channel2) in
152 let is_unusable = in_list unusable in
153 if (is_unusable channel1) || (is_unusable channel2) then -10000
154 else if (in_list nointerference wi1.wi_name) &&
155 (in_list nointerference wi2.wi_name) then 1
156 else runtable scoretable diff
157
158(** Given a configuration and a node, return the score. this is simply the sum
159 of the scores of all the combinations of interfaces, written down as a fold
160 for efficiency *)
161let node_score c n =
162 let nointerference_ = try Hashtbl.find nointerference n.node_name
163 with Not_found -> [] in
164 let unusable_ = try Hashtbl.find unusable n.node_name
165 with Not_found -> [] in
166 let f a (wi1, wi2) = a + (wi_score c unusable_ nointerference_ wi1 wi2) in
167 let base_score = List.fold_left f 0 (combinations n.node_wis) in
168 base_score * (List.length n.node_wis)
169
170(** Score the given pair of interferent interfaces against the given
171 configuration *)
172let score_interference c (nodename1, winame1, nodename2, winame2) =
173 let node1 = Hashtbl.find nodes nodename1 in
174 let node2 = Hashtbl.find nodes nodename2 in
175 let f winame = fun wi -> (compare wi.wi_name winame) == 0 in
176 let wi1 = List.find (f winame1) node1.node_wis in
177 let wi2 = List.find (f winame2) node2.node_wis in
178 let channel1 = c wi1.wi_essid in
179 let channel2 = c wi2.wi_essid in
180 let diff = abs (channel1 - channel2) in
181 let res = runtable scoretable diff in
182 res
183
184(** Given a list of nodes and a configuration, return the score for the whole
185 configuration. This is the sum of the scores for all nodes, plus the sum
186 of the scores for all user-specified interferent pairs of interfaces. *)
187let score_configuration ns c =
188 let mapper = Hashtbl.find c in
189 let f1 a n = a + (node_score mapper n) in
190 let nodescores = List.fold_left f1 0 ns in
191 let f2 a i = a + (score_interference mapper i) in
192 let interference_score = List.fold_left f2 0 !interference in
193 nodescores + interference_score
194
195(** Return a random configuration. For some reason, if this function accesses
196 the global 'groups' hash instead of getting it passed in from above, that
197 hash is empty. *)
198let random_configuration groups evaluate =
199 let h = Hashtbl.create 30 in
200 Hashtbl.iter (fun k _ -> Hashtbl.add h k (1 + (Random.int 12))) groups;
201 { score = (evaluate h); conf = h }
202
203let print_conf conf =
204 let channel wi = string_of_int (Hashtbl.find conf wi.wi_essid) in
205 let print_wi wi = wi.wi_name ^ ": " ^ (channel wi) in
206 let wis node = List.fold_left (fun a wi -> a ^ " " ^ (print_wi wi))
207 "" node.node_wis in
208 let cmpnode a b = compare (a.node_name) (b.node_name) in
209 let sorted_nodes = List.sort cmpnode (values nodes) in
210 let print_node n = print_string (n.node_name ^ ": " ^ (wis n) ^ "\n") in
211 List.iter print_node sorted_nodes
212
213(** n-point crossover operator. pick n points along the length of the parents,
214 produce a child by copying from one parent, switching parents when hitting a
215 chosen crossover point *)
216let crossover n c1 c2 =
217 let keys1 = keys (c1.conf) in
218 let numkeys1 = List.length keys1 in
219 let pickpoint i = (if even i then c1.conf else c2.conf),
220 (if i < n then (Random.int numkeys1) else numkeys1) in
221 let crosspoints = Array.init (n + 1) pickpoint in
222 let result = Hashtbl.create (List.length keys1) in
223 let i = ref 0 in
224 Array.sort (fun a b -> compare (snd a) (snd b)) crosspoints;
225 Array.iter (fun (h, p) -> while !i < p do
226 let key = List.nth keys1 !i in
227 Hashtbl.add result key (Hashtbl.find h key);
228 incr i
229 done) crosspoints;
230 assert ((List.length (keys result)) == (List.length keys1));
231 { score = 0; conf = result }
232
233(** Generalized evolutionary algorithm driver.
234 initialize: () -> configuration array
235 recombine:
236 mutate: configuration array -> configuration array
237 evaluate: configuration array -> configuration array
238 select: configuration array -> configuration array
239
240 and the result is the best configuration *)
241let evolutionary_algorithm initialize recombine mutate evaluate select =
242 let population = (evaluate $ initialize) () in
243 let last_high_score = ref population.(0).score in
244 let iterations_since_new_high_score = ref 0 in
245 let generation = ref 0 in
246 let all_nodes = values nodes in
247 (*let iterate = recombine $ mutate $ evaluate $ select in*)
248 let iterate = select $ evaluate $ mutate $ recombine in
249 while !iterations_since_new_high_score < max_stagnant_iterations do
250 let newpop = iterate population in
251 assert ((Array.length newpop) == population_size);
252 copyarray newpop population;
253 let high_score = population.(0).score in
254 if high_score > !last_high_score then begin
255 last_high_score := high_score;
256 iterations_since_new_high_score := 0
257 end;
258 assert (!last_high_score >= high_score);
259 if (!generation mod 10) == 0 then begin
260 print_int !generation;
261 print_string ": ";
262 print_int !last_high_score;
263 print_newline();
264 end;
265 incr iterations_since_new_high_score;
266 incr generation
267 done;
268 population.(0)
269
270(** BEGIN PARSING CODE *)
271
272(** Given a filename, return a list of all the lines in the file with the given
273 filename. Don't count on the order of the lines in the result. *)
274let snarf_lines fname =
275 let infile = open_in fname in
276 let result = ref [] in
277 try
278 while true do
279 result := (input_line infile)::!result
280 done;
281 !result (* never gets here *)
282 with End_of_file -> !result
283
284(** Read the main input from the given filename *)
285let parse_file fname =
286 let spacere = Str.regexp " " in
287 (** Given the name of the node currently being parsed, parse the given
288 tuple that consists of a wi name and an essid. *)
289 let parse_pair nodename (wname, essid) =
290 let new_wi = { wi_name = wname;
291 wi_nodename = nodename;
292 wi_essid = essid} in
293 let _ = try
294 let group = Hashtbl.find groups essid in
295 group.group_wis <- new_wi::group.group_wis;
296 with Not_found ->
297 let group = { group_essid = essid;
298 group_wis = [ new_wi ] } in
299 Hashtbl.add groups essid group in
300 new_wi in
301 let parse_fields fields =
302 let nodename = head fields in
303 let rec makepairs l =
304 match l with
305 [] -> []
306 | x::[] -> assert false
307 | a::b::xs -> (a, b)::(makepairs xs) in
308 let wis = List.map (parse_pair nodename)
309 (makepairs (tail fields)) in
310 let sorted_wis = List.sort compare wis in
311 let node = { node_name = nodename; node_wis = sorted_wis } in
312 Hashtbl.add nodes nodename node in
313 List.iter (parse_fields $ (Str.split spacere)) (snarf_lines fname)
314
315(* the parsers for the special case components *)
316
317(** The first field is the nodename, the rest are interface names *)
318let parse_nointerference fs = Hashtbl.add nointerference (head fs) (tail fs)
319(** Read four fields from the given list and add them as a tuple to the given
320 list reference *)
321let parse_quadruplet l fs =
322 let f = List.nth fs in
323 l := (f 0, f 1, f 2, f 3)::!l
324(** The first field is the nodename, the rest are channels.*)
325let parse_unusable fs =
326 let channels = List.map int_of_string (tail fs) in
327 Hashtbl.add unusable (head fs) channels
328(** The first field is the supernode name, the rest are the names of the
329 subnodes. Construct a new node for the supernode and remove the subnodes
330 from the nodes hash *)
331let parse_supernode fs =
332 let nodename = head fs in
333 let subnodenames = tail fs in
334 let subnodes = List.map (Hashtbl.find nodes) subnodenames in
335 List.iter (Hashtbl.remove nodes) subnodenames;
336 let prefixed_wis n = List.map (fun w -> { w with wi_name = n.node_name ^ "." ^ w.wi_name}) n.node_wis in
337 let wis = List.fold_left (fun a s -> a@(prefixed_wis s)) [] subnodes in
338 let node = { node_name = nodename; node_wis = wis } in
339 Hashtbl.add nodes nodename node
340
341let parse_special_conf fname =
342 let spacere = Str.regexp " " in
343 let functable = [ "nointerference", parse_nointerference;
344 "important", parse_quadruplet importantlinks;
345 "interference", parse_quadruplet interference;
346 "unusable", parse_unusable;
347 "supernode", parse_supernode ] in
348 let do_line fs = (List.assoc (head fs) functable) (tail fs) in
349 try
350 List.iter (do_line $ Str.split spacere) (snarf_lines fname)
351 with x -> ()
352
353(** END PARSING CODE *)
354
355let main =
356 parse_file Sys.argv.(1);
357 parse_special_conf "special.conf";
358 Random.self_init();
359 let all_nodes = values nodes in
360 let evaluate_hash = score_configuration all_nodes in
361 let initialize () = Array.init population_size (fun _ -> random_configuration groups evaluate_hash) in
362 let recombine pop = pop in
363(*
364 let numoffspring = Random.int population_size in
365 let children = Array.init numoffspring (fun _ ->
366 let father = pop.(Random.int population_size) in
367 let mother = pop.(Random.int population_size) in
368 crossover 2 father mother) in
369 Array.append pop children in *)
370 let maxchannel essid =
371 let group = Hashtbl.find groups essid in
372 if (List.length group.group_wis) == 1 then 11
373 else 13 in
374 let mutate_conf conf =
375 Hashtbl.iter (fun essid _ ->
376 let f = Random.float 1.0 in
377 if (f < mutation_rate) then
378 let channel = 1 + (Random.int (maxchannel essid)) in
379 Hashtbl.replace conf essid channel) conf in
380 let mutate population =
381 let mutants = Array.map (fun c -> let hash = Hashtbl.copy c.conf in
382 mutate_conf hash;
383 { score = evaluate_hash hash;
384 conf = hash}) population in
385 Array.append population mutants in
386 let evaluate population =
387 Array.iter (fun c -> c.score <- evaluate_hash c.conf) population;
388 population in
389 let select p =
390 Array.sort (fun a b -> compare b.score a.score) p;
391 (*shuffle p;*)
392 Array.sub p 0 population_size in
393 let best = evolutionary_algorithm initialize recombine mutate evaluate select in
394 print_conf best.conf;;
395
396main
Note: See TracBrowser for help on using the repository browser.