problem_generator/problem/clique_tree.rs
1/*!
2Module for the 1) Clique Tree construction and global optimum calculation, 2) and the struct to contain solutions.
3*/
4
5use rand_chacha::ChaChaRng;
6use rand::seq::SliceRandom;
7use serde::{Deserialize, Serialize};
8
9use std::{error::Error, str::Lines};
10
11use crate::problem::codomain_subclasses::CodomainFunction;
12use crate::problem::problem_generation::Problem;
13
14const FITNESS_EPSILON: f64 = 0.0000000001;
15
16///Struct to contain the solution and its fitness, with the solution stored as a vector of u32 values (0 or 1) and the fitness as a f64 value
17#[derive(Debug, Clone)]
18pub struct SolutionFit {
19 pub solution: Vec<u32>,
20 pub fitness: f64,
21}
22
23///Struct to contain the input parameters of the TD Mk Landscape:
24/// Number of cliques/subfunctions M,
25/// size k of each clique/subfunction,
26/// number of overlapping variables between cliques/subfunctions o,
27/// number of branches in the clique tree / tree decomposition b
28#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
29pub struct InputParameters {
30 pub m: u32,
31 pub k: u32,
32 pub o: u32,
33 pub b: u32,
34}
35
36impl InputParameters {
37 pub fn new(args: &[String]) -> Result<InputParameters, &'static str> {
38 if args.len() < 5 {
39 return Err("not enough arguments");
40 }
41
42 let m = args[1]
43 .clone()
44 .parse::<u32>()
45 .map_err(|_| "Could not parse M to u32")?;
46 let k = args[2]
47 .clone()
48 .parse::<u32>()
49 .map_err(|_| "Could not parse k to u32")?;
50 let o = args[3]
51 .clone()
52 .parse::<u32>()
53 .map_err(|_| "Could not parse o to u32")?;
54 let b = args[4]
55 .clone()
56 .parse::<u32>()
57 .map_err(|_| "Could not parse b to u32")?;
58
59 //let codomain_file = args[5].clone();
60 Ok(InputParameters { m, k, o, b })
61 }
62
63 pub fn new_from_primitives(m: u32, k: u32, o: u32, b: u32) -> InputParameters {
64 InputParameters { m, k, o, b }
65 }
66
67 ///Get the input parameters from an iterator containing the line on which the parameters are listed
68 pub fn from_line_iterator(
69 content_iterator: &mut Lines,
70 ) -> Result<InputParameters, Box<dyn Error>> {
71 //Get the line that contains the parameters
72 let line = content_iterator
73 .next()
74 .ok_or("Input file does not contain enough entries")?;
75 //Split the line
76 let parameters: Vec<&str> = line.split(' ').collect();
77 if parameters.len() != 4 {
78 return Err("not enough input parameters on first line of input file".into());
79 }
80 //And set the parameters
81 let m: u32 = parameters[0].parse()?;
82 let k: u32 = parameters[1].parse()?;
83 let o: u32 = parameters[2].parse()?;
84 let b: u32 = parameters[3].parse()?;
85
86 Ok(InputParameters::new_from_primitives(m, k, o, b))
87 }
88}
89
90#[repr(C)]
91#[derive(Debug)]
92///The CliqueTree struct with properties input parameters, clique variable indices, the used codomain function, codomain values, global optimum strings and score
93pub struct CliqueTree {
94 pub input_parameters: InputParameters,
95 pub codomain_function: CodomainFunction,
96 pub cliques: Vec<Vec<u32>>,
97 pub codomain_values: Vec<Vec<f64>>,
98 pub glob_optima_strings: Vec<Vec<u32>>,
99 pub glob_optima_score: f64,
100}
101
102impl CliqueTree {
103 pub fn new(
104 input_parameters: InputParameters,
105 codomain_function: CodomainFunction,
106 codomain_values: Vec<Vec<f64>>,
107 rng: &mut ChaChaRng,
108 ) -> CliqueTree {
109 //Create a new clique tree (as its cliques and separators)
110 let (cliques, separators) = CliqueTree::construct(&input_parameters, rng);
111
112 //Then calculate the global optimum (optima) for the clique tree
113 let global_opt_tuples = CliqueTree::calculate_global_optima(
114 &input_parameters,
115 &codomain_function,
116 &codomain_values,
117 &cliques,
118 &separators,
119 );
120
121 let glob_optima_score = global_opt_tuples[0].1;
122 let glob_optima_strings = global_opt_tuples.into_iter().map(|tuple| tuple.0).collect();
123
124 // and return the resulting CliqueTree struct
125 CliqueTree {
126 input_parameters,
127 codomain_function,
128 cliques,
129 codomain_values,
130 glob_optima_strings,
131 glob_optima_score,
132 }
133 }
134
135 ///Construct the clique tree from the problem struct and codomain values
136 pub fn construct_from_problem_codomain(problem: Problem, codomain: Vec<Vec<f64>>) -> Self {
137 CliqueTree {
138 input_parameters: problem.input_parameters,
139 codomain_function: CodomainFunction::Unknown,
140 cliques: problem.cliques,
141 codomain_values: codomain,
142 glob_optima_strings: problem.glob_optima_strings,
143 glob_optima_score: problem.glob_optima_score,
144 }
145 }
146
147 ///Calculate the global optimum for a separable problem
148 fn calculate_global_optimum_separable(
149 input_parameters: &InputParameters,
150 codomain_values: &[Vec<f64>],
151 cliques: &[Vec<u32>],
152 ) -> Vec<(Vec<u32>, f64)> {
153 //Set score to 0 and glob_optimum string to all zeroes.
154 let mut glob_opt_score = 0.0;
155
156 //Store the optimas per clique. The optima are stored as a number whose bit representation is the actual solution substring.
157 let mut clique_optimas = Vec::with_capacity(input_parameters.m as usize);
158
159 let mut number_global_optima_strings = 1;
160
161 //Go over all 'cliques/subfunctions'
162 for i in 0..input_parameters.m {
163 //Set the current highest score for this subfunction to the string with all zeroes.
164 let mut highest_score = codomain_values[i as usize][0];
165 let mut highest_score_indices = vec![0];
166
167 //Go over the rest of the possible permutations of the string.
168 for j in 1..(1 << input_parameters.k) as usize {
169 //And determine whether they have a higher score
170 let score = codomain_values[i as usize][j as usize];
171 if is_equal_fitness(score, highest_score) {
172 highest_score_indices.push(j as u32);
173 } else if is_better_fitness(score, highest_score) {
174 highest_score = score;
175 highest_score_indices.clear();
176 highest_score_indices.push(j as u32);
177 }
178 }
179
180 //Add the highest score to the global optimum score
181 glob_opt_score += highest_score;
182
183 //Calculate the number of global optima
184 number_global_optima_strings *= highest_score_indices.len() as u32;
185 //And push this clique's optima to the clique_optima list
186 clique_optimas.push(highest_score_indices);
187 }
188
189 //Construct the global optima strings. First reserve space equal to the number of global optima, then add a first element.
190 let mut result_optima_strings = Vec::with_capacity(number_global_optima_strings as usize);
191 result_optima_strings.push(vec![0; (input_parameters.m * input_parameters.k) as usize]);
192
193 //Construct the global optima
194 CliqueTree::set_optimal_clique_substrings(
195 input_parameters,
196 cliques,
197 &mut result_optima_strings,
198 &clique_optimas,
199 0,
200 );
201
202 //Return global optima strings and score
203 result_optima_strings
204 .into_iter()
205 .map(|optimum| (optimum, glob_opt_score))
206 .collect()
207 }
208
209 ///Construct the global optima, by inserting a clique's optimal substrings into the global optima strings and calling itself recursively for the next clique.
210 ///When there are more than one optimal substrings for a clique, we clone the current global optima and then set all the values.
211 fn set_optimal_clique_substrings(
212 input_parameters: &InputParameters,
213 cliques: &[Vec<u32>],
214 result_optima_strings: &mut Vec<Vec<u32>>,
215 clique_optimas: &[Vec<u32>],
216 current_index: usize,
217 ) {
218 //If we handled all the cliques, exit.
219 if current_index as u32 == input_parameters.m {
220 return;
221 }
222
223 //Otherwise, first clone the current global optima strings
224 let original_global_optima_length = result_optima_strings.len();
225 //We want to clone (number_clique_optima - 1) times, as we already have one instance.
226 for _ in 0..clique_optimas[current_index].len() - 1 {
227 //clone the global optima
228 for i in 0..original_global_optima_length {
229 result_optima_strings.push(result_optima_strings[i].clone());
230 }
231 }
232
233 //and then set the clique's optimal substrings's values in the global optima strings
234 //Go over all the clique optima
235 for (num, clique_optimum) in clique_optimas[current_index].iter().enumerate() {
236 //And for each, we insert its values into the original global optima.
237 for i in 0..original_global_optima_length {
238 //Insert all its values
239 for j in 0..input_parameters.k {
240 result_optima_strings[original_global_optima_length * num + i]
241 [cliques[current_index][j as usize] as usize] =
242 (clique_optimum >> (input_parameters.k - j - 1)) & 1;
243 }
244 }
245 }
246
247 //Call itself recursively to insert next clique's optimal values
248 CliqueTree::set_optimal_clique_substrings(
249 input_parameters,
250 cliques,
251 result_optima_strings,
252 clique_optimas,
253 current_index + 1,
254 );
255 }
256
257 ///Calculate the global optima strings and fitnesses
258 pub fn calculate_global_optima(
259 input_parameters: &InputParameters,
260 codomain_function: &CodomainFunction,
261 codomain_values: &[Vec<f64>],
262 cliques: &[Vec<u32>],
263 separators: &[Vec<u32>],
264 ) -> Vec<(Vec<u32>, f64)> {
265 //If the problem is separable, we use a simple optimizer.
266 if input_parameters.o == 0 {
267 return CliqueTree::calculate_global_optimum_separable(
268 input_parameters,
269 codomain_values,
270 cliques,
271 );
272 }
273
274 //Capacity set to 2 right now, as I assume the number of global optima is low;
275 // every time there are more than 2 C/S instances with the same score for a given seperator instance,
276 // we will need to allocate memory, which is unwanted. Better be safe than sorry here.
277 let size_per_separator_instance = if let CodomainFunction::NKq { q: _ } = codomain_function
278 {
279 1 << (input_parameters.k - input_parameters.o)
280 } else {
281 2
282 };
283
284 // [M][o] = [(best_string1, best_score), (best_string2, best_score)], so it saves the h_i by selecting
285 // the best strings with their score for each x_a and x_b value
286 //possible TODO: Can't we store the index of the substring instead of the substring, i.e. u32 instead of Vec<u32>?
287 //This should make sure that the inner vectors are initialized
288 let mut best_scores: Vec<Vec<Vec<(Vec<u32>, f64)>>> =
289 vec![
290 vec![
291 Vec::with_capacity(size_per_separator_instance);
292 (1 << input_parameters.o) as usize
293 ];
294 input_parameters.m as usize
295 ];
296
297 //Determine number of levels to detect whether a clique has any children, and how to reach that child.
298 //Also store the start indices for each level
299 let mut sum = 0;
300 let mut l = 0;
301 let mut start_indices = Vec::new();
302 while sum < input_parameters.m {
303 start_indices.push(sum);
304 sum += input_parameters.b.pow(l);
305 l += 1;
306 }
307
308 //Set lowest level and its start index
309 let start_index_lowest_level = sum - input_parameters.b.pow(l - 1);
310 let lowest_level = l - 1;
311
312 //Set current level and its start index
313 let mut start_index_current_level = start_index_lowest_level;
314 let mut current_level = lowest_level;
315
316 //Calculate all possible substrings, so that we can easily store and retrieve the substrings for the given index.
317 // This way, we don't need to use intermediate representations that use the substrings, but simply an index that points to the substring.
318 let possible_clique_substrings = get_possible_substrings(input_parameters.k);
319 let possible_separator_substrings = get_possible_substrings(input_parameters.o);
320 let possible_clique_without_separator_substrings =
321 get_possible_substrings(input_parameters.k - input_parameters.o);
322
323 //Go over all nodes but the root, in reversed order.
324 for i in (1..input_parameters.m).rev() {
325 //Keep track of current level in the tree, and the current start index for that level
326 if i < start_index_current_level {
327 current_level -= 1;
328 start_index_current_level = start_indices[current_level as usize];
329 }
330
331 //Iterate over all possible values for the separator, so that we can calculate h_i(x_a, x_b) for these values (of x_a and x_b).
332 for j in 0..possible_separator_substrings.len() {
333 //Keep track of highest score and the highest scoring Ci/Si values, for these Si values (j)
334 //TODONE: replace this with another value as soon as we allow for multiple global optima. I can make these quite a bit bigger, as it's a small structure.
335 let mut scores = Vec::with_capacity(1 << (input_parameters.k - input_parameters.o));
336 let mut highest_score = 0.0;
337 //Iterate over all possible values for Ci/Si. Store the score in the list if it has a higher score than the current highest score.
338 for k in 0..possible_clique_without_separator_substrings.len() {
339 //Calculate f(x_p x_q x_r), which is given by the codomain values passed as input.
340 //I assume codomain is structured [M][k] = score
341 let mut score = codomain_values[i as usize]
342 [j * possible_clique_without_separator_substrings.len() + k]; //f
343 //Then, if it's a parent, add h_l for each child l.
344 if i < start_index_lowest_level {
345 let start_index_children = start_indices[(current_level + 1) as usize]
346 + input_parameters.b * (i - start_index_current_level);
347 for child_index in
348 start_index_children..(start_index_children + input_parameters.b)
349 {
350 //Make sure child exists!
351 if child_index >= input_parameters.m {
352 break;
353 }
354 //Maakt niet uit welke optie we kiezen toch? Want ze hebben allemaal dezelfde score en er hoeft verder nog niet gebrancht te worden,
355 // het enige dat belangrijk is, is dat we de hoogste score selecteren. Toch? Daarna kunnen we aangeven dat er meerdere globale optima zijn.
356 //Calculate the separator substring values for the current child, from the parent clique substring.
357 let separator_substring = get_child_separator_substring(
358 &cliques[i as usize],
359 &separators[child_index as usize],
360 &possible_clique_substrings
361 [j * possible_clique_without_separator_substrings.len() + k],
362 );
363 //separators shouldn't break here, as we have now inserted a filler for 'separator 0', which doesn't exist,
364 // so everything should be aligned well.
365 //Add the h_l for this child l to the parent's score, by first transforming into an index variant (easier storage) and
366 // then retrieving the stored score of the child using the separator substring index.
367 let separator_substring_index_version =
368 transform_substring_vector_to_index(&separator_substring);
369 score += best_scores[child_index as usize]
370 [separator_substring_index_version as usize][0]
371 .1;
372 //h_child
373 }
374 }
375 //store temporarily highest score in scores
376 //This already allows for multiple highest scores
377 if !scores.is_empty() && is_better_fitness(score, highest_score) {
378 scores.clear();
379 }
380 if scores.is_empty() || is_better_or_equal_fitness(score, highest_score) {
381 //TODO: Here I could store k instead of the substring!
382 scores.push((
383 possible_clique_without_separator_substrings[k].clone(),
384 score,
385 ));
386 highest_score = score;
387 }
388 }
389
390 //store the highest score into h for that separator (i) and for these values of the separator(j)
391 for tuple in scores.into_iter() {
392 //This shouldn't break anymore, as we should now have initialized the inner array (j as usize)
393 best_scores[i as usize][j as usize].push(tuple);
394 }
395 }
396 }
397
398 //Now we need to process the root to get the global optimum, which is just one more calculation,
399 // but is different from the others, as it doesn't have a separator.
400
401 //Store the scores again in a list
402 let mut scores = Vec::with_capacity(1 << input_parameters.k);
403 let mut highest_score = 0.0;
404
405 //Iterate over all possible clique substrings / values for the root
406 for c in 0..possible_clique_substrings.len() {
407 //I assume codomain is structured [M][k] = score
408 //Add f
409 let mut score = codomain_values[0][c as usize]; //f
410
411 //Add the h_l scores for each child l.
412 let start_index_children = 1;
413 for child_index in start_index_children..(start_index_children + input_parameters.b) {
414 //Maakt niet uit welke optie we kiezen toch? Want ze hebben allemaal dezelfde score en er hoeft verder nog niet gebrancht te worden,
415 // het enige dat belangrijk is, is dat we de hoogste score selecteren. Toch? Daarna kunnen we aangeven dat er meerdere globale optima zijn.
416
417 //Make sure child exists!
418 if child_index >= input_parameters.m {
419 break;
420 }
421
422 //Calculate the separator substring values for the current child, from the parent clique substring.
423 let separator_substring = get_child_separator_substring(
424 &cliques[0],
425 &separators[child_index as usize],
426 &possible_clique_substrings[c],
427 );
428 //Add the h_l for this child l to the root clique's score, by first transforming into an index variant (easier storage) and
429 // then retrieving the stored score of the child using the separator substring index.
430 let separator_substring_index_version =
431 transform_substring_vector_to_index(&separator_substring);
432 score += best_scores[child_index as usize]
433 [separator_substring_index_version as usize][0]
434 .1;
435 }
436
437 //store temporarily highest score in scores
438 //This already allows for multiple highest scores
439 if !scores.is_empty() && is_better_fitness(score, highest_score) {
440 scores.clear();
441 }
442 if scores.is_empty() || is_better_or_equal_fitness(score, highest_score) {
443 //TODO: Here I could store k instead of the substring!
444 scores.push((possible_clique_substrings[c].clone(), score));
445 highest_score = score;
446 }
447 }
448
449 //store the highest score into h for that separator (i) and for these values (j)
450 for tuple in &scores {
451 debug!("Best clique0: {:?} with score {:?}", tuple.0, tuple.1);
452 }
453
454 //Now we want to construct the global optima string with the score we just calculated.
455 // To construct the global optima, we just need to traverse the tree again, now starting from the top.
456 //possible TODO: Count the number of multiple maximizing instances so that we can make
457 // an estimate of the number of global optima. I can just use a high number, as the structure is quite small and won't take much space
458
459 let problem_size = (input_parameters.m - 1) * (input_parameters.k - input_parameters.o)
460 + input_parameters.k;
461
462 //initialize string that will store resulting global optimum string to zeroes
463 let mut glob_opt_strings = Vec::with_capacity(40);
464
465 //let mut glob_opt_string = vec![
466 // 0;
467 // ((input_parameters.M - 1) * (input_parameters.k - input_parameters.o)
468 // + input_parameters.k) as usize
469 //];
470 //Create vector for global optimum substring for that clique, insert C0 already.
471 //I couuuuld consider storing indices, but then I'd be constantly be translating these values from and to strings...
472 //Only allocate space for the cliques that have a child, as it is temporary storage
473 //let mut clique_opt_substrings =
474 //vec![Vec::new(); (std::cmp::max(start_indices[lowest_level as usize], 1)) as usize];
475 //clique_opt_substrings[0] = scores[0].0.clone();
476
477 //Just take the first tuple of all the choices as the global optimum, ignore other possible global optima for now.
478 //Set C0's global optimum substring values in the global optimum string
479 for clique_opt in &scores {
480 let mut new_glob_opt_string = vec![0; problem_size as usize];
481 for index_in_clique in 0..input_parameters.k as usize {
482 new_glob_opt_string[cliques[0][index_in_clique as usize] as usize] =
483 clique_opt.0[index_in_clique as usize];
484 }
485 glob_opt_strings.push(new_glob_opt_string);
486 }
487 //for index_in_clique in 0..input_parameters.k as usize {
488 // glob_opt_string[cliques[0][index_in_clique as usize] as usize] =
489 // scores[0].0[index_in_clique as usize];
490 //}
491
492 //Set level and start index to the first clique, as we're starting from the root and iterate to the end
493 start_index_current_level = 0;
494 current_level = 0;
495
496 //Calculate the end of the loop
497 let mut division = (input_parameters.m - 1) / input_parameters.b;
498 if (input_parameters.m - 1) % input_parameters.b > 0 {
499 division += 1;
500 }
501
502 //Go until latest node/clique with children
503 for i in 0..division {
504 if (current_level as usize) < (start_indices.len() - 1) {
505 //Increase the current level in the tree when the considered index is at the next level's start index
506 if i >= start_indices[(current_level + 1) as usize] {
507 current_level += 1;
508 start_index_current_level = start_indices[current_level as usize];
509 }
510
511 //Calculate the start_index for this clique's children
512 let start_index_children = start_indices[(current_level + 1) as usize]
513 + input_parameters.b * (i - start_index_current_level);
514
515 //Go over all its b children
516 for j in 0..input_parameters.b {
517 //Break if the index of the child to consider goes out of the M range
518 if (i * input_parameters.b) + j >= input_parameters.m - 1 {
519 break;
520 }
521
522 //Get current considered child's index
523 let current_child_index = start_index_children + j;
524
525 //For all current global optimum strings, either fill in the only maximizing instance for this separator instance,
526 // or clone the global optimum string x times, for the x maximizing instances of this separator instance.
527 let glob_opt_strings_length = glob_opt_strings.len();
528 let mut glob_opt_strings_marked_deletion =
529 Vec::with_capacity(glob_opt_strings_length);
530 for k in 0..glob_opt_strings_length {
531 let glob_opt_string = &mut glob_opt_strings[k];
532
533 //Construct child's separator values using the global string values and the stored indices of the separator.
534 let separator_substring = get_separator_substring_from_string(
535 &separators[current_child_index as usize],
536 glob_opt_string,
537 );
538
539 //Get index for that substring, to index into h
540 let separator_substring_index_version =
541 transform_substring_vector_to_index(&separator_substring);
542
543 //For each maximizing instance for the given separator instance, clone the global string and
544 // set the maximizing instance values. These maximizing instance values are retrieved from h
545 //Get best tuple for that child's separator values from h:
546 let c_without_s_substrings: Vec<&Vec<u32>> = (&best_scores
547 [current_child_index as usize]
548 [separator_substring_index_version as usize])
549 .iter()
550 .map(|tuple| &tuple.0)
551 .collect();
552
553 //Remove the item currently in consideration? (check if loops don't break then)
554 // Then clone it a number of times equal to the number of maximizing instances for this separator,
555 // and assign the bits from the maximizing instances.
556
557 //If there is just one maximizing instance for this seperator,
558 // then just insert the values for this instance into the current global optimum string
559 let number_maximizing_instances = c_without_s_substrings.len();
560 if number_maximizing_instances == 1 {
561 //Insert Ci/Si values into global optimum string
562 for index in 0..(input_parameters.k - input_parameters.o) {
563 glob_opt_string[cliques[current_child_index as usize]
564 [(index + input_parameters.o) as usize]
565 as usize] = c_without_s_substrings[0][index as usize];
566 }
567 } else {
568 //otherwise, clone the global optimum under consideration x times, where x is equal to the number of maximizing instances
569 // for this clique.
570
571 // make sure there are more than 0 maximizing instances
572 assert_ne!(
573 number_maximizing_instances, 0,
574 "there are 0 maximizing instances, which is impossible"
575 );
576
577 //direct naar glob_opt_strings pushen ipv eerst naar nieuwe array? -> Dit kan niet, doordat we nog een mutable borrow in scope hebben
578 //Clone the global optimum string under consideration and add to vector
579 let mut new_glob_opt_strings =
580 Vec::with_capacity(number_maximizing_instances);
581 for _l in 0..number_maximizing_instances {
582 new_glob_opt_strings.push(glob_opt_string.clone());
583 }
584
585 //For each maximizing instance, write the maximizing values to one of the cloned global optimum strings
586 for (num, maximizing_instance) in
587 c_without_s_substrings.iter().enumerate()
588 {
589 for index in 0..(input_parameters.k - input_parameters.o) {
590 new_glob_opt_strings[num][cliques[current_child_index as usize]
591 [(index + input_parameters.o) as usize]
592 as usize] = maximizing_instance[index as usize];
593 }
594 }
595
596 //Append the newly created global optimum strings to the global optimum strings vector,
597 // and mark the global optimum string currenly under consideration as to be deleted.
598 glob_opt_strings.append(&mut new_glob_opt_strings);
599 glob_opt_strings_marked_deletion.push(k);
600 }
601 }
602
603 //Remove the global optimum strings that were marked as to be deleted,
604 // in reversed order, as we want to make sure that the indices correctly point to the strings to be deleted
605 for marked_index in glob_opt_strings_marked_deletion.into_iter().rev() {
606 glob_opt_strings.remove(marked_index);
607 }
608 }
609 }
610 }
611
612 for i in 1..input_parameters.m {
613 for j in 0..(1 << input_parameters.o) {
614 debug!(
615 "Best score for clique {:?} for index {:?}: {:?} with score {:?}",
616 i,
617 j,
618 best_scores[i as usize][j as usize][0].0,
619 best_scores[i as usize][j as usize][0].1
620 );
621 }
622 }
623
624 let glob_opt_score = scores.swap_remove(0).1;
625 for glob_opt_string in &glob_opt_strings {
626 debug!(
627 "Glob opt string: {:?} and glob opt score: {:?}",
628 glob_opt_string, glob_opt_score
629 );
630 }
631 //Return the global optimum string and its fitness
632 glob_opt_strings
633 .into_iter()
634 .map(|glob_opt_string| (glob_opt_string, glob_opt_score))
635 .collect()
636 }
637
638 ///Construct the clique tree, using the input paramters and the codomain values. It returns a tuple (cliques, separators)
639 pub fn construct(input_parameters: &InputParameters, rng: &mut ChaChaRng) -> (Vec<Vec<u32>>, Vec<Vec<u32>>) {
640 let mut cliques: Vec<Vec<u32>> = Vec::with_capacity(input_parameters.m as usize);
641 let mut separators: Vec<Vec<u32>> = Vec::with_capacity(input_parameters.m as usize);
642
643 //Shuffle the variable indices, so that we don't get an easy tree.
644 let mut indices: Vec<u32> = (0..((input_parameters.m - 1)
645 * (input_parameters.k - input_parameters.o)
646 + input_parameters.k))
647 .collect();
648
649 indices.shuffle(rng);
650 debug!("{:?}", indices);
651
652 //Initialize clique 0, C0, by just taking the first k variable indices from the list.
653 let mut clique0: Vec<u32> = Vec::with_capacity(input_parameters.k as usize);
654 for i in 0..input_parameters.k {
655 clique0.push(indices[i as usize]);
656 }
657
658 //Add C0 to the cliques list and add separator 0, S0, to the separator list. (S0 is dummy)
659 cliques.push(clique0);
660 separators.push(Vec::new()); //filler, there is no separator 0!
661
662 //We set the number of currenlty constructed cliques to 1
663 let mut count = 1;
664
665 let b = if input_parameters.o == 0 {
666 1
667 } else {
668 input_parameters.b
669 };
670
671 //We calculate the index of the first clique that should not get any children.
672 let mut division = (input_parameters.m - 1) / b;
673 //If a clique should construct at least one child, it is considered as well.
674 if (input_parameters.m - 1) % b > 0 {
675 division += 1;
676 }
677
678 //Dit kan nog geoptimaliseerd worden door die separator_count en variables_to_add ertussenuit te halen,
679 // want daarna plaats ik het toch samen in een nieuwe vector...
680 //Go over all cliques that will become a parent
681 for i in 0..division {
682 //Create b children, if possible
683 for j in 0..b {
684 //If b would be too many children, break as soon as we would create too many chldren.
685 if (i * b) + j >= input_parameters.m - 1 {
686 break;
687 }
688
689 //Choose o random variable indices from Ci
690 //Here, we first clone Ci, shuffle it, and push the first o variable indices to the separator.
691 let mut clique_copy = cliques[i as usize].clone();
692 clique_copy.shuffle(rng);
693
694 let mut new_separator: Vec<u32> = Vec::with_capacity(input_parameters.o as usize);
695 for k in 0..input_parameters.o {
696 new_separator.push(clique_copy[k as usize]);
697 }
698
699 //Set the start index of the k variable indices we will copy from the variable index list
700 let start_index =
701 (count - 1) * (input_parameters.k - input_parameters.o) + input_parameters.k;
702 //Copy the variable indices into a list
703 let mut variables_to_add: Vec<u32> =
704 Vec::with_capacity((input_parameters.k - input_parameters.o) as usize);
705 for k in 0..(input_parameters.k - input_parameters.o) {
706 variables_to_add.push(indices[(start_index + k) as usize]);
707 }
708
709 //Construct new clique for the child, by taking the o variables indices from the separator and
710 // (k - o) variable indices from the variable index list
711 let mut new_clique: Vec<u32> = Vec::with_capacity(input_parameters.o as usize);
712 for k in 0..input_parameters.o {
713 new_clique.push(new_separator[k as usize]);
714 }
715
716 for k in 0..(input_parameters.k - input_parameters.o) {
717 new_clique.push(variables_to_add[k as usize]);
718 }
719
720 //Add the new clique and separator to the clique and separator list, increase the count of constructed cliques.
721 cliques.push(new_clique);
722 separators.push(new_separator);
723 count += 1;
724 }
725 }
726
727 debug!("{:?}", cliques);
728 (cliques, separators)
729 }
730
731 ///Calculate the fitness of a passed solution using the knowledge that only one bit will be flipped,
732 /// and given that the solution has **not** been mutated at the given index yet
733 pub fn calculate_fitness_delta(
734 &self,
735 current_solutionfit: &SolutionFit,
736 number_evaluations: &mut u32,
737 index_mutation: u32,
738 ) -> f64 {
739 //First set the fitness to the current fitness
740 let mut fitness = current_solutionfit.fitness;
741
742 //Then loop over all the cliques
743 for clique_index in 0..self.cliques.len() {
744 let clique = &self.cliques[clique_index];
745 if clique.contains(&index_mutation) {
746 //And for each clique calculate the solution substring for this clique, as an index into an array of these substrings.
747 let mut clique_substring_as_index = 0;
748 //Create variable to conveniently store reference to the current clique in.
749 let clique = &self.cliques[clique_index];
750
751 //We will store the index in the clique of the bit that will be flipped
752 let mut clique_mutation_index = 0;
753
754 //Go over each variable index in the clique and for each one, take the bit value from the solution string and add it to the clique substring.
755 for j in (0..clique.len()).rev() {
756 //If the solution index of the considered index is equal to the index of the mutated bit, we store the index (in this clique) for future use.
757 if clique[j] == index_mutation {
758 clique_mutation_index = j;
759 }
760
761 //As we would otherwise do, add all the bits from the solution to the clique's subsolution, to be evaluated hereafter
762 clique_substring_as_index +=
763 current_solutionfit.solution[clique[j] as usize] << (clique.len() - j - 1);
764 }
765
766 //Substract the fitness contribution of this clique, as this has been previously added to get to the current fitness.
767 fitness -= self.codomain_values[clique_index][clique_substring_as_index as usize];
768
769 //Now set the bit in the clique's subsolution to the value it would be after mutation.
770 // It looks a bit involved, as we use u32 values.
771 if current_solutionfit.solution[clique[clique_mutation_index] as usize] == 0 {
772 clique_substring_as_index += 1 << (clique.len() - clique_mutation_index - 1);
773 } else {
774 clique_substring_as_index -= 1 << (clique.len() - clique_mutation_index - 1);
775 }
776
777 //Add the fitness contribution of this clique, taking into account the mutation.
778 fitness += self.codomain_values[clique_index][clique_substring_as_index as usize];
779
780 //Now we subtracted the old codomain value of this clique and have added the new value.
781 }
782 }
783
784 *number_evaluations += 1;
785
786 fitness
787 }
788
789 ///Calculate the fitnesss of a passed solution
790 pub fn calculate_fitness(&self, solution: &[u32], number_evaluations: &mut u32) -> f64 {
791 //First set the fitness to 0.0
792 let mut fitness = 0.0;
793
794 //Then loop over all the cliques
795 for clique_index in 0..self.cliques.len() {
796 //And for each clique calculate the solution substring for this clique, as an index into an array of these substrings.
797 let mut clique_substring_as_index = 0;
798 //Create variable to conveniently store reference to the current clique in.
799 let clique = &self.cliques[clique_index];
800 //Go over each variable index in the clique and for each one, take the bit value from the solution string and add it to the clique substring.
801 for j in (0..clique.len()).rev() {
802 clique_substring_as_index += solution[clique[j] as usize] << (clique.len() - j - 1);
803 }
804
805 //Add the fitness contribution of this clique
806 fitness += self.codomain_values[clique_index][clique_substring_as_index as usize];
807 }
808
809 *number_evaluations += 1;
810
811 fitness
812 }
813
814 pub fn is_global_optimum(&self, solution_fit: &SolutionFit) -> bool {
815 // if solution_fit.fitness != self.glob_optima_score
816 // && (self.glob_optima_score - solution_fit.fitness).abs() < 0.0000000001
817 // && (self.glob_optima_score - solution_fit.fitness).abs() >= FITNESS_EPSILON {
818 // println!("difference in fitness with global optimum was: {}", (self.glob_optima_score - solution_fit.fitness).abs() );
819 // panic!("global optimum found, but my current accepted range is too small: ");
820 // }
821 solution_fit.fitness == self.glob_optima_score
822 || ((self.glob_optima_score - solution_fit.fitness).abs() < FITNESS_EPSILON
823 && self.glob_optima_strings.contains(&solution_fit.solution))
824 }
825}
826
827pub fn is_better_solutionfit(solutionfit1: &SolutionFit, solutionfit2: &SolutionFit) -> bool {
828 solutionfit1.fitness > solutionfit2.fitness
829 && (solutionfit1.fitness - solutionfit2.fitness).abs() >= FITNESS_EPSILON
830}
831
832pub fn is_worse_solutionfit(solutionfit1: &SolutionFit, solutionfit2: &SolutionFit) -> bool {
833 solutionfit1.fitness < solutionfit2.fitness
834 && (solutionfit1.fitness - solutionfit2.fitness).abs() >= FITNESS_EPSILON
835}
836
837pub fn is_better_or_equal_solutionfit(
838 solutionfit1: &SolutionFit,
839 solutionfit2: &SolutionFit,
840) -> bool {
841 solutionfit1.fitness > solutionfit2.fitness || is_equal_solutionfit(solutionfit1, solutionfit2)
842}
843
844pub fn is_equal_solutionfit(solutionfit1: &SolutionFit, solutionfit2: &SolutionFit) -> bool {
845 solutionfit1.fitness == solutionfit2.fitness
846 || (solutionfit1.fitness - solutionfit2.fitness).abs() < FITNESS_EPSILON
847 && solutionfit1.solution == solutionfit2.solution
848}
849
850pub fn is_better_fitness(fitness1: f64, fitness2: f64) -> bool {
851 fitness1 > fitness2 && (fitness1 - fitness2).abs() >= FITNESS_EPSILON
852}
853
854pub fn is_worse_fitness(fitness1: f64, fitness2: f64) -> bool {
855 fitness1 < fitness2 && (fitness1 - fitness2).abs() >= FITNESS_EPSILON
856}
857
858pub fn is_better_or_equal_fitness(fitness1: f64, fitness2: f64) -> bool {
859 fitness1 > fitness2 || is_equal_fitness(fitness1, fitness2)
860}
861
862pub fn is_equal_fitness(fitness1: f64, fitness2: f64) -> bool {
863 (fitness1 - fitness2).abs() < FITNESS_EPSILON
864}
865
866///Get an iterator for all possible substrings of certain length
867pub fn get_possible_substrings_iter(length: u32) -> impl Iterator<Item = Vec<u32>> {
868 assert!(length < 32);
869
870 (0..(1 << length)).map(move |substring_as_index| {
871 //bit shift to get vector representation of solution from bit string version
872 (0..length)
873 .rev()
874 .map(|i| (substring_as_index >> i) & 1)
875 .collect()
876 })
877}
878
879/// Get all possible (sub)strings for a given length (bits)
880pub fn get_possible_substrings(length: u32) -> Vec<Vec<u32>> {
881 assert!(length < 32);
882
883 (0..(1 << length))
884 .map(|substring_as_index| {
885 (0..length)
886 .rev()
887 .map(|i| (substring_as_index >> i) & 1)
888 .collect()
889 })
890 .collect()
891}
892
893/// Get the separator substring for the child, by taking the string values from the parent clique
894/// for the variable indices both in the parent clique and in the separator
895fn get_separator_substring_from_string(separator: &[u32], glob_string: &[u32]) -> Vec<u32> {
896 //Get the substring values from the parent clique for the variable indices both in the parent clique and in the separator
897 let mut separator_substring = Vec::with_capacity(separator.len());
898 //For every variable index in the child separator, find the index of that variable index in the parent clique,
899 // and copy that value into the separator substring.
900 for &index in separator {
901 separator_substring.push(glob_string[index as usize]);
902 }
903 separator_substring
904}
905
906/// Get the separator substring for the child, by taking the substring values from the parent clique
907/// for the variable indices both in the parent clique and in the separator
908fn get_child_separator_substring(
909 clique: &[u32],
910 separator: &[u32],
911 clique_substring: &[u32],
912) -> Vec<u32> {
913 //Get the substring values from the parent clique for the variable indices both in the parent clique and in the separator
914 let mut separator_substring = Vec::with_capacity(separator.len());
915 //For every variable index in the child separator, find the index of that variable index in the parent clique,
916 // and copy that value into the separator substring.
917 for &index in separator {
918 let found_index = clique
919 .iter()
920 .position(|&x| x == index)
921 .expect("index in separator not found in clique!");
922 separator_substring.push(clique_substring[found_index]);
923 }
924 separator_substring
925}
926
927///Transform the passed substring into an index(bit value) that would point to that substring
928pub fn transform_substring_vector_to_index(substring: &[u32]) -> u32 {
929 let mut sum = 0;
930 let mut current_bit_shift_amount = 0;
931 //Calculate bit value using the input bit string
932 for i in (0..substring.len()).rev() {
933 sum += substring[i as usize] << current_bit_shift_amount;
934 current_bit_shift_amount += 1;
935 }
936 sum
937}