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? *)
|
---|
35 | let population_size = 100
|
---|
36 | (** How long should the system iterate after an improvement? *)
|
---|
37 | and max_stagnant_iterations = 10000
|
---|
38 | (** What is the chance for an ESSID to channel assignment to mutate to a
|
---|
39 | random channel? *)
|
---|
40 | and 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 *)
|
---|
45 | type wi = {
|
---|
46 | wi_name: string;
|
---|
47 | wi_nodename: string;
|
---|
48 | wi_essid: string;
|
---|
49 | };;
|
---|
50 | type group = {
|
---|
51 | group_essid: string;
|
---|
52 | mutable group_wis: wi list;
|
---|
53 | };;
|
---|
54 | type 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. *)
|
---|
61 | type configuration = {
|
---|
62 | mutable score: int;
|
---|
63 | conf: (string, int) Hashtbl.t;
|
---|
64 | };;
|
---|
65 | type 'a maybe = Nothing | Just of 'a;;
|
---|
66 |
|
---|
67 | (** The global nodes hash, mapping from node name to node struct. *)
|
---|
68 | let nodes = Hashtbl.create 4
|
---|
69 | (** The global groups hash, mapping from essid to group struct. *)
|
---|
70 | let groups = Hashtbl.create 4
|
---|
71 |
|
---|
72 | (* some convenience functions *)
|
---|
73 |
|
---|
74 | (** Function composition. *)
|
---|
75 | let compose f g = fun x -> f(g(x))
|
---|
76 | let ($) = compose
|
---|
77 | (** Turn two individual values into a tuple *)
|
---|
78 | let maketuple a b = (a, b)
|
---|
79 | (** Shorthand for List.hd *)
|
---|
80 | let head = List.hd
|
---|
81 | (** Shorthand for List.tail *)
|
---|
82 | let tail = List.tl
|
---|
83 | let just x = match x with
|
---|
84 | Nothing -> assert false
|
---|
85 | | Just s -> s
|
---|
86 | (** Given a hashtable, return all the keys as a list *)
|
---|
87 | let keys t = Hashtbl.fold (fun k d a -> k::a) t []
|
---|
88 | (** Given a hashtable, return all the values as a list *)
|
---|
89 | let values t = Hashtbl.fold (fun k d a -> d::a) t []
|
---|
90 | (** Copy one array into the other *)
|
---|
91 | let 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 *)
|
---|
95 | let 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 *)
|
---|
101 | let 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 *)
|
---|
118 | let 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. *)
|
---|
125 | let 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 *)
|
---|
132 | let 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. *)
|
---|
144 | let 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 |
|
---|
154 | let 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 |
|
---|
165 | let 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. *)
|
---|
173 | let 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 |
|
---|
178 | let 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 |
|
---|
184 | let 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 *)
|
---|
209 | let 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 |
|
---|
236 | let 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 |
|
---|
262 | main
|
---|