diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09dec4d --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*# +0/ +__pycache__/ +mgga_openmc.slurm +population_0.json diff --git a/README.md b/README.md index 9e80285..188f92c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,25 @@ -Readme -========== +Overview +======== + +This is a prototype for a Multiscale Grid Genetic Algorithm based on that of [this PhD thesis by Stephen Asbury](https://deepblue.lib.umich.edu/bitstream/handle/2027.42/91388/stasbury_1.pdf?sequence=1&isAllowed=y). + +The idea is to optimise OpenMC geometries for given quanities of interest at increasing resolution. + +Prerequisites +============= + - OpenMC: Please follow the installation instructions [here.](https://docs.openmc.org/en/stable/usersguide/install.html). + +Usage +===== +``` +python make_geom.py [-h] [--initialise] [--generate GENERATE] --input INPUT + +required: +--input INPUT JSON file containing input settings + +optional arguments: + -h, --help show this help message and exit + --initialise In this run mode, initialise the first generation population + --generate In this run mode, initialise the next generation population +``` -This is a prototype for a Multiscale Grid Genetic Algorithm based on that of `https://deepblue.lib.umich.edu/bitstream/handle/2027.42/91388/stasbury_1.pdf?sequence=1&isAllowed=y` the idea is to allow OpenMC geometries to be optimised in various resolutions hence the multi grid, \ No newline at end of file diff --git a/config.json b/config.json index 00d842f..976abf5 100644 --- a/config.json +++ b/config.json @@ -10,6 +10,6 @@ "chromosome_length": 100, "num_of_states": 6, "percentage_worst": 0.25, - "tornament_size": 5 + "tournament_size": 5 } } diff --git a/make_geom.py b/make_geom.py index 6799dd9..208ecef 100644 --- a/make_geom.py +++ b/make_geom.py @@ -1,5 +1,5 @@ """ -This script builds a simple geometry +This script builds a simple geometry """ import argparse @@ -18,8 +18,8 @@ def add_element(element,material,fraction,fraction_type): for nuclide in to_add: material.add_nuclide(nuclide[0],nuclide[1],percent_type=nuclide[2]) - -class openmc_problem(): + +class OpenmcProblem(): def __init__(self): self.materials = {} self.model = None @@ -102,16 +102,21 @@ def __build_tallies(self, flux_cells, tbr_cells): self.model.tallies = tallies # generate the fitness for the current generation and - # index + # index def generate_fitness(self, directory, sp_name = "statepoint.10.h5"): - sp = openmc.StatePoint(directory + '/' + sp_name) + try: + sp = openmc.StatePoint(directory + '/' + sp_name) + except OSError: + print("Could not open file ",sp_name, " in directory ", directory) + sys.exit(1) + tbr = sp.get_tally(name = 'tbr') cells = [] [cells.append(x.id) for x in self.cells] tbr_data = tbr.get_slice(scores=['(n,t)'],filters=[openmc.CellFilter], filter_bins = [tuple(cells)]) tbr_ave = tbr_data.mean - - # maximise tbr + + # maximise tbr fitness = sum(tbr_ave)[0][0] sp.close() return fitness @@ -125,10 +130,10 @@ def assign_genome(self, genome): self.cells[idx].fill = mat idx = idx + 1 - # given the genome build the region of geometry + # given the genome build the region of geometry # to optimise def build_geometry(self): - + univ = openmc.Universe(name='optimisation') cells = [] idx = 0 @@ -179,7 +184,7 @@ def __build_model(self): # build the region to optimise optimisation = self.build_geometry() - + # Create a cell filled with the lattice inside_boundary = -self.y_planes[-1] & +self.y_planes[0] & -self.x_planes[-1] & +self.x_planes[0] outside_boundary = +self.y_planes[-1] | -self.y_planes[0] | +self.x_planes[-1] | -self.x_planes[0] @@ -191,7 +196,7 @@ def __build_model(self): self.x_planes[0].boundary_type = 'vacuum' self.x_planes[-1].boundary_type = 'vacuum' - + self.y_planes[0].boundary_type = 'vacuum' self.y_planes[-1].boundary_type = 'vacuum' @@ -206,7 +211,7 @@ def build_slurm(generation): contents.append('#SBATCH -A UKAEA-AP001-CPU') contents.append('#SBATCH -p cclake') contents.append('#SBATCH --nodes=1') - contents.append('#SBATCH --ntasks=56') + contents.append('#SBATCH --ntasks=56') contents.append('#SBATCH --time=36:00:00') contents.append('#SBATCH --output=array_%A-%a.out') contents.append('#SBATCH --array=1-1000') @@ -224,8 +229,8 @@ def build_slurm(generation): contents.append('cd ..') with open('mgga_openmc.slurm','w') as f: - f.writelines(s + '\n' for s in contents) - + f.writelines(s + '\n' for s in contents) + def write_population(population, generation): data = {"population": [population]} json_string = json.dumps(data) @@ -241,47 +246,42 @@ def read_population(generation): data = json.loads(jsonContent) return data["population"][0] -if __name__ == '__main__': - +def main(): # Set up command-line arguments for generating/running the model parser = argparse.ArgumentParser() - parser.add_argument('--generate') - parser.add_argument('--run', action='store_true') - parser.add_argument('--initialise', action='store_true') - parser.add_argument('--input') + #parser.add_argument('--run', action='store_true') + parser.add_argument('--initialise', help="In this run mode, initialise the first generation population", action='store_true') + parser.add_argument('--generate', help="In this run mode, initialise the next generation population", action='store_true') + parser.add_argument('--input', help="JSON file containing input settings", required=True) + args = parser.parse_args() + + try: + with open(args.input) as f: + data = json.load(f) + f.close() + except FileNotFoundError: + print("Could not find file",args.input) + sys.exit(1) + # MGGA class - mgga = MGGA() - - if args.input: - f = open(args.input) - data = json.load(f) - mgga.seed = data["mgga_settings"]["seed"] - mgga.population_size = data["mgga_settings"]["population"] - mgga.generations = data["mgga_settings"]["generations"] - mgga.crossover_prob = data["mgga_settings"]["crossover_prob"] - mgga.mutation_prob = data["mgga_settings"]["mutation_prob"] - mgga.copy_prob = data["mgga_settings"]["copy_prob"] - mgga.chromosome_length = data["mgga_settings"]["chromosome_length"] - mgga.num_genes = data["mgga_settings"]["num_of_states"] - mgga.percentage_worst = data["mgga_settings"]["percentage_worst"] - mgga.tornament_size = data["mgga_settings"]["tornament_size"] - mgga.initialise() - f.close() - else: - print('No input specified, use --input') - sys.exit() + mgga = MGGA(data["mgga_settings"]) # initialise the first generation if args.initialise: + # Generate generic population set mgga.fill_population() - openmc_problem = openmc_problem() + + # For each member of population make an openmc model + openmc_problem = OpenmcProblem() openmc_problem.setup(10,[0,100],10,[0,200]) - for idx,i in enumerate(mgga.population): - openmc_problem.assign_genome(i) - openmc_problem.model.export_to_xml(directory='0/'+str(idx)) - build_slurm(0) - write_population(mgga.population, 0) + + generation_id=0 + for index, genome in enumerate(mgga.population): + openmc_problem.assign_genome(genome) + openmc_problem.model.export_to_xml(directory=str(generation_id)+'/'+str(index)) + build_slurm(generation_id) + write_population(mgga.population, generation_id) # need to write a filename dependent population file!! @@ -291,7 +291,7 @@ def read_population(generation): population = read_population(generation-1) mgga.population = population genomes = mgga.population - openmc_problem = openmc_problem() + openmc_problem = OpenmcProblem() openmc_problem.setup(10,[0,100],10,[0,200]) # loop over each of the genomes fitness = [] @@ -306,13 +306,16 @@ def read_population(generation): print('min fitness: ' + str(min(fitness))) mgga.sample_population() - for idx,i in enumerate(mgga.children): - openmc_problem.assign_genome(i) + for idx, genome in enumerate(mgga.children): + openmc_problem.assign_genome(genome) openmc_problem.model.export_to_xml(directory=str(generation) + '/'+str(idx)) write_population(mgga.children,generation) - + """ # translate to a higher resolution if args.translate: genomes = mgga.population -""" +""" + +if __name__ == '__main__': + main() diff --git a/mgga.py b/mgga.py index 25e2b96..8028892 100644 --- a/mgga.py +++ b/mgga.py @@ -5,18 +5,28 @@ import copy class MGGA(): - def __init__(self, seed = 1283123901319): - self.population_size = 0 - self.generations = 0 - self.num_genes = 0 - self.chromosome_length = 0 - self.copy_prob = 0.0 - self.crossover_prob = 0.0 - self.mutation_prob = 0.0 + def __init__(self, settings, seed = 1283123901319): + + # Override default seed if it is in settings + if "seed" in settings.keys(): + seed = settings["seed"] + self.seed = seed + random.seed(self.seed) + + # Settings + self.population_size = settings["population"] + self.generations = settings["generations"] + self.num_genes = settings["num_of_states"] + self.chromosome_length = settings["chromosome_length"] + self.copy_prob = settings["copy_prob"] + self.crossover_prob = settings["crossover_prob"] + self.mutation_prob = settings["mutation_prob"] + self.percentage_worst = settings["percentage_worst"] + self.tourament_size = settings["tournament_size"] + self.population = [] self.children = [] self.fitness = [] - self.seed = seed self.count = {} self.count["mutate"] = 0 self.count["crossover"] = 0 @@ -35,10 +45,6 @@ def __summary(self): print("clone happened: %f (%f%%)" % (self.count["clone"], per_clone)) print("mutate happened: %f (%f%%)" % (self.count["mutate"], per_mutate)) - def initialise(self): - if self.seed != 0: - random.seed(self.seed) - # purely random mutation def __mutate(self, chromosome): selected_gene = random.randint(0,self.chromosome_length-1) @@ -57,14 +63,14 @@ def __uniform_crossover(self,chromosome1): for i in range(self.chromosome_length): select_parent = random.randint(0,1) - + if select_parent == 0: child1[i] = chromosome1[i] child2[i] = chromosome2[i] else: child1[i] = chromosome2[i] child2[i] = chromosome1[i] - + return [child1,child2] # copy the chromosome @@ -76,7 +82,7 @@ def __random(self): for i in range(self.chromosome_length): chromosome[i] = random.randint(0,self.num_genes-1) return chromosome - + def __sample(self,chromosome): rand = random.random() if rand <= self.copy_prob: @@ -123,4 +129,4 @@ def sample_population(self): else: self.children.append(child) - self.__summary() \ No newline at end of file + self.__summary()