source: genesis/nodes/channelea.ml@ 1997

Last change on this file since 1997 was 1997, checked in by lodewijk, 21 years ago
  • bij tuples in een lijst blijken de (haakjes) niet nodig
  • credit /me
File size: 13.4 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 of
157 the scores of all the combinations of interfaces, written down as a fold for
158 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 foldfunc acc (wi1, wi2) = acc + (wi_score c unusable_ nointerference_ wi1 wi2) in
165 let base_score = List.fold_left foldfunc 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 wi1 = List.find (fun wi -> (compare wi.wi_name winame1) == 0) (Hashtbl.find nodes nodename1).node_wis in
172 let wi2 = List.find (fun wi -> (compare wi.wi_name winame2) == 0) (Hashtbl.find nodes nodename2).node_wis in
173 let channel1 = c wi1.wi_essid in
174 let channel2 = c wi2.wi_essid in
175 let diff = abs (channel1 - channel2) in
176 let res = runtable scoretable diff in
177 res
178
179(** Given a list of nodes and a configuration, return the score for the whole
180 configuration. This is the sum of the scores for all nodes, plus the sum
181 of the scores for all user-specified interferent pairs of interfaces. *)
182let score_configuration ns c =
183 let mapper = Hashtbl.find c in
184 let foldfunc acc n = acc + (node_score mapper n) in
185 let nodescores = List.fold_left foldfunc 0 ns in
186 let interference_score = List.fold_left (fun a i -> a + score_interference mapper i) 0 !interference in
187 nodescores + interference_score;;
188
189(** Return a random configuration. For some reason, if this function accesses
190 the global 'groups' hash instead of getting it passed in from above, that
191 hash is empty. *)
192let random_configuration groups evaluate =
193 let h = Hashtbl.create 30 in
194 Hashtbl.iter (fun k _ -> Hashtbl.add h k (1 + (Random.int 12))) groups;
195 { score = (evaluate h); conf = h }
196
197let print_conf conf =
198 let print_wi wi = wi.wi_name ^ ": " ^ (string_of_int (Hashtbl.find conf wi.wi_essid)) in
199 let wis node = List.fold_left (fun acc wi -> acc ^ " " ^ (print_wi wi)) "" node.node_wis in
200 let sorted_nodes = List.sort (fun a b -> compare (a.node_name) (b.node_name)) (values nodes) in
201 List.iter (fun n -> print_string (n.node_name ^ ":" ^ (wis n) ^ "\n")) sorted_nodes
202
203(** Generalized evolutionary algorithm driver.
204 initialize: () -> configuration array
205 recombine:
206 mutate: configuration array -> configuration array
207 evaluate: configuration array -> configuration array
208 select: configuration array -> configuration array
209
210 and the result is the best configuration *)
211let evolutionary_algorithm initialize recombine mutate evaluate select =
212 let population = (evaluate $ initialize) () in
213 let last_high_score = ref population.(0).score in
214 let iterations_since_new_high_score = ref 0 in
215 let generation = ref 0 in
216 let all_nodes = values nodes in
217 let _ = while !iterations_since_new_high_score < max_stagnant_iterations do
218 let newpop = (recombine $ mutate $ evaluate $ select) population in
219 Array.sort (fun a b -> compare b.score a.score) newpop;
220 copyarray newpop population;
221 let high_score = population.(0).score in
222 if high_score > !last_high_score then begin
223 last_high_score := high_score;
224 iterations_since_new_high_score := 0
225 end;
226 assert (!last_high_score >= high_score);
227 if (!generation mod 10) == 0 then begin
228 print_int !generation;
229 print_string ": ";
230 print_int !last_high_score;
231 print_newline();
232 end;
233 incr iterations_since_new_high_score;
234 incr generation
235 done in
236 population.(0);;
237
238(** BEGIN PARSING CODE *)
239
240(** Given a filename, return a list of all the lines in the file with the given
241 filename. Don't count on the order of the lines in the result. *)
242let snarf_lines fname =
243 let infile = open_in fname in
244 let result = ref [] in
245 try
246 while true do
247 result := (input_line infile)::!result
248 done;
249 !result (* never gets here *)
250 with End_of_file -> !result
251
252(** Read the main input from the given filename *)
253let parse_file fname =
254 let spacere = Str.regexp " " in
255 (** Given the name of the node currently being parsed, parse the given tuple
256 that consists of a wi name and an essid. *)
257 let parse_pair nodename (wname, essid) =
258 let new_wi = { wi_name = wname; wi_nodename = nodename; wi_essid = essid} in
259 let foo = try
260 let group = Hashtbl.find groups essid in
261 group.group_wis <- new_wi::group.group_wis;
262 with Not_found ->
263 let group = { group_essid = essid; group_wis = [ new_wi ] } in
264 Hashtbl.add groups essid group in
265 new_wi in
266 let parse_fields fields =
267 let nodename = head fields in
268 let rec makepairs l = match l with
269 [] -> []
270 | x::[] -> assert false
271 | a::b::xs -> (a, b)::(makepairs xs) in
272 let wis = List.map (parse_pair nodename) (makepairs (tail fields)) in
273 let sorted_wis = List.sort compare wis in
274 let node = { node_name = nodename; node_wis = sorted_wis } in
275 Hashtbl.add nodes nodename node in
276 List.iter (parse_fields $ (Str.split spacere)) (snarf_lines fname)
277 ;;
278
279(* the parsers for the special case components *)
280
281(** The first field is the nodename, the rest are interface names *)
282let parse_nointerference fs = Hashtbl.add nointerference (head fs) (tail fs)
283(** Read four fields from the given list and add them as a tuple to the given
284 list reference *)
285let parse_quadruplet l fs =
286 let f = List.nth fs in
287 l := (f 0, f 1, f 2, f 3)::!l
288(** The first field is the nodename, the rest are channels.*)
289let parse_unusable fs =
290 let channels = List.map int_of_string (tail fs) in
291 Hashtbl.add unusable (head fs) channels
292(** The first field is the supernode name, the rest are the names of the
293 subnodes. Construct a new node for the supernode and remove the subnodes
294 from the nodes hash *)
295let parse_supernode fs =
296 let nodename = head fs in
297 let subnodenames = tail fs in
298 let subnodes = List.map (Hashtbl.find nodes) subnodenames in
299 List.iter (Hashtbl.remove nodes) subnodenames;
300 let prefixed_wis n = List.map (fun w -> { w with wi_name = n.node_name ^ "." ^ w.wi_name}) n.node_wis in
301 let wis = List.fold_left (fun a s -> a@(prefixed_wis s)) [] subnodes in
302 let node = { node_name = nodename; node_wis = wis } in
303 Hashtbl.add nodes nodename node
304
305let parse_special_conf fname =
306 let spacere = Str.regexp " " in
307 let functable = [ "nointerference", parse_nointerference;
308 "important", parse_quadruplet importantlinks;
309 "interference", parse_quadruplet interference;
310 "unusable", parse_unusable;
311 "supernode", parse_supernode ] in
312 let do_line fs = (List.assoc (head fs) functable) (tail fs) in
313 try
314 List.iter (do_line $ Str.split spacere) (snarf_lines fname)
315 with x -> ()
316
317(** END PARSING CODE *)
318
319let main =
320 parse_file Sys.argv.(1);
321 parse_special_conf "special.conf";
322 Random.self_init();
323 let all_nodes = values nodes in
324 let evaluate_hash = score_configuration all_nodes in
325 let initialize () = Array.init population_size (fun _ -> random_configuration groups evaluate_hash) in
326 let recombine x = x in
327 let mutate_conf conf =
328 Hashtbl.iter (fun essid _ -> let f = Random.float 1.0 in
329 let group = Hashtbl.find groups essid in
330 let maxchannel = if (List.length group.group_wis) == 1 then 11
331 else 13 in
332 if (f < mutation_rate) then
333 Hashtbl.replace conf essid (1 + (Random.int maxchannel))) conf in
334 let mutate population = let mutants = Array.map (fun c -> let hash = Hashtbl.copy c.conf in
335 mutate_conf hash;
336 { score = evaluate_hash hash;
337 conf = hash}) population in
338 Array.append population mutants in
339 let evaluate population = Array.iter (fun c -> c.score <- evaluate_hash c.conf) population;
340 population in
341 let select p = Array.sub p 0 ((Array.length p) / 2) in
342 let best = evolutionary_algorithm initialize recombine mutate evaluate select in
343 print_conf best.conf;;
344
345main
Note: See TracBrowser for help on using the repository browser.