def generate_paths(self): """ Initialise the file structure """ # Output subdirectory to structure results from APM analyses output_subdir = f"/{self.project_basename}" + "_{counter:03d}" # ===== Directories ===== self.paths = ProjectPaths(self.project_dir) self.paths.add_path(uid='d_aircraft', path=PATHS.DIR.AIRCRAFT, uid_groups='dir') self.paths.add_path(uid='d_airfoils', path=PATHS.DIR.AIRFOILS, uid_groups='dir') self.paths.add_path(uid='d_deformation', path=PATHS.DIR.DEFORMATION, uid_groups='dir') self.paths.add_path(uid='d_settings', path=PATHS.DIR.SETTINGS, uid_groups='dir') self.paths.add_path(uid='d_state', path=PATHS.DIR.STATE, uid_groups='dir') # Output directories self.paths.add_path(uid='d_plots_TOP', path=PATHS.DIR.PLOTS, uid_groups=('dir', 'tmp')) self.paths.add_path(uid='d_results_TOP', path=PATHS.DIR.RESULTS, uid_groups=('dir', 'tmp')) self.paths.add_path(uid='d_plots', path=PATHS.DIR.PLOTS+output_subdir, uid_groups=('dir', 'tmp')) self.paths.add_path(uid='d_results', path=PATHS.DIR.RESULTS+output_subdir, uid_groups=('dir', 'tmp')) # ===== Files ===== self.paths.add_path(uid='f_log', path=PATHS.FILES.LOG) self.paths.add_subpath(uid_parent='d_aircraft', uid='f_aircraft', path=f"{self.settings['aircraft']}") self.paths.add_subpath(uid_parent='d_deformation', uid='f_deformation', path=f"{self.settings['deformation']}") self.paths.add_subpath(uid_parent='d_settings', uid='f_settings', path=f"{self.project_basename}.json") self.paths.add_subpath(uid_parent='d_state', uid='f_state', path=f"{self.settings['state']}") # Output files self.paths.add_subpath(uid_parent='d_results', uid='f_results_global', path=f"global.json") # File namespaced with folder self.paths.add_subpath(uid_parent='d_results', uid='f_results_panelwise', path=f"panelwise.dat") # File namespaced with folder self.paths.add_subpath(uid_parent='d_results_TOP', uid='f_results_apm_global', path=f"{self.project_basename}_aeroperformance.json")
def test_calling_paths(): """ Test calling functions """ paths = ProjectPaths(ROOT) paths.add_path(uid=UID1, path=PATH1, uid_groups=UID_GROUP1) assert str(paths(UID1)).endswith(f'/{ROOT}/{PATH1}')
def test_counter_paths(): """ Test counter paths """ paths = ProjectPaths(ROOT) assert paths.counter == 0 # Setting invalid_conter_values = [1.2, '23', None] for invalid_conter_value in invalid_conter_values: with raises(ValueError): paths.counter = invalid_conter_value paths.counter = 1 # Value okay
def test_project_paths_basics(): """ Test basics """ paths = ProjectPaths(ROOT) assert str(paths.root.name) == ROOT assert paths.root == paths(ProjectPaths.UID_ROOT)
def __init__(self, root_dir=None, make_dirs=True): """ Aeroframe file structure Note: * If 'root_dir' is None, the root directory, we use 'os.getcwd()' Args: :root_dir: (str,path) Project root folder :make_dirs: (bool) If True, make directories of group 'init' """ if root_dir is None: root_dir = os.getcwd() # ============================== self.settings = None self.cfd_wrapper = None self.stru_wrapper = None # ============================== self.paths = ProjectPaths(root_dir) # ----- Directories ----- self.paths.add_path( uid='d_cfd', path=PATHS.DIRS.CFD, uid_groups=(PATHS.GROUPS.INIT, PATHS.GROUPS.CFD) ) self.paths.add_path( uid='d_structure', path=PATHS.DIRS.STRUCTURE, uid_groups=(PATHS.GROUPS.INIT, PATHS.GROUPS.STRUCTURE) ) # ----- Files ----- self.paths.add_path( uid='f_root_settings', path=PATHS.FILES.DEFAULT_SETTINGS ) if make_dirs: self.init_dirs()
def test_mk_rm_dirs(): """ Test making/remove directories """ paths = ProjectPaths(ROOT) paths.add_path(uid=UID1, path=PATH1, uid_groups=UID_GROUP1) paths.add_path(uid=UID2, path=PATH2, uid_groups=UID_GROUP1) paths.make_dirs_for_groups(UID_GROUP1, is_dir=True) assert os.path.exists(paths(UID1)) assert os.path.exists(paths(UID2)) paths.rm_dirs_for_groups(UID_GROUP1) assert not os.path.exists(paths(UID1)) assert not os.path.exists(paths(UID2))
class Settings: def __init__(self, root_dir=None, make_dirs=True): """ Aeroframe file structure Note: * If 'root_dir' is None, the root directory, we use 'os.getcwd()' Args: :root_dir: (str,path) Project root folder :make_dirs: (bool) If True, make directories of group 'init' """ if root_dir is None: root_dir = os.getcwd() # ============================== self.settings = None self.cfd_wrapper = None self.stru_wrapper = None # ============================== self.paths = ProjectPaths(root_dir) # ----- Directories ----- self.paths.add_path( uid='d_cfd', path=PATHS.DIRS.CFD, uid_groups=(PATHS.GROUPS.INIT, PATHS.GROUPS.CFD) ) self.paths.add_path( uid='d_structure', path=PATHS.DIRS.STRUCTURE, uid_groups=(PATHS.GROUPS.INIT, PATHS.GROUPS.STRUCTURE) ) # ----- Files ----- self.paths.add_path( uid='f_root_settings', path=PATHS.FILES.DEFAULT_SETTINGS ) if make_dirs: self.init_dirs() def init_dirs(self): """ Create directories belonging to 'init' group """ self.paths.make_dirs_for_groups(uid_groups='init', is_dir=True) def init_emtpy_settings_file(self, overwrite=False): """ Create a settings file with default values Args: :overwrite: (bool) If True, overwrite an existing settings file """ settings_file = self.paths("f_root_settings") if not overwrite and settings_file.exists(): raise FileExistsError(f"Path '{settings_file}' exists. Will not overwrite.") with open(settings_file, "w") as fp: settings_dict = get_default_value_dict(DEFAULT_SETTINGS_DICT) dump_pretty_json(settings_dict, fp) # ============================== # ============================== def load_root_settings(self): """ Load and return the root settings file Args: :aeroframe_files: file structure of aeroframe program """ with open(self.paths("f_root_settings"), "r") as fp: self.settings = json.load(fp) def get_wrappers(self): """ Load wrapper modules dynamically Args: :aeroframe_files: file structure of aeroframe program """ self.load_root_settings() self.cfd_lib = importlib.import_module(self.settings['cfd_model']['wrapper']) self.stru_lib = importlib.import_module(self.settings['structure_model']['wrapper']) root_path = root_path = self.paths('root') cfd_settings = self.settings['cfd_model'].get('exec_settings', {}) stru_settings = self.settings['structure_model'].get('exec_settings', {}) # Wrapper must share same instance of 'SharedData()' shared_data = SharedData() cfd_wrapper = self.cfd_lib.Wrapper(root_path, shared_data, cfd_settings) stru_wrapper = self.stru_lib.Wrapper(root_path, shared_data, stru_settings) return cfd_wrapper, stru_wrapper
class Settings: def __init__(self, settings_filename, project_dir, *, settings_dict=None, make_dirs=True, check_ac_file_type=True): """ Data structure with PyTornado execution settings Attributes: :settings_filename: Name of the settings file :project_dir: PyTornado project directory (where all data is expected) :settings_dict: Basic settings data as a dictionary :make_dirs: (bool) Flag for creation of project directories """ self.project_dir = Path(project_dir).resolve() self.project_basename = os.path.splitext(settings_filename)[0] self.settings = get_default_dict(template_dict=DEFAULT_SETTINGS) if settings_dict is not None: self.update_from_dict(settings_dict) self.paths = None self.generate_paths() # Aircraft format self.aircraft_is_cpacs = None if check_ac_file_type: self._check_aircraft_file_type() if make_dirs: self.paths.make_dirs_for_groups('dir') @property def state_is_cpacs(self): """ Flag indicating if state is to be read from CPACS """ if self.settings['state'].upper() == '__CPACS': if not self.aircraft_is_cpacs: raise RuntimeError("State cannot be read from CPACS if aircraft file is not CPACS") return True else: return False def generate_paths(self): """ Initialise the file structure """ # Output subdirectory to structure results from APM analyses output_subdir = f"/{self.project_basename}" + "_{counter:03d}" # ===== Directories ===== self.paths = ProjectPaths(self.project_dir) self.paths.add_path(uid='d_aircraft', path=PATHS.DIR.AIRCRAFT, uid_groups='dir') self.paths.add_path(uid='d_airfoils', path=PATHS.DIR.AIRFOILS, uid_groups='dir') self.paths.add_path(uid='d_deformation', path=PATHS.DIR.DEFORMATION, uid_groups='dir') self.paths.add_path(uid='d_settings', path=PATHS.DIR.SETTINGS, uid_groups='dir') self.paths.add_path(uid='d_state', path=PATHS.DIR.STATE, uid_groups='dir') # Output directories self.paths.add_path(uid='d_plots_TOP', path=PATHS.DIR.PLOTS, uid_groups=('dir', 'tmp')) self.paths.add_path(uid='d_results_TOP', path=PATHS.DIR.RESULTS, uid_groups=('dir', 'tmp')) self.paths.add_path(uid='d_plots', path=PATHS.DIR.PLOTS+output_subdir, uid_groups=('dir', 'tmp')) self.paths.add_path(uid='d_results', path=PATHS.DIR.RESULTS+output_subdir, uid_groups=('dir', 'tmp')) # ===== Files ===== self.paths.add_path(uid='f_log', path=PATHS.FILES.LOG) self.paths.add_subpath(uid_parent='d_aircraft', uid='f_aircraft', path=f"{self.settings['aircraft']}") self.paths.add_subpath(uid_parent='d_deformation', uid='f_deformation', path=f"{self.settings['deformation']}") self.paths.add_subpath(uid_parent='d_settings', uid='f_settings', path=f"{self.project_basename}.json") self.paths.add_subpath(uid_parent='d_state', uid='f_state', path=f"{self.settings['state']}") # Output files self.paths.add_subpath(uid_parent='d_results', uid='f_results_global', path=f"global.json") # File namespaced with folder self.paths.add_subpath(uid_parent='d_results', uid='f_results_panelwise', path=f"panelwise.dat") # File namespaced with folder self.paths.add_subpath(uid_parent='d_results_TOP', uid='f_results_apm_global', path=f"{self.project_basename}_aeroperformance.json") def _check_aircraft_file_type(self): """Check whether aircraft is a CPACS or a JSON file""" ac_file_extension = self.paths('f_aircraft').suffix.lower() if ac_file_extension not in ['.xml', '.json']: raise ValueError("Aircraft file must have extension '.json' or '.xml' (CPACS)") if ac_file_extension == '.json': self.aircraft_is_cpacs = False else: self.aircraft_is_cpacs = True def update_from_dict(self, settings_dict): """ Update settings from dictionary structures Args: :settings: Dictionary with general settings :plot: Dictionary with plot settings """ for key, value in settings_dict.items(): self.settings[key] = value # If 'results' are not specified, plot 'cp' values if not self.settings['plot']['results']['opt']: self.settings['plot']['results']['opt'] = ['cp'] self._check_settings_dict() def clean(self): """ Remove old files in project directory """ self.paths.rm_dirs_for_groups('tmp') def _check_settings_dict(self): """ Check that settings dictionary contains valid input arguments """ logger.debug("Checking settings...") check_dict(template_dict=DEFAULT_SETTINGS, test_dict=self.settings) # ===== Other checks ===== if not (0 < self.settings['_epsilon'] < 1): raise ValueError("'_epsilon' must be a (small) float in range (0, 1)")
def test_adding_paths(): """ Test functions for adding paths """ # ----- add_path() ----- paths = ProjectPaths(ROOT) paths.add_path(uid=UID1, path=PATH1, uid_groups=UID_GROUP1) # Adding same UID must raise error with raises(ValueError): paths.add_path(uid=UID1, path=PATH1) # Add new path paths.add_path(uid=UID2, path=PATH2, uid_groups=(UID_GROUP1, UID_GROUP2)) assert len(paths._groups[UID_GROUP1]) == 2 assert len(paths._groups[UID_GROUP2]) == 1 assert len(paths._abs_paths) == 3 # Adding invalid group UID must raise error with raises(TypeError): paths.add_path(uid=str(random.randint(100, 200)), path=PATH1, uid_groups=1234) # ----- add_subpath() ----- paths.add_subpath(uid_parent=UID1, uid=UID3, path=SUBPATH1, uid_groups=UID_GROUP1) paths.add_subpath(uid_parent=UID2, uid=UID4, path=SUBPATH2, uid_groups=(UID_GROUP1, UID_GROUP2)) assert len(paths._groups[UID_GROUP1]) == 4 assert len(paths._groups[UID_GROUP2]) == 2 assert len(paths._abs_paths) == 5 # Adding non-exsiting group UID must raise error with raises(ValueError): paths.add_subpath(uid_parent='invalid_parent_uid', uid=str(random.randint(100, 200)), path=SUBPATH2, uid_groups=(UID_GROUP1, UID_GROUP2))