source: genesis/nodes/channelea.ml@ 1998

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

syntax cleanup, overbodige ;;'s weg en wat regels die wrappen met a2ps herlayout.

File size: 13.5 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 just x = match x with
122 Nothing -> assert false
123 | Just s -> s
124(** Given a hashtable, return all the keys as a list *)
125let keys t = Hashtbl.fold (fun k d a -> k::a) t []
126(** Given a hashtable, return all the values as a list *)
127let values t = Hashtbl.fold (fun k d a -> d::a) t []
128(** Copy one array into the other *)
129let copyarray src dest = Array.blit src 0 dest 0 (Array.length src)
130
131(** Is the given element in the given list? uses compare, so it works on
132 strings as well *)
133let in_list l e = try
134 let _ = List.find (fun x -> (compare e x) == 0) l in
135 true
136 with Not_found -> false
137
138(** Given a list, return a list of pairs with all possible combinations of
139 items from the given list *)
140let rec combinations l =
141 match l with
142 [] -> []
143 | x::xs -> (List.map (maketuple x) xs)@(combinations xs)
144
145(** Given a configuration and two wi's, return the score *)
146let wi_score c unusable nointerference wi1 wi2 =
147 let channel1 = c wi1.wi_essid in
148 let channel2 = c wi2.wi_essid in
149 let diff = abs (channel1 - channel2) in
150 let is_unusable = in_list unusable in
151 if (is_unusable channel1) || (is_unusable channel2) then -10000
152 else if (in_list nointerference wi1.wi_name) &&
153 (in_list nointerference wi2.wi_name) then 1
154 else runtable scoretable diff
155
156(** Given a configuration and a node, return the score. this is simply the sum
157 of the scores of all the combinations of interfaces, written down as a fold
158 for efficiency *)
159let node_score c n =
160 let nointerference_ = try Hashtbl.find nointerference n.node_name
161 with Not_found -> [] in
162 let unusable_ = try Hashtbl.find unusable n.node_name
163 with Not_found -> [] in
164 let f a (wi1, wi2) = a + (wi_score c unusable_ nointerference_ wi1 wi2) in
165 let base_score = List.fold_left f 0 (combinations n.node_wis) in
166 base_score * (List.length n.node_wis)
167
168(** Score the given pair of interferent interfaces against the given
169 configuration *)
170let score_interference c (nodename1, winame1, nodename2, winame2) =
171 let node1 = Hashtbl.find nodes nodename1 in
172 let node2 = Hashtbl.find nodes nodename2 in
173 let f winame = fun wi -> (compare wi.wi_name winame) == 0 in
174 let wi1 = List.find (f winame1) node1.node_wis in
175 let wi2 = List.find (f winame2) node2.node_wis in
176 let channel1 = c wi1.wi_essid in
177 let channel2 = c wi2.wi_essid in
178 let diff = abs (channel1 - channel2) in
179 let res = runtable scoretable diff in
180 res
181
182(** Given a list of nodes and a configuration, return the score for the whole
183 configuration. This is the sum of the scores for all nodes, plus the sum
184 of the scores for all user-specified interferent pairs of interfaces. *)
185let score_configuration ns c =
186 let mapper = Hashtbl.find c in
187 let f1 a n = a + (node_score mapper n) in
188 let nodescores = List.fold_left f1 0 ns in
189 let f2 a i = a + (score_interference mapper i) in
190 let interference_score = List.fold_left f2 0 !interference in
191 nodescores + interference_score
192
193(** Return a random configuration. For some reason, if this function accesses
194 the global 'groups' hash instead of getting it passed in from above, that
195 hash is empty. *)
196let random_configuration groups evaluate =
197 let h = Hashtbl.create 30 in
198 Hashtbl.iter (fun k _ -> Hashtbl.add h k (1 + (Random.int 12))) groups;
199 { score = (evaluate h); conf = h }
200
201let print_conf conf =
202 let channel wi = string_of_int (Hashtbl.find conf wi.wi_essid) in
203 let print_wi wi = wi.wi_name ^ ": " ^ (channel wi) in
204 let wis node = List.fold_left (fun a wi -> a ^ " " ^ (print_wi wi))
205 "" node.node_wis in
206 let cmpnode a b = compare (a.node_name) (b.node_name) in
207 let sorted_nodes = List.sort cmpnode (values nodes) in
208 let print_node n = print_string (n.node_name ^ ": " ^ (wis n) ^ "\n") in
209 List.iter print_node sorted_nodes
210
211(** Generalized evolutionary algorithm driver.
212 initialize: () -> configuration array
213 recombine:
214 mutate: configuration array -> configuration array
215 evaluate: configuration array -> configuration array
216 select: configuration array -> configuration array
217
218 and the result is the best configuration *)
219let evolutionary_algorithm initialize recombine mutate evaluate select =
220 let population = (evaluate $ initialize) () in
221 let last_high_score = ref population.(0).score in
222 let iterations_since_new_high_score = ref 0 in
223 let generation = ref 0 in
224 let all_nodes = values nodes in
225 let iterate = recombine $ mutate $ evaluate $ select in
226 while !iterations_since_new_high_score < max_stagnant_iterations do
227 let newpop = iterate population in
228 Array.sort (fun a b -> compare b.score a.score) newpop;
229 copyarray newpop population;
230 let high_score = population.(0).score in
231 if high_score > !last_high_score then begin
232 last_high_score := high_score;
233 iterations_since_new_high_score := 0
234 end;
235 assert (!last_high_score >= high_score);
236 if (!generation mod 10) == 0 then begin
237 print_int !generation;
238 print_string ": ";
239 print_int !last_high_score;
240 print_newline();
241 end;
242 incr iterations_since_new_high_score;
243 incr generation
244 done;
245 population.(0)
246
247(** BEGIN PARSING CODE *)
248
249(** Given a filename, return a list of all the lines in the file with the given
250 filename. Don't count on the order of the lines in the result. *)
251let snarf_lines fname =
252 let infile = open_in fname in
253 let result = ref [] in
254 try
255 while true do
256 result := (input_line infile)::!result
257 done;
258 !result (* never gets here *)
259 with End_of_file -> !result
260
261(** Read the main input from the given filename *)
262let parse_file fname =
263 let spacere = Str.regexp " " in
264 (** Given the name of the node currently being parsed, parse the given
265 tuple that consists of a wi name and an essid. *)
266 let parse_pair nodename (wname, essid) =
267 let new_wi = { wi_name = wname;
268 wi_nodename = nodename;
269 wi_essid = essid} in
270 let _ = try
271 let group = Hashtbl.find groups essid in
272 group.group_wis <- new_wi::group.group_wis;
273 with Not_found ->
274 let group = { group_essid = essid;
275 group_wis = [ new_wi ] } in
276 Hashtbl.add groups essid group in
277 new_wi in
278 let parse_fields fields =
279 let nodename = head fields in
280 let rec makepairs l =
281 match l with
282 [] -> []
283 | x::[] -> assert false
284 | a::b::xs -> (a, b)::(makepairs xs) in
285 let wis = List.map (parse_pair nodename)
286 (makepairs (tail fields)) in
287 let sorted_wis = List.sort compare wis in
288 let node = { node_name = nodename; node_wis = sorted_wis } in
289 Hashtbl.add nodes nodename node in
290 List.iter (parse_fields $ (Str.split spacere)) (snarf_lines fname)
291
292(* the parsers for the special case components *)
293
294(** The first field is the nodename, the rest are interface names *)
295let parse_nointerference fs = Hashtbl.add nointerference (head fs) (tail fs)
296(** Read four fields from the given list and add them as a tuple to the given
297 list reference *)
298let parse_quadruplet l fs =
299 let f = List.nth fs in
300 l := (f 0, f 1, f 2, f 3)::!l
301(** The first field is the nodename, the rest are channels.*)
302let parse_unusable fs =
303 let channels = List.map int_of_string (tail fs) in
304 Hashtbl.add unusable (head fs) channels
305(** The first field is the supernode name, the rest are the names of the
306 subnodes. Construct a new node for the supernode and remove the subnodes
307 from the nodes hash *)
308let parse_supernode fs =
309 let nodename = head fs in
310 let subnodenames = tail fs in
311 let subnodes = List.map (Hashtbl.find nodes) subnodenames in
312 List.iter (Hashtbl.remove nodes) subnodenames;
313 let prefixed_wis n = List.map (fun w -> { w with wi_name = n.node_name ^ "." ^ w.wi_name}) n.node_wis in
314 let wis = List.fold_left (fun a s -> a@(prefixed_wis s)) [] subnodes in
315 let node = { node_name = nodename; node_wis = wis } in
316 Hashtbl.add nodes nodename node
317
318let parse_special_conf fname =
319 let spacere = Str.regexp " " in
320 let functable = [ "nointerference", parse_nointerference;
321 "important", parse_quadruplet importantlinks;
322 "interference", parse_quadruplet interference;
323 "unusable", parse_unusable;
324 "supernode", parse_supernode ] in
325 let do_line fs = (List.assoc (head fs) functable) (tail fs) in
326 try
327 List.iter (do_line $ Str.split spacere) (snarf_lines fname)
328 with x -> ()
329
330(** END PARSING CODE *)
331
332let main =
333 parse_file Sys.argv.(1);
334 parse_special_conf "special.conf";
335 Random.self_init();
336 let all_nodes = values nodes in
337 let evaluate_hash = score_configuration all_nodes in
338 let initialize () = Array.init population_size (fun _ -> random_configuration groups evaluate_hash) in
339 let recombine x = x in
340 let maxchannel essid =
341 let group = Hashtbl.find groups essid in
342 if (List.length group.group_wis) == 1 then 11
343 else 13 in
344 let mutate_conf conf =
345 Hashtbl.iter (fun essid _ ->
346 let f = Random.float 1.0 in
347 if (f < mutation_rate) then
348 let channel = 1 + (Random.int (maxchannel essid)) in
349 Hashtbl.replace conf essid channel) conf in
350 let mutate population =
351 let mutants = Array.map (fun c -> let hash = Hashtbl.copy c.conf in
352 mutate_conf hash;
353 { score = evaluate_hash hash;
354 conf = hash}) population in
355 Array.append population mutants in
356 let evaluate population =
357 Array.iter (fun c -> c.score <- evaluate_hash c.conf) population;
358 population in
359 let select p = Array.sub p 0 ((Array.length p) / 2) in
360 let best = evolutionary_algorithm initialize recombine mutate evaluate select in
361 print_conf best.conf;;
362
363main
Note: See TracBrowser for help on using the repository browser.