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}