Source code for gastop.utilities

"""utilities.py
This file is a part of GASTOp
Authors: Amlan Sinha, Cristian Lacey, Daniel Shaw, Paul Kaneelil, Rory Conlin, Susan Redmond
Licensed under GNU GPLv3.
This module houses the utilities functions.

"""

import numpy as np
import configobj
import json
import ast
import os
import imageio
import matplotlib.pyplot as plt
import shutil
import copy
from pathlib import Path

from gastop import Truss, ProgMon, encoders


[docs]def save_gif(progress_history, progress_fitness, progress_truss, animation_path, num_gens, config, gif_pause=0.5): """Saves progress history to gif Clears contents of folder specified then creates png of each generation of the evolution and then combines the png's into a gif. Accomplishes this by creating progress monitor instance and passing it the truss object stored in the progress history. Args: progress_history (dictionary of dictionaries): population statistics and best truss from each generation. progress_fitness (boolean): indicates whether to plot the fitness score. progress_truss (boolean): indicates whether to plot the current truss. animation_path (string): path to the file where the gif should be created. num_gens (integer): total number of generations config (dictionary of dictionaries): stores domain, loads, and fixtures gif_pause (float): pause between images in the gif Returns: Nothing """ # Delete old animation folder try: shutil.rmtree(animation_path) except: pass os.makedirs(animation_path) if progress_fitness or progress_truss: evolution = ProgMon(progress_fitness, progress_truss, num_gens, config['random_params']['domain'], config['evaluator_params']['boundary_conditions']['loads'], config['evaluator_params']['boundary_conditions']['fixtures']) images = [] for current_gen in range(num_gens): progress_truss = progress_history['Generation ' + str(current_gen+1)]['Best Truss'] evolution.progress_monitor(current_gen, progress_truss) fig = plt.gcf() fig.savefig(animation_path + '/truss_evo_iter' + str(current_gen+1) + '.png') images.append(imageio.imread( 'animation/truss_evo_iter' + str(current_gen+1) + '.png')) imageio.mimsave(animation_path + '/truss_evo_gif.gif', images, duration=gif_pause)
[docs]def beam_file_parser(properties_path): """Parses csv file of beam material properties Each line of the properties file denotes one type of beam, with a specified cross section and material properties. Property entries should be formatted as: beam #, material name, OD (m), ID (m), elastic_modulus (Pa), yield_strength (Pa), density (kg/m^3), poisson_ratio, cost ($) Args: properties_path (str): Path to the properties csv file, relative to the directory GASTOp is being executed from. Returns: properties_dict (dict): Dictionary of property values. Each entry is an ndarray of the keyed property of each beam. For example, properties_dict['dens'] is an ndarray of the density of each beam type. """ OD = np.loadtxt(properties_path, delimiter=',', skiprows=1, usecols=2) ID = np.loadtxt(properties_path, delimiter=',', skiprows=1, usecols=3) E = np.loadtxt(properties_path, delimiter=',', skiprows=1, usecols=4) YS = np.loadtxt(properties_path, delimiter=',', skiprows=1, usecols=5) dens = np.loadtxt(properties_path, delimiter=',', skiprows=1, usecols=6) nu = np.loadtxt(properties_path, delimiter=',', skiprows=1, usecols=7) cost = np.loadtxt(properties_path, delimiter=',', skiprows=1, usecols=8) G = E/(2*(1+nu)) A = np.pi/4*(OD**2 - ID**2) Iz = np.pi/64*(OD**4 - ID**4) Iy = np.pi/64*(OD**4 - ID**4) J = np.pi/32*(OD**4 - ID**4) properties_dict = {'elastic_modulus': E, 'yield_strength': YS, 'shear_modulus': G, 'poisson_ratio': nu, 'x_section_area': A, 'moment_inertia_z': Iz, 'moment_inertia_y': Iy, 'polar_moment_inertia': J, 'outer_diameter': OD, 'inner_diameter': ID, 'density': dens, 'cost': cost} return properties_dict
[docs]def init_file_parser(init_file_path): # Cristian """Parse init file for input parameters. Creates ConfigObj object, which reads input parameters as a nested dictionary of strings. The string are then converted to their correct types using the ConfigObj walk method and a transform function. Defaults are then set with if statements. Args: init_file_path (string): Path to the init file, relative to the directory GASTOp is being executed from. Returns: config (ConfigObj object): Nested dicitonary of input parameters. """ abs_path = Path(init_file_path).resolve() # Extract inputs from the file as strings, if path exists if abs_path.exists(): config = configobj.ConfigObj(str(abs_path)) else: raise IOError("No such path to init file.") def transform(section, key): """convert each string in config to associated type Args: section: section of the file key: key for dictionary Returns: Returns each string """ val = section[key] newval = val # Convert string to float or int try: newval = float(val) newval = int(val) except ValueError: pass # Convert string to True, False, None if val == 'True' or val == 'true' or val == 'yes': newval = True elif val == 'False' or val == 'false' or val == 'no': newval = False elif val == 'None' or val == 'none': newval = None # Convert string to numpy array try: a = ast.literal_eval(val) if isinstance(a, list): newval = np.array(a) except: pass section[key] = newval # Recursively walk through config object converting strings according to # transform function. config.walk(transform) # Parse 'properties' CSV file properties_path = abs_path.parent.joinpath( config['general']['properties_path']) properties_dict = beam_file_parser(properties_path) # ---------------------------Set Defaults--------------------------------- user_spec_nodes = config['general']['user_spec_nodes'] num_user_nodes = user_spec_nodes.shape[0] num_rand_nodes = config['general']['num_rand_nodes'] num_nodes = num_user_nodes + num_rand_nodes num_edges = config['general']['num_rand_edges'] num_matl = properties_dict['elastic_modulus'].shape[0] loads = config['general']['loads'] fixtures = config['general']['fixtures'] if loads.ndim < 3: loads = np.reshape(loads, (loads.shape + (1,))) if fixtures.ndim < 3: fixtures = np.reshape(fixtures, (fixtures.shape + (1,))) num_loads = loads.shape[2] fixtures = np.concatenate((fixtures, np.zeros( (num_rand_nodes, 6, num_loads))), axis=0) loads = np.concatenate((loads, np.zeros( (num_rand_nodes, 6, num_loads))), axis=0) domain = config['general']['domain'] # ga_params config['ga_params']['current_generation'] = 0 if config['ga_params']['save_filename_prefix']: config['ga_params']['config_save_name'] = config['ga_params']['save_filename_prefix'] + '_config.json' config['ga_params']['pop_save_name'] = config['ga_params']['save_filename_prefix'] + \ '_population.json' else: config['ga_params']['config_save_name'] = 'config.json' config['ga_params']['pop_save_name'] = 'population.json' if not config['ga_params']['save_frequency']: config['ga_params']['save_frequency'] = 0 # evaluator_params config['evaluator_params']['boundary_conditions'] = {} config['evaluator_params']['boundary_conditions']['loads'] = loads config['evaluator_params']['boundary_conditions']['fixtures'] = fixtures config['evaluator_params']['properties_dict'] = properties_dict # fitness params if config['fitness_params']['parameters']['critical_nodes'] is '': config['fitness_params']['parameters']['critical_nodes'] = np.array([]) # random params config['random_params']['num_rand_nodes'] = num_rand_nodes config['random_params']['num_rand_edges'] = num_edges config['random_params']['domain'] = domain config['random_params']['num_material_options'] = num_matl config['random_params']['user_spec_nodes'] = user_spec_nodes if not config['random_params']['rng_seed']: config['random_params']['rng_seed'] = 1729 # crossover params config['crossover_params']['user_spec_nodes'] = user_spec_nodes # mutator params config['mutator_params']['user_spec_nodes'] = user_spec_nodes config['mutator_params']['node_mutator_params']['boundaries'] = domain config['mutator_params']['node_mutator_params']['int_flag'] = False config['mutator_params']['edge_mutator_params']['boundaries'] = np.array( [[-1, -1], [num_nodes, num_nodes]]) config['mutator_params']['edge_mutator_params']['int_flag'] = True config['mutator_params']['property_mutator_params']['boundaries'] = np.array([ [0], [num_matl]]) config['mutator_params']['property_mutator_params']['int_flag'] = True # defaults or user override if not config['crossover_params']['node_crossover_method']: config['crossover_params']['node_crossover_method'] = 'uniform_crossover' if not config['crossover_params']['edge_crossover_method']: config['crossover_params']['edge_crossover_method'] = 'uniform_crossover' if not config['crossover_params']['property_crossover_method']: config['crossover_params']['property_crossover_method'] = 'uniform_crossover' if not config['mutator_params']['node_mutator_method']: config['mutator_params']['node_mutator_method'] = 'gaussian' config['mutator_params']['node_mutator_params']['std'] = .1 if not config['mutator_params']['edge_mutator_method']: config['mutator_params']['edge_mutator_method'] = 'pseudo_bit_flip' config['mutator_params']['edge_mutator_params']['proportions'] = 0.3 if not config['mutator_params']['property_mutator_method']: config['mutator_params']['property_mutator_method'] = 'pseudo_bit_flip' config['mutator_params']['property_mutator_params']['proportions'] = 0.3 if not config['selector_params']['method']: config['selector_params']['method'] = 'inverse_square_rank_probability' config['selector_params']['method_params'] = {} if not config['ga_params']['num_elite']: config['ga_params']['num_elite'] = int(np.ceil( .01*config['ga_params']['pop_size'])) if not config['ga_params']['percent_crossover']: config['ga_params']['percent_crossover'] = 0.4 if not config['ga_params']['percent_mutation']: config['ga_params']['percent_mutation'] = 0.4 if (config['ga_params']['percent_mutation'] + config['ga_params']['percent_crossover']) > 1: raise RuntimeError('percent_crossover + percent_mutation > 1') if not config['monitor_params']['progress_fitness']: # sfr config['monitor_params']['progress_fitness'] = False if not config['monitor_params']['progress_truss']: # sfr config['monitor_params']['progress_truss'] = False return config
[docs]def save_progress_history(progress_history, path_progress_history='progress_history.json'): '''Saves the population history (progress_history) to a JSON file. Args: progress_history (dict): History of each generation, including generation number, fittest truss, etc. path_progress_history (string): Path to save progress_history data file. If file doesn't exist, creates it. Returns: None ''' # Save progress_history data with open(path_progress_history, 'w') as f: progress_history_dumped = json.dumps( copy.deepcopy(progress_history), cls=encoders.PopulationEncoder) json.dump(progress_history_dumped, f)
[docs]def load_progress_history(path_progress_history='progress_history.json'): '''Loads the population history (progress_history) from a JSON file. Args: path_progress_history (string): Path to progress_history data file. Returns: progress_history (dict): History of each generation, including generation number, fittest truss, etc. ''' # Load progress_history data with open(path_progress_history, 'r') as f: progress_history_loaded = json.load(f) progress_history = json.loads( progress_history_loaded, object_hook=encoders.numpy_decoder) # Bundle truss dictionaries as Truss objects for gen in progress_history.keys(): progress_history[gen]['Best Truss'] = Truss( **progress_history[gen]['Best Truss']) return progress_history