Source code for Population.PopulationInitialization
1"""Population initialization factories and strategies.
2
3Provides protocols and concrete implementations for creating initial
4populations of :class:`~Individual.BitstreamIndividual.BitstreamIndividual`
5objects with configurable post-construction and randomization strategies.
6"""
7
8from pathlib import Path
9from random import Random
10from typing import Protocol
11from BitstreamEvolutionProtocols import Circuit, Individual, Population
12from Circuit.FileBasedCircuit import FileBasedCircuit
13from Directories import Directories
14from Individual.BitstreamIndividual import BitstreamIndividual
15from Logger import Logger
16from utilities import wipe_folder
17import os
18
19SEED_HARDWARE_FILEPATH = Path("data/seed-hardware.asc")
20
[docs]
21class GenerateInitialPopulations(Protocol):
22 "Somehow gets you an initial implementation."
23 def __call__(self) -> list[Population]: ...
24
[docs]
25class GenerateInitialPopulation(Protocol):
26 "The special case of GenerateInitialPopulations that constructs only a single population"
27 def __call__(self) -> Population: ...
28
[docs]
29class GenerateSinglePopulationWrapper:
30 """
31 A wrapper around GenerateInitialPopulation (singular) that follows
32 GenerateInitialPopulations (plural) which is the protocol used in Evolution
33 Returns a one-element list of the population created
34 Example Usage: Evolution(..., GenerateSinglePopulationWrapper(GenerateRandomPopulation(...)), ...)
35 """
36 def __init__(self, gen_func: GenerateInitialPopulation):
37 self.__gen_func = gen_func
38 def generate(self) -> list[Population]:
39 return [self.__gen_func()]
40
[docs]
41class PostConstructionStrategy(Protocol):
42 '''Consumes the incoming parameter'''
43 def run(self, individual: BitstreamIndividual) -> BitstreamIndividual: ...
44
[docs]
45class RandomizationStrategy(Protocol):
46 '''Consumes the incoming parameter'''
47 def randomize(self, individuals: list[BitstreamIndividual]) -> list[BitstreamIndividual]: ...
48
49class GenerateBitstreamPopulation:
50 # force everything to be passed by keyword
51 def __init__(self, *, sz: int, bitstream_sz: int,
52 post_construction_strategy: PostConstructionStrategy,
53 randomization_strategy: RandomizationStrategy,
54 mutation_prob: float, rand: Random):
55 self.__sz = sz
56 self.__bitstream_sz = bitstream_sz
57 self.__post_construction_strategy = post_construction_strategy
58 self.__randomization_strategy = randomization_strategy
59 self.__rand = rand
60
61 self.__mutation_prob = mutation_prob
62
63 def generate(self) -> Population:
64 """
65 Creates initial population based on the config.
66 1. Clears the files used to keep track of circuit
67 2. Uses appropriate initialization method specified by config.
68 3. Handles randomization until condition in config is met.
69 """
70
71 individuals = []
72
73 for index in range(1, self.__sz + 1):
74 individual = BitstreamIndividual(self.__bitstream_sz, self.__rand, self.__mutation_prob)
75 individual = self.__post_construction_strategy.run(individual)
76
77 individuals.append(individual)
78
79 circuits = self.__randomization_strategy.randomize(individuals)
80
81 return Population(circuits, None)
82
83# used for CLONE_SEED
84class NoPostConstructionStrategy:
85 def run(self, individual: BitstreamIndividual) -> BitstreamIndividual:
86 return individual
87
88# used for CLONE_SEED_MUTATE
89class MutateOncePostConstructionStrategy:
90 def run(self, individual: BitstreamIndividual) -> BitstreamIndividual:
91 individual.mutate()
92 return individual
93
94# used for RANDOM
95class RandomizeBitstreamPostConstructionStrategy:
96 def run(self, individual: BitstreamIndividual) -> BitstreamIndividual:
97 individual.randomize()
98 return individual
99
100class NoRandomizationStrategy:
101 def randomize(self, individuals: list[BitstreamIndividual]) -> list[BitstreamIndividual]:
102 return individuals
103
104class FileBasedCircuitFactory:
105 def __init__(self, *, sz: int, logger: Logger, directories: Directories, routing_type: str, accessed_columns: list[int]):
106 self.__logger = logger
107 self.__directories = directories
108 self.__routing_type = routing_type
109 self.__accessed_columns = accessed_columns
110
111 wipe_folder(self.__directories.asc_dir)
112 wipe_folder(self.__directories.bin_dir)
113 wipe_folder(self.__directories.data_dir)
114
115 self.__circuits = []
116 for i in range(sz):
117 circuit = FileBasedCircuit(
118 index=i,
119 filename="hardware" + str(i),
120 template=SEED_HARDWARE_FILEPATH,
121 logger=self.__logger,
122 directories=self.__directories,
123 routing_type=self.__routing_type,
124 accessed_columns=self.__accessed_columns
125 )
126 self.__circuits.append(circuit)
127
128 def create(self, populations: list[Population]) -> dict[Circuit, list[tuple[Population, Individual]]]:
129 res: dict[Circuit, list[tuple[Population, Individual]]] = {}
130 index = 0
131 for p in populations:
132 pop = p.population_list
133 for (individual, _) in pop:
134 circuit = self.__circuits[index]
135 bitstream: list[bool] = individual.get_bitstream() # type: ignore
136 circuit.set_bitstream(bitstream)
137 res[circuit] = [(p, individual)]
138 index += 1
139
140 return res
141
142# TODO: to add in randomize_until modes, we need a way to get circuit fitness/measurements AT initialization time