Skip to content

Commit 618ff07

Browse files
committed
v0.0.18 Wrapped LateAcceptance
1 parent 0e36edd commit 618ff07

File tree

10 files changed

+285
-20
lines changed

10 files changed

+285
-20
lines changed

examples/object_oriented/nqueens/scripts/solve_nqueens.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from greyjack.Solver import Solver
1717
from greyjack.agents.TabuSearch import TabuSearch
1818
from greyjack.agents.GeneticAlgorithm import GeneticAlgorithm
19+
from greyjack.agents.LateAcceptance import LateAcceptance
1920

2021
if __name__ == "__main__":
2122

@@ -27,12 +28,15 @@
2728
termination_strategy = TimeSpentLimit(time_seconds_limit=60)
2829
#termination_strategy = ScoreNoImprovement(time_seconds_limit=15)
2930
#termination_strategy = ScoreLimit(score_to_compare=[0])
30-
agent = TabuSearch(neighbours_count=20, tabu_entity_rate=0.0,
31+
agent = TabuSearch(neighbours_count=128, tabu_entity_rate=0.0,
3132
mutation_rate_multiplier=None, move_probas=[0, 1, 0, 0, 0, 0],
3233
migration_frequency=1, termination_strategy=termination_strategy)
3334
"""agent = GeneticAlgorithm(population_size=128, crossover_probability=0.5, p_best_rate=0.05,
3435
tabu_entity_rate=0.0, mutation_rate_multiplier=1.0, move_probas=[0, 1, 0, 0, 0, 0],
3536
migration_rate=0.00001, migration_frequency=1, termination_strategy=termination_strategy)"""
37+
"""agent = LateAcceptance(late_acceptance_size=10, tabu_entity_rate=0.0,
38+
mutation_rate_multiplier=None, move_probas=[0, 1, 0, 0, 0, 0],
39+
migration_frequency=1000, termination_strategy=termination_strategy)"""
3640

3741
solver = Solver(domain_builder, cotwin_builder, agent,
3842
n_jobs=10, parallelization_backend="processing", #processing, threading

greyjack/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

greyjack/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "greyjack"
3-
version = "0.0.17"
3+
version = "0.0.18"
44
edition = "2021"
55

66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

greyjack/greyjack/Solver.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
from greyjack.agents.base.GJSolution import GJSolution
77
from greyjack.agents.base.individuals.Individual import Individual
8+
# TODO: create comparing with global top mechanism like in Rust version (by Queue/SimpleQueue for Linux, ZMQ PUB/SUB for other platforms)
9+
#from greyjack.agents.LateAcceptance import LateAcceptance
810
from pathos.multiprocessing import ProcessPool
911
from pathos.threading import ThreadPool
1012
import multiprocessing
@@ -112,11 +114,10 @@ def solve(self):
112114

113115
agents = self._setup_agents()
114116
agents_process_pool = self._run_jobs(agents)
115-
#self._subscribe_for_new_solutions()
116117

117118
start_time = time.perf_counter()
118119
steps_count = 0
119-
current_best_candidate = None
120+
global_top_individual = None
120121
while True:
121122

122123
if self.is_windows:
@@ -128,25 +129,25 @@ def solve(self):
128129
agent_status = agent_publication["status"]
129130
local_step = agent_publication["step"]
130131
score_variant = agent_publication["score_variant"]
131-
solution_candidate = agent_publication["candidate"]
132-
solution_candidate = Individual.get_related_individual_type_by_value(score_variant).from_list(solution_candidate)
132+
received_individual = agent_publication["candidate"]
133+
received_individual = Individual.get_related_individual_type_by_value(score_variant).from_list(received_individual)
133134

134135
new_best_flag = False
135-
if current_best_candidate is None:
136-
current_best_candidate = solution_candidate
137-
elif solution_candidate < current_best_candidate:
138-
current_best_candidate = solution_candidate
136+
if global_top_individual is None:
137+
global_top_individual = received_individual
138+
elif received_individual < global_top_individual:
139+
global_top_individual = received_individual
139140
new_best_flag = True
140141
total_time = time.perf_counter() - start_time
141142
steps_count += 1
142143
new_best_string = "New best score!" if new_best_flag else ""
143144
if self.logging_level == "trace":
144-
self.logger.info(f"Solutions received: {steps_count} Best score: {current_best_candidate.score}, Solving time: {total_time:.6f}, {new_best_string}, Current (agent: {agent_id}, status: {agent_status}, local_step: {local_step}): {solution_candidate.score}")
145+
self.logger.info(f"Solutions received: {steps_count} Best score: {global_top_individual.score}, Solving time: {total_time:.6f}, {new_best_string}, Current (agent: {agent_id}, status: {agent_status}, local_step: {local_step}): {received_individual.score}")
145146
elif self.logging_level == "info":
146-
self.logger.info(f"Solutions received: {steps_count} Best score: {current_best_candidate.score}, Solving time: {total_time:.6f} {new_best_string}")
147+
self.logger.info(f"Solutions received: {steps_count} Best score: {global_top_individual.score}, Solving time: {total_time:.6f} {new_best_string}")
147148

148149
if len(self.observers) >= 1:
149-
self._notify_observers(current_best_candidate)
150+
self._notify_observers(global_top_individual)
150151

151152
self.agent_statuses[agent_id] = agent_status
152153
someone_alive = False
@@ -160,15 +161,11 @@ def solve(self):
160161
agents_process_pool.terminate()
161162
agents_process_pool.close()
162163
agents_process_pool.join()
163-
164-
#atexit.register(agents_process_pool.close)
165164
del agents_process_pool
166165
gc.collect()
167166
break
168167

169-
#current_best_candidate = self._build_gjsolution_from_individual(current_best_candidate)
170-
171-
return current_best_candidate
168+
return global_top_individual
172169

173170
def _run_jobs(self, agents):
174171
def run_agent_solving(agent):
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
2+
from greyjack.agents.base.Agent import Agent
3+
from greyjack.agents.metaheuristic_bases.LateAcceptanceBase import LateAcceptanceBase
4+
from greyjack.score_calculation.score_requesters.OOPScoreRequester import OOPScoreRequester
5+
6+
class LateAcceptance(Agent):
7+
def __init__(
8+
self,
9+
late_acceptance_size,
10+
tabu_entity_rate,
11+
mutation_rate_multiplier=None,
12+
move_probas=None,
13+
migration_frequency=None,
14+
termination_strategy=None,
15+
):
16+
17+
super().__init__(1.0, migration_frequency, termination_strategy)
18+
19+
self.population_size = 1
20+
self.late_acceptance_size = late_acceptance_size
21+
self.tabu_entity_rate = tabu_entity_rate
22+
self.mutation_rate_multiplier = mutation_rate_multiplier
23+
self.move_probas = move_probas
24+
25+
def _build_metaheuristic_base(self):
26+
self.score_requester = OOPScoreRequester(self.cotwin)
27+
semantic_groups_dict = self.score_requester.variables_manager.semantic_groups_map.copy()
28+
discrete_ids = self.score_requester.variables_manager.discrete_ids.copy()
29+
30+
self.metaheuristic_base = LateAcceptanceBase.new(
31+
self.cotwin.score_calculator.score_variant,
32+
self.score_requester.variables_manager,
33+
self.late_acceptance_size,
34+
self.tabu_entity_rate,
35+
semantic_groups_dict,
36+
self.mutation_rate_multiplier,
37+
self.move_probas.copy() if self.move_probas else None,
38+
discrete_ids,
39+
)
40+
41+
return self
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
from greyjack.greyjack import LateAcceptanceSimple, LateAcceptanceHardSoft, LateAcceptanceHardMediumSoft
3+
from greyjack.score_calculation.scores.ScoreVariants import ScoreVariants
4+
5+
class LateAcceptanceBase:
6+
def new(score_variant, variables_manager_py,
7+
late_acceptance_size, tabu_entity_rate, semantic_groups_map, mutation_rate_multiplier=None, move_probas=None, discrete_ids=None):
8+
if score_variant == ScoreVariants.SimpleScore:
9+
return LateAcceptanceSimple(variables_manager_py, late_acceptance_size, tabu_entity_rate,
10+
semantic_groups_map, mutation_rate_multiplier, move_probas, discrete_ids)
11+
if score_variant == ScoreVariants.HardSoftScore:
12+
return LateAcceptanceHardSoft(variables_manager_py, late_acceptance_size, tabu_entity_rate,
13+
semantic_groups_map, mutation_rate_multiplier, move_probas, discrete_ids)
14+
if score_variant == ScoreVariants.HardMediumSoftScore:
15+
return LateAcceptanceHardMediumSoft(variables_manager_py, late_acceptance_size, tabu_entity_rate,
16+
semantic_groups_map, mutation_rate_multiplier, move_probas, discrete_ids)
17+
18+
raise Exception("score_variant unrecognized")

greyjack/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ features = ["pyo3/extension-module"]
77

88
[project]
99
name = "greyjack"
10-
version = "0.0.17"
10+
version = "0.0.18"
1111
requires-python = ">=3.9"
1212
dependencies = [
1313
"dill",
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
2+
3+
#[macro_export]
4+
macro_rules! build_concrete_late_acceptance_base {
5+
6+
($me_base_name: ident, $individual_variant: ident, $score_type: ty) => {
7+
#[pyclass]
8+
pub struct $me_base_name {
9+
10+
pub late_acceptance_size: usize,
11+
pub late_scores: VecDeque<$score_type>,
12+
pub tabu_entity_rate: f64,
13+
14+
pub metaheuristic_kind: String,
15+
pub metaheuristic_name: String,
16+
17+
pub group_mutation_rates_map: HashMap<String, f64>,
18+
pub discrete_ids: Option<Vec<usize>>,
19+
pub mover: Mover,
20+
pub variables_manager: VariablesManager,
21+
}
22+
23+
#[pymethods]
24+
impl $me_base_name {
25+
26+
#[new]
27+
#[pyo3(signature = (variables_manager_py, late_acceptance_size, tabu_entity_rate, semantic_groups_map, mutation_rate_multiplier=None, move_probas=None, discrete_ids=None))]
28+
pub fn new(
29+
variables_manager_py: VariablesManagerPy,
30+
late_acceptance_size: usize,
31+
tabu_entity_rate: f64,
32+
semantic_groups_map: HashMap<String, Vec<usize>>,
33+
mutation_rate_multiplier: Option<f64>,
34+
move_probas: Option<Vec<f64>>,
35+
discrete_ids: Option<Vec<usize>>,
36+
) -> Self {
37+
38+
let current_mutation_rate_multiplier;
39+
match mutation_rate_multiplier {
40+
Some(x) => current_mutation_rate_multiplier = mutation_rate_multiplier.unwrap(),
41+
None => current_mutation_rate_multiplier = 0.0,
42+
}
43+
let mut group_mutation_rates_map: HashMap<String, f64> = HashMap::new();
44+
for group_name in semantic_groups_map.keys() {
45+
let group_size = semantic_groups_map[group_name].len();
46+
let current_group_mutation_rate = current_mutation_rate_multiplier * (1.0 / (group_size as f64));
47+
group_mutation_rates_map.insert(group_name.clone(), current_group_mutation_rate);
48+
}
49+
50+
Self {
51+
late_acceptance_size: late_acceptance_size,
52+
tabu_entity_rate: tabu_entity_rate,
53+
late_scores: VecDeque::new(),
54+
55+
56+
metaheuristic_kind: "LocalSearch".to_string(),
57+
metaheuristic_name: "LateAcceptance".to_string(),
58+
59+
group_mutation_rates_map: group_mutation_rates_map.clone(),
60+
discrete_ids: discrete_ids.clone(),
61+
mover: Mover::new(tabu_entity_rate, HashMap::new(), HashMap::new(), HashMap::new(), group_mutation_rates_map.clone(), move_probas),
62+
variables_manager: VariablesManager::new(variables_manager_py.variables_vec.clone()),
63+
}
64+
}
65+
66+
fn sample_candidates_plain(
67+
&mut self,
68+
population: Vec<$individual_variant>,
69+
current_top_individual: $individual_variant,
70+
) -> Vec<Vec<f64>> {
71+
72+
if self.mover.tabu_entity_size_map.len() == 0 {
73+
let semantic_groups_map = self.variables_manager.semantic_groups_map.clone();
74+
for (group_name, group_ids) in semantic_groups_map {
75+
self.mover.tabu_ids_sets_map.insert(group_name.clone(), HashSet::new());
76+
self.mover.tabu_entity_size_map.insert(group_name.clone(), max((self.tabu_entity_rate * (group_ids.len() as f64)).ceil() as usize, 1));
77+
self.mover.tabu_ids_vecdeque_map.insert(group_name.clone(), VecDeque::new());
78+
}
79+
}
80+
81+
let mut candidate = population[0].variable_values.clone();
82+
let (changed_candidate, changed_columns, candidate_deltas) = self.mover.do_move(&mut candidate, &self.variables_manager, false);
83+
candidate = changed_candidate.unwrap();
84+
self.variables_manager.fix_variables(&mut candidate, changed_columns);
85+
let candidate = vec![candidate; 1];
86+
87+
return candidate;
88+
89+
}
90+
91+
fn sample_candidates_incremental(
92+
&mut self,
93+
population: Vec<$individual_variant>,
94+
current_top_individual: $individual_variant,
95+
) -> (Vec<f64>, Vec<Vec<(usize, f64)>>) {
96+
97+
if self.mover.tabu_entity_size_map.len() == 0 {
98+
let semantic_groups_map = self.variables_manager.semantic_groups_map.clone();
99+
for (group_name, group_ids) in semantic_groups_map {
100+
self.mover.tabu_ids_sets_map.insert(group_name.clone(), HashSet::new());
101+
self.mover.tabu_entity_size_map.insert(group_name.clone(), max((self.tabu_entity_rate * (group_ids.len() as f64)).ceil() as usize, 1));
102+
self.mover.tabu_ids_vecdeque_map.insert(group_name.clone(), VecDeque::new());
103+
}
104+
}
105+
106+
let mut candidate = population[0].variable_values.clone();
107+
let (_, changed_columns, candidate_deltas) = self.mover.do_move(&mut candidate, &self.variables_manager, true);
108+
let mut candidate_deltas = candidate_deltas.unwrap();
109+
self.variables_manager.fix_deltas(&mut candidate_deltas, changed_columns.clone());
110+
let changed_columns = changed_columns.unwrap();
111+
let candidate_deltas: Vec<(usize, f64)> = changed_columns.iter().zip(candidate_deltas.iter()).map(|(col_id, delta_value)| (*col_id, *delta_value)).collect();
112+
let deltas = vec![candidate_deltas; 1];
113+
114+
return (candidate, deltas);
115+
}
116+
117+
fn build_updated_population(
118+
&mut self,
119+
current_population: Vec<$individual_variant>,
120+
candidates: Vec<$individual_variant>,
121+
) -> Vec<$individual_variant> {
122+
123+
let candidate_to_compare_score;
124+
if self.late_scores.len() == 0 {
125+
candidate_to_compare_score = current_population[0].score.clone();
126+
} else {
127+
candidate_to_compare_score = self.late_scores.back().unwrap().clone();
128+
}
129+
130+
let new_population;
131+
let candidate_score = candidates[0].score.clone();
132+
if (candidate_score <= candidate_to_compare_score) || (candidate_score <= current_population[0].score) {
133+
let best_candidate = candidates[0].clone();
134+
new_population = vec![best_candidate; 1];
135+
self.late_scores.push_front(candidate_score);
136+
if self.late_scores.len() > self.late_acceptance_size {
137+
self.late_scores.pop_back();
138+
}
139+
} else {
140+
new_population = current_population.clone();
141+
}
142+
143+
return new_population;
144+
}
145+
146+
fn build_updated_population_incremental(
147+
&mut self,
148+
current_population: Vec<$individual_variant>,
149+
sample: Vec<f64>,
150+
deltas: Vec<Vec<(usize, f64)>>,
151+
scores: Vec<$score_type>,
152+
) -> Vec<$individual_variant> {
153+
154+
let late_native_score;
155+
if self.late_scores.len() == 0 {
156+
late_native_score = current_population[0].score.clone();
157+
} else {
158+
late_native_score = self.late_scores.back().unwrap().clone();
159+
}
160+
161+
let candidate_score = scores[0].clone();
162+
163+
let mut sample = sample;
164+
let new_population:Vec<$individual_variant>;
165+
if (candidate_score <= late_native_score) || (candidate_score <= current_population[0].score) {
166+
let best_deltas = &deltas[0];
167+
for (var_id, new_value) in best_deltas {
168+
sample[*var_id] = *new_value;
169+
}
170+
let best_candidate = $individual_variant::new(sample.clone(), candidate_score.clone());
171+
new_population = vec![best_candidate; 1];
172+
self.late_scores.push_front(candidate_score);
173+
if self.late_scores.len() > self.late_acceptance_size {
174+
self.late_scores.pop_back();
175+
}
176+
} else {
177+
new_population = current_population.clone();
178+
}
179+
180+
return new_population;
181+
}
182+
183+
#[getter]
184+
fn get_metaheuristic_kind(&self) -> String {
185+
self.metaheuristic_kind.clone()
186+
}
187+
188+
#[getter]
189+
fn get_metaheuristic_name(&self) -> String {
190+
self.metaheuristic_name.clone()
191+
}
192+
193+
}
194+
};
195+
}

greyjack/src/agents/base/metaheuristic_bases/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
pub mod mover;
44
pub mod concrete_tabu_search_base_macros;
55
pub mod concrete_genetic_algorithm_macros;
6+
pub mod concrete_late_acceptance_macros;
67

78
pub use mover::Mover;

0 commit comments

Comments
 (0)