From ba357a6334d852a768218c79410a413eb77929c7 Mon Sep 17 00:00:00 2001 From: Ross Whitfield Date: Fri, 30 Oct 2020 15:19:42 -0400 Subject: [PATCH 1/2] Remove component, clone, sim_name and run options --- ipsframework/checklist.py | 76 ----- ipsframework/component.py | 19 -- ipsframework/configurationManager.py | 169 +--------- ipsframework/ips.py | 431 ++------------------------ ipsframework/ipsutil.py | 115 ------- ipsframework/runspaceInitComponent.py | 108 +------ ipsframework/services.py | 58 ---- tests/multirun/test_basic_serial.py | 34 +- tests/new/test_helloworld.py | 86 ++--- tests/new/test_ips_framework.py | 18 +- tests/new/test_ips_main.py | 41 +-- tests/new/test_module_components.py | 16 +- 12 files changed, 63 insertions(+), 1108 deletions(-) delete mode 100755 ipsframework/checklist.py diff --git a/ipsframework/checklist.py b/ipsframework/checklist.py deleted file mode 100755 index 71eca648..00000000 --- a/ipsframework/checklist.py +++ /dev/null @@ -1,76 +0,0 @@ -# ------------------------------------------------------------------------------- -# Copyright 2006-2012 UT-Battelle, LLC. See LICENSE for more information. -# ------------------------------------------------------------------------------- -from .configobj import ConfigObj - - -def get_status(checklist_file): - ips_status = {} - ips_status['CREATE_RUNSPACE'] = False - ips_status['RUN_SETUP'] = False - ips_status['RUN'] = False - - try: - with open(checklist_file, 'r'): - pass - conf = ConfigObj(checklist_file, interpolation='template', file_error=True) - - if conf['CREATE_RUNSPACE'] == 'DONE': - ips_status['CREATE_RUNSPACE'] = True - elif conf['CREATE_RUNSPACE'] == 'NOT_DONE': - ips_status['CREATE_RUNSPACE'] = False - - if conf['RUN_SETUP'] == 'DONE': - ips_status['RUN_SETUP'] = True - elif conf['RUN_SETUP'] == 'NOT_DONE': - ips_status['RUN_SETUP'] = False - - if conf['RUN'] == 'DONE': - ips_status['RUN'] = True - elif conf['RUN'] == 'NOT_DONE': - ips_status['RUN'] = False - - except IOError: - print('Checklist config file "%s" could not be found, continuing without.' % checklist_file) - except SyntaxError: - print('Error parsing config file: %s' % checklist_file) - raise - except Exception: - print('encountered exception during fwk.run() checklist configuration') - finally: - # create a new empty file - with open(checklist_file, 'w'): - pass - conf = ConfigObj(checklist_file, interpolation='template', file_error=True) - conf['CREATE_RUNSPACE'] = False - conf['RUN_SETUP'] = False - conf['RUN'] = False - conf.write() - - return ips_status - - -def update(checklist_file, ips_status): - - try: - conf = ConfigObj(checklist_file, interpolation='template', file_error=True) - except IOError: - # SEK: Remove because for the create_runspace it is not there? - # print 'Checklist config file "%s" could not be found, continuing without.' % checklist_file - return '', ips_status - except SyntaxError: - errmsg = 'Error parsing config file: ' + checklist_file - return errmsg, ips_status - except Exception as e: - print(e) - return 'encountered exception during fwk.run() checklist status', ips_status - # Make it general to be able to take fullpath or relative path - for step in ['CREATE_RUNSPACE', 'RUN_SETUP', 'RUN']: - if ips_status[step]: - conf[step] = 'DONE' - else: - conf[step] = 'NOT_DONE' - # print(step + ' = ' + conf[step]) - conf.write() - - return diff --git a/ipsframework/component.py b/ipsframework/component.py index 7bc8117a..0349bc0e 100755 --- a/ipsframework/component.py +++ b/ipsframework/component.py @@ -157,25 +157,6 @@ def init(self, timestamp=0.0, **keywords): self.services.debug('init() method called') pass - # SIMYAN: This is the step method that is used for run-setup - def setup(self, timestamp=0.0, **keywords): - """ - Produce some default debugging information before the rest of the code - is executed. - """ - self.services.debug('setup() method called') - pass - - # SIMYAN: this is a placeholder for future validation methods (i.e. - # checking data) - def validate(self, timestamp=0.0, **keywords): - """ - Produce some default debugging information before the rest of the code - is executed. - """ - self.services.debug('validate() method called') - pass - def restart(self, timestamp=0.0, **keywords): """ Produce some default debugging information before the rest of the code diff --git a/ipsframework/configurationManager.py b/ipsframework/configurationManager.py index 1ae93892..95e49416 100755 --- a/ipsframework/configurationManager.py +++ b/ipsframework/configurationManager.py @@ -35,8 +35,6 @@ def __init__(self, sim_name): self.sim_root = None self.sim_conf = None self.config_file = None - # SIMYAN: used for the directory of the platform conf file - self.conf_file_dir = None self.driver_comp = None self.init_comp = None self.all_comps = [] @@ -48,9 +46,7 @@ def __init__(self, sim_name): self.process_list = [] self.fwk_logger = None - # SIMYAN: accept a compset_list to find specific configuration files for - # the components - def __init__(self, fwk, config_file_list, platform_file_name, compset_list): + def __init__(self, fwk, config_file_list, platform_file_name): """ Initialize the values to be used by the configuration manager. Also specified are the required fields of the simulation configuration @@ -102,14 +98,10 @@ def __getattr__(self, attr): sys.stdout = Unbuffered(sys.stdout) self.platform_file = os.path.abspath(platform_file_name) self.platform_conf = {} - # SIMYAN: breaking up the keywords a little bit - self.compset_list = compset_list loc_keys = [] mach_keys = ['MPIRUN', 'NODE_DETECTION', 'CORES_PER_NODE', 'SOCKETS_PER_NODE', 'NODE_ALLOCATION_MODE'] prov_keys = ['HOST'] self.platform_keywords = loc_keys + mach_keys + prov_keys - # SIMYAN: keywords specific to individual components - self.compset_keywords = ['BIN_PATH', 'PHYS_BIN_ROOT', 'DATA_TREE_ROOT'] self.service_methods = ['get_port', 'getPort', @@ -138,7 +130,6 @@ def initialize(self, data_mgr, resource_mgr, task_mgr): :py:obj:`ConfigObj` module. Create and initialize simulation(s) and their components, framework components and loggers. """ - local_debug = False self.event_mgr = None # eventManager(self) self.data_mgr = data_mgr self.resource_mgr = resource_mgr @@ -267,39 +258,6 @@ def initialize(self, data_mgr, resource_mgr, task_mgr): self.platform_conf['USE_ACCURATE_NODES'] = use_accurate_nodes self.platform_conf['MPIRUN_VERSION'] = mpirun_version - # section to parse component conf files - """ - Physics (compset) configuration files (PHYS_BIN_ROOT, BIN_PATH, ...) - """ - self.compset_conf = [] - if self.compset_list: - for csfile in self.compset_list: - if local_debug: - print(csfile) - try: - csconf = ConfigObj(csfile, interpolation='template', file_error=True) - except IOError: - self.fwk.exception('Error opening config file: %s', csfile) - raise - except SyntaxError: - self.fwk.exception('Error parsing config file: %s', csfile) - raise - # get mandatory values - for kw in self.compset_keywords: - try: - csconf[kw] - except KeyError: - self.fwk.exception('Missing required parameter %s in %s config file', - kw, csfile) - raise - - # allow any csconf parameters override the platform file - for key in list(self.platform_conf.keys()): - if key in list(csconf.keys()): - self.platform_conf[key] = csconf[key] - - self.compset_conf.append(csconf) - """ Simulation Configuration """ @@ -307,14 +265,6 @@ def initialize(self, data_mgr, resource_mgr, task_mgr): try: conf = ConfigObj(conf_file, interpolation='template', file_error=True) - # Allow simulation file to override compset values - # and then put all compset values into simulation map - # The fact that csconf is a list is confusing - for csconf in self.compset_conf: - for key in list(csconf.keys()): - if key in list(conf.keys()): - csconf[key] = conf[key] - conf.merge(csconf) # Import environment variables into config file # giving precedence to config file definitions in case of duplicates conf_keys = list(conf.keys()) @@ -349,13 +299,6 @@ def initialize(self, data_mgr, resource_mgr, task_mgr): in configuration file %s', conf_file) raise - # SIMYAN allow for a container file with default .zip extension - self.container_ext = 'zip' - if 'CONTAINER_FILE_EXT' in conf: - self.container_ext = conf['CONTAINER_FILE_EXT'] - else: - conf['CONTAINER_FILE_EXT'] = self.container_ext - if (sim_name in sim_name_list): self.fwk.exception('Error: Duplicate SIM_NAME in configuration files') sys.exit(1) @@ -365,8 +308,6 @@ def initialize(self, data_mgr, resource_mgr, task_mgr): if (log_file in log_file_list): self.fwk.exception('Error: Duplicate LOG_FILE in configuration files') sys.exit(1) - if 'SIMULATION_CONFIG_FILE' not in conf: - conf['SIMULATION_CONFIG_FILE'] = conf_file sim_name_list.append(sim_name) sim_root_list.append(sim_root) log_file_list.append(log_file) @@ -441,7 +382,6 @@ def _initialize_fwk_components(self): runspace_conf['CLASS'] = 'FWK' runspace_conf['SUB_CLASS'] = 'COMP' runspace_conf['NAME'] = 'runspaceInitComponent' - # SIMYAN: using inspect to grab the ipsPathName from the current frame runspace_conf['SCRIPT'] = '' runspace_conf['MODULE'] = 'ipsframework.runspaceInitComponent' runspace_conf['INPUT_DIR'] = '/dev/null' @@ -684,15 +624,6 @@ def get_all_simulation_components_map(self): del sim_comps[self.fwk_sim_name] return sim_comps - def get_driver_components(self): - """ - Return a list of driver components, one for each sim. - """ - driver_list = [] - for sim in list(self.sim_map.values()): - driver_list.append(sim.driver_comp) - return driver_list - def get_framework_components(self): """ Return list of framework components. @@ -700,16 +631,6 @@ def get_framework_components(self): fwk_components = self.fwk_components[:] return fwk_components - def get_init_components(self): - """ - Return list of init components. - """ - init_list = [] - for sim_data in list(self.sim_map.values()): - if (sim_data.init_comp): - init_list.append(sim_data.init_comp) - return init_list - def get_sim_parameter(self, sim_name, param): """ Return value of *param* from simulation configuration file for @@ -746,94 +667,6 @@ def process_service_request(self, msg): retval = method(sim_name, *msg.args) return retval - def create_simulation(self, sim_name, config_file, override, sub_workflow=False): - try: - conf = ConfigObj(config_file, interpolation='template', - file_error=True) - except IOError: - self.fwk.exception('Error opening config file %s: ', config_file) - raise - except SyntaxError: - self.fwk.exception(' Error parsing config file %s: ', config_file) - raise - parent_sim_name = sim_name - parent_sim = self.sim_map[parent_sim_name] - # Incorporate environment variables into config file - # Use config file entries when duplicates are detected - conf_keys = list(conf.keys()) - for (k, v) in os.environ.items(): - # Do not include functions from environment - if k not in conf_keys and \ - not any([x in v for x in '{}()$']): - conf[k] = v - - # Allow propagation of entries from platform config file to simulation - # config file - for keyword in list(self.platform_conf.keys()): - if keyword not in list(conf.keys()): - conf[keyword] = self.platform_conf[keyword] - if (override): - for kw in list(override.keys()): - conf[kw] = override[kw] - try: - sim_name = conf['SIM_NAME'] - sim_root = conf['SIM_ROOT'] - log_file = os.path.abspath(conf['LOG_FILE']) - except KeyError: - self.fwk.exception('Missing required parameters SIM_NAME, SIM_ROOT or LOG_FILE\ -in configuration file %s', config_file) - raise - if (sim_name in self.sim_name_list): - self.fwk.error('Error: Duplicate SIM_NAME %s in configuration files' % (sim_name)) - raise Exception('Duplicate SIM_NAME %s in configuration files' % (sim_name)) - if (sim_root in self.sim_root_list): - self.fwk.exception('Error: Duplicate SIM_ROOT in configuration files') - raise Exception('Duplicate SIM_ROOT in configuration files') - if (log_file in self.log_file_list): - self.fwk.exception('Error: Duplicate LOG_FILE in configuration files') - raise Exception('Duplicate LOG_FILE in configuration files') - - # Add path to configuration file to simulation configuration in memory - if 'SIMULATION_CONFIG_FILE' not in conf: - conf['SIMULATION_CONFIG_FILE'] = config_file - - self.sim_name_list.append(sim_name) - self.sim_root_list.append(sim_root) - self.log_file_list.append(log_file) - new_sim = self.SimulationData(sim_name) - new_sim.sim_conf = conf - new_sim.config_file = config_file - new_sim.sim_root = sim_root - new_sim.log_file = log_file - if not sub_workflow: - new_sim.portal_sim_name = sim_name - new_sim.log_pipe_name = tempfile.mktemp('.logpipe', 'ips_') - self.log_dynamic_sim_queue.put('CREATE_SIM %s %s' % (new_sim.log_pipe_name, new_sim.log_file)) - else: - new_sim.portal_sim_name = parent_sim.portal_sim_name - new_sim.log_pipe_name = parent_sim.log_pipe_name - - conf['__PORTAL_SIM_NAME'] = new_sim.portal_sim_name - log_level = 'DEBUG' - try: - log_level = conf['LOG_LEVEL'] - except KeyError: - pass - try: - getattr(logging, log_level) - except AttributeError: - self.fwk.exception('Invalid LOG_LEVEL value %s in config file %s ', - log_level, config_file) - raise - # self.log_dynamic_sim_queue.put('CREATE_SIM %s %s' % (new_sim.log_pipe_name, new_sim.log_file)) - self.sim_map[sim_name] = new_sim - self._initialize_sim(new_sim) - - if not sub_workflow: - self.fwk.initiate_new_simulation(sim_name) - - return (sim_name, new_sim.init_comp, new_sim.driver_comp) - def getPort(self, sim_name, port_name): """ .. deprecated:: 1.0 Use :py:meth:`.get_port` diff --git a/ipsframework/ips.py b/ipsframework/ips.py index 3b634e9b..945db896 100755 --- a/ipsframework/ips.py +++ b/ipsframework/ips.py @@ -58,13 +58,9 @@ print("IPS can is only compatible with Python 3.5 or higher") sys.exit(1) -import glob -import fnmatch import optparse import multiprocessing -from . import platformspec, ipsutil, checklist -import shutil -import zipfile +from . import platformspec import inspect from .messages import Message, ServiceRequestMessage, \ ServiceResponseMessage, MethodInvokeMessage @@ -81,16 +77,12 @@ from .ips_es_spec import eventManager import os import time -from .configobj import ConfigObj class Framework: - # SIMYAN: added options for creating runspace, run-setup, and running, # added compset_list for list of components to load config files for - def __init__(self, do_create_runspace, do_run_setup, do_run, - config_file_list, log_file_name, platform_file_name=None, - compset_list=None, debug=False, - verbose_debug=False, cmd_nodes=0, cmd_ppn=0): + def __init__(self, config_file_list, log_file_name, platform_file_name=None, + debug=False, verbose_debug=False, cmd_nodes=0, cmd_ppn=0): """ Create an IPS Framework Instance to coordinate the execution of IPS simulations @@ -135,13 +127,6 @@ def __init__(self, do_create_runspace, do_run_setup, do_run, """ - # SIMYAN: collect the steps to do in this invocation of the Framework - self.ips_dosteps = {} - self.ips_dosteps['CREATE_RUNSPACE'] = do_create_runspace # create runspace: init.init() - self.ips_dosteps['RUN_SETUP'] = do_run_setup # validate inputs: sim_comps.init() - self.ips_dosteps['RUN'] = do_run # Main part of simulation - - # SIMYAN: set the logging to either file or sys.stdout based on # command line option self.log_file_name = log_file_name if log_file_name == 'sys.stdout': @@ -157,38 +142,11 @@ def __init__(self, do_create_runspace, do_run_setup, do_run, # map of ports self.port_map = {} - # SIMYAN: Complicated here to allow for a generalization of previous - # functionality and to enable the automatic finding of the files - self.compset_list = None - current_dir = inspect.getfile(inspect.currentframe()) (self.platform_file_name, self.ipsShareDir) = \ platformspec.get_share_and_platform(platform_file_name, current_dir) - checked_compset_list = [] - # if not self.ipsShareDir == '': - if compset_list: - for cname in compset_list: - cfile = 'component-' + cname + '.conf' - fullcfile = os.path.join(self.ipsShareDir, cfile) - if os.path.exists(fullcfile): - checked_compset_list.append(fullcfile) - if not len(checked_compset_list): - # print "Cannot find specified component configuration files." - # print " Assuming that variables are defined anyway" - pass - else: - self.compset_list = checked_compset_list - else: - if os.path.exists(os.path.join(self.ipsShareDir, 'component-generic.conf')): - checked_compset_list.append(os.path.join(self.ipsShareDir, 'component-generic.conf')) - self.compset_list = checked_compset_list - else: - # print "Cannot find any component configuration files." - # print " Assuming that variables are defined anyway" - pass - # config file list self.config_file_list = config_file_list @@ -201,11 +159,8 @@ def __init__(self, do_create_runspace, do_run_setup, do_run, self.event_service = EventService(self) initialize_event_service(self.event_service) self.event_manager = eventManager(self) - # SIMYAN: added a few parameters to Configuration Manager for - # component files self.config_manager = \ - ConfigurationManager(self, self.config_file_list, self.platform_file_name, self.compset_list) - + ConfigurationManager(self, self.config_file_list, self.platform_file_name) self.resource_manager = ResourceManager(self) self.data_manager = DataManager(self) self.task_manager = TaskManager(self) @@ -489,37 +444,12 @@ def run(self): self.terminate_all_sims(status=Message.FAILURE) return False - # SIMYAN: required fields for the checklist file - self.required_fields = set(['CREATE_RUNSPACE', 'RUN_SETUP', 'RUN']) - # SIMYAN: get the runspaceInit_component and invoke its init() method # this creates the base directory and container file for the simulation # and copies the conf files into both and change directory to base dir - self.ips_status = {} main_fwk_comp = self.comp_registry.getEntry(fwk_comps[0]) self.sim_root = os.path.abspath(main_fwk_comp.services.get_config_param('SIM_ROOT')) - self.checklist_file = os.path.join(self.sim_root, 'checklist.conf') - # SIMYAN: - # Get the status of the simulation - # - self.ips_status = checklist.get_status(self.checklist_file) - - if self.ips_dosteps['CREATE_RUNSPACE'] and self.ips_status['CREATE_RUNSPACE']: - self._invoke_framework_comps(fwk_comps, 'init') - self.ips_status['CREATE_RUNSPACE'] = True - self.ips_status['RUN_SETUP'] = False - self.ips_status['RUN'] = False - elif self.ips_dosteps['CREATE_RUNSPACE'] and not self.ips_status['CREATE_RUNSPACE']: - self._invoke_framework_comps(fwk_comps, 'init') - self.ips_status['CREATE_RUNSPACE'] = True - self.ips_status['RUN_SETUP'] = False - self.ips_status['RUN'] = False - elif not self.ips_dosteps['CREATE_RUNSPACE'] and self.ips_status['CREATE_RUNSPACE']: - print('Skipping CREATE_RUNSPACE, option was %s but runspace exists' % - self.ips_dosteps['CREATE_RUNSPACE']) - else: - print('Skipping CREATE_RUNSPACE, option was %s and runspace did not exist' % - self.ips_dosteps['CREATE_RUNSPACE']) + self._invoke_framework_comps(fwk_comps, 'init') try: # Each Framework Component is treated as a stand-alone simulation @@ -527,16 +457,11 @@ def run(self): for comp_id in fwk_comps: msg_list = [] for method in ['step', 'finalize']: - # SIMYAN: if --create_runspace was specified, add calls - # to step() and finalize() for the framework components - if self.ips_dosteps['CREATE_RUNSPACE']: - req_msg = ServiceRequestMessage(self.component_id, self.component_id, - comp_id, 'init_call', method, 0) - msg_list.append(req_msg) + req_msg = ServiceRequestMessage(self.component_id, self.component_id, + comp_id, 'init_call', method, 0) + msg_list.append(req_msg) - # SIMYAN: add those calls to the outstanding call map - if self.ips_dosteps['CREATE_RUNSPACE']: - outstanding_sim_calls[str(comp_id)] = msg_list + outstanding_sim_calls[str(comp_id)] = msg_list # generate a queue of invocation messages for each simulation # - list will look like: [init_comp.init(), init_comp.step(), init_comp.finalize(), @@ -551,37 +476,7 @@ def run(self): (self.resource_manager.num_nodes, self.resource_manager.ppn) self._send_monitor_event(sim_name, 'IPS_RESOURCE_ALLOC', comment) # SIMYAN: ordered list of methods to call - methods = [] - - if self.ips_dosteps['RUN_SETUP'] and self.ips_status['CREATE_RUNSPACE']: - self.ips_status['RUN_SETUP'] = True - methods.append('setup') - elif self.ips_dosteps['RUN_SETUP'] and not self.ips_status['CREATE_RUNSPACE']: - self.ips_status['RUN_SETUP'] = False - print('RUN_SETUP was requested, but a runspace does not exist...') - elif self.ips_status['RUN_SETUP'] and self.ips_status['CREATE_RUNSPACE']: - print('RUN_SETUP already performed by a previous run, continuing...') - elif self.ips_status['RUN_SETUP'] and not self.ips_status['CREATE_RUNSPACE']: - self.ips_status['RUN_SETUP'] = False - print('RUN_SETUP is marked as complete, but a runspace does not exist...') - else: - self.ips_status['RUN'] = False - - if self.ips_dosteps['RUN'] and self.ips_status['RUN_SETUP']: - self.ips_status['RUN'] = True - methods.append('init') - methods.append('step') - methods.append('finalize') - elif self.ips_dosteps['RUN'] and not self.ips_status['RUN_SETUP']: - self.ips_status['RUN'] = False - print('RUN was requested, but run setup hasn\'t been performed...') - elif self.ips_status['RUN'] and self.ips_dosteps['RUN_SETUP']: - self.ips_status['RUN'] = False - print('RUN was not requested, but had been performed.') - elif not self.ips_dosteps['RUN'] and self.ips_status['RUN'] and self.ips_status['RUN_SETUP']: - print('RUN was not requested, but had been performed.') - else: - self.ips_status['RUN'] = False + methods = ['init', 'step', 'finalize'] # SIMYAN: add each method call to the msg_list for comp_id in comp_list: @@ -679,42 +574,10 @@ def run(self): self.send_terminate_msg(sim_name, Message.SUCCESS) self.config_manager.terminate_sim(sim_name) - # SIMYAN: update the status of the simulation after all calls have - # been executed - main_fwk_comp = self.comp_registry.getEntry(fwk_comps[0]) - container_ext = main_fwk_comp.services.get_config_param('CONTAINER_FILE_EXT') - self.container_file = os.path.basename(self.sim_root) + os.path.extsep + container_ext - ipsutil.writeToContainer(self.container_file, self.sim_root, 'checklist.conf') - self.terminate_all_sims(Message.SUCCESS) self.event_service._print_stats() return True - def initiate_new_simulation(self, sim_name): - ''' - This is to be called by the configuration manager as part of dynamically creating - a new simulation. The purpose here is to initiate the method invocations for the - framework-visible components in the new simulation - ''' - comp_list = self.config_manager.get_simulation_components(sim_name) - msg_list = [] - self._send_monitor_event(sim_name, 'IPS_START', 'Starting IPS Simulation', ok=True) - self._send_dynamic_sim_event(sim_name=sim_name, event_type='IPS_START') - for comp_id in comp_list: - for method in ['init', 'step', 'finalize']: - req_msg = ServiceRequestMessage(self.component_id, - self.component_id, comp_id, - 'init_call', method, 0) - msg_list.append(req_msg) - - # send off first round of invocations... - msg = msg_list.pop(0) - self.debug('Framework sending message %s ', msg.__dict__) - call_id = self.task_manager.init_call(msg, manage_return=False) - self.call_queue_map[call_id] = msg_list - self.outstanding_calls_list.append(call_id) - return - def _send_monitor_event(self, sim_name='', eventType='', comment='', ok='True'): """ Publish a portal monitor event to the *_IPS_MONITOR* event topic. @@ -843,55 +706,6 @@ def terminate_all_sims(self, status=Message.SUCCESS): self.exception('exception encountered while cleaning up config_manager') # sys.exit(status) -# SIMYAN: method for modifying the config file with the given parameter to -# the new value given - - -def modifyConfigObjFile(configFile, parameter, newValue, writeNew=True): - # open the file, create the config object - cfg_file = ConfigObj(configFile, interpolation='template', file_error=True) - - # modify the SIM_NAME value to the new value - if parameter in cfg_file: - cfg_file[parameter] = newValue - else: - print(configFile + " has no parameter: " + parameter) - return "" - - newFileName = newValue + '.ips' - newFile = open(newFileName, "w") - if writeNew: - cfg_file.write(newFile) # write & close to avoid multiple references to these files - else: - cfg_file.write() - - return newFileName - -# SIMYAN: method for extracting the needed files to the current directory -# for cloning runs from a container file - - -def extractIpsFile(containerFile, newSimName): - """ - Given a container file, get the ips file in it and write it to current - directory so that it can be used - """ - oldIpsFile = os.path.splitext(containerFile)[0] + os.extsep + "ips" - - zf = zipfile.ZipFile(containerFile, "r") - - # Assume that container file contains 1 ips file. - oldIpsFile = fnmatch.filter(zf.namelist(), "*.ips")[0] - ifile = zf.read(oldIpsFile) - ipsFile = newSimName + ".ips" - if os.path.exists(ipsFile): - print("Moving " + ipsFile + " to " + "Save" + ipsFile) - shutil.copy(ipsFile, "Save" + ipsFile) - ff = open(ipsFile, "w") - ff.write(ifile) - ff.close() - return ipsFile - # callback function for options needing a file list @@ -903,14 +717,11 @@ def main(argv=None): """ Check and parse args, create and run the framework. """ - # SIMYAN: setting options for command line invocation of the Framework - runopts = "[--create-runspace | --clone | --run-setup | --run] " fileopts = "--simulation=SIM_FILE_NAME --platform=PLATFORM_FILE_NAME " - miscopts = "[--component=COMPONENT_FILE_NAME(S)] [--sim_name=SIM_NAME] [--log=LOG_FILE_NAME] " + miscopts = "[--log=LOG_FILE_NAME] " debugopts = "[--debug] [--verbose] " - # SIMYAN: add the options to the options parser - parser = optparse.OptionParser(usage="%prog " + runopts + debugopts + fileopts + miscopts) + parser = optparse.OptionParser(usage="%prog " + debugopts + fileopts + miscopts) parser.add_option('-d', '--debug', dest='debug', default=False, action='store_true', help='Turn on debugging') parser.add_option('-v', '--verbose', dest='verbose_debug', default=False, action='store_true', @@ -924,243 +735,37 @@ def main(argv=None): print("IPS using platform file :", platform_default) parser.add_option('-p', '--platform', dest='platform_filename', default=platform_default, type="string", help='IPS platform configuration file') - parser.add_option('-c', '--component', - action='callback', callback=filelist_callback, - type="string", help='IPS component configuration file(s)') parser.add_option('-i', '--simulation', action='callback', callback=filelist_callback, type="string", help='IPS simulation/config file') parser.add_option('-j', '--config', action='callback', callback=filelist_callback, type="string", help='IPS simulation/config file') - parser.add_option('-y', '--clone', - action='callback', callback=filelist_callback, - type="string", help='Clone container file') - parser.add_option('-e', '--sim_name', - action='callback', callback=filelist_callback, - type="string", help='Simulation name to replace in the IPS simulation file or a directory that has an ips file') parser.add_option('-l', '--log', dest='log_file', default='sys.stdout', type="string", help='IPS Log file') parser.add_option('-n', '--nodes', dest='cmd_nodes', default='0', type="int", help='Computer nodes') parser.add_option('-o', '--ppn', dest='cmd_ppn', default='0', type="int", help='Computer processor per nodes') - parser.add_option('-t', '--create-runspace', dest='do_create_runspace', default=False, action='store_true', - help='Create the runspace') - parser.add_option('-s', '--run-setup', dest='do_run_setup', default=False, action='store_true', - help='Run the setup (init of the driver)') - parser.add_option('-r', '--run', dest='do_run', default=False, action='store_true', - help='Run') - parser.add_option('-a', '--all', dest='do_all', default=False, action='store_true', - help='Do all steps of the workflow') - - # SIMYAN: parse the options from command line + options, args = parser.parse_args() - # Default behavior : do all steps - if options.do_create_runspace or \ - options.do_run_setup or \ - options.do_run: - pass - else: - options.do_all = True - - # -SIMYAN:---------------------------------------------------------------------------------- - ## - # Three ways of specifying where to find the config_file - # 1. --simulation or --config: Specify directly - # 2. --sim_name: Either rename simulation files, or use sim_name/sim_name.ips - # 3. --clone: Look for IPS file in container file. --sim_name must be specified to rename - ## - # See tests/refactor/test_ips.sh for motivation - ## - # ------------------------------------------------------------------------------------------ - ipsFilesToRemove = [] - - # SIMYAN: - # Some initial processing of the --simulation, --sim_name, clone - # for basic checking and ease in processing better. - # + cfgFile_list = [] - usedSimulation = False if options.simulation: cfgFile_list = options.simulation - nCfgFile = len(cfgFile_list) - usedSimulation = True if options.config: cfgFile_list = options.config - nCfgFile = len(cfgFile_list) - - # SIMYAN: grab the list of sim_names for multirun situations - simName_list = [] - usedSim_name = False - if options.sim_name: - simName_list = options.sim_name - nSimName = len(simName_list) - usedSim_name = True - if usedSimulation: - if nSimName != nCfgFile and usedSimulation: - print("When using both --simulation and --sim_name the list length must be the same") - return - - # SIMYAN: grab the list of clone container files, and make sure the - # new sim_names are given for each - clone_list = [] - usedClone = False - multiCloneSingleSource = False - if options.clone: - if not usedSim_name: - print("Must specify SIM_NAME using --sim_name when cloning") - return - if usedSimulation: - print("Cannot use both --simulation and --clone") - return - clone_list = options.clone - nClone = len(clone_list) - usedClone = True - if nSimName > nClone and nClone == 1: - # print "When using both --clone and --sim_name the list length must be the same" - print('Cloning from single clone file', clone_list[0], 'into multiple simulations', simName_list) - multiCloneSingleSource = True - # return - - # SIMYAN: initialize list for each sim_name - sim_file_map = {} - for sim_name in simName_list: - sim_file_map[sim_name] = [] - - # SIMYAN: - # Now process the simulation files - # Two methods for replacing the SIM_NAME: - # 1. colon syntax: NEW_SIM_NAME:ips_file - # 2. sim_name parameter list - # - if usedSimulation: - cleaned_file_list = [] - ipsFilesToRemove = [] - # iterate over the list of files - i = -1 - for file in cfgFile_list: - # if the file name contains ':', we must replace SIM_NAME in the - # file with the new value and remove the new SIM_NAME and ':' - # from the string for IPS to read the modified .ips file. - if file.find(':') != -1: - # split the mapping. new_sim_name gets replaced below - (new_sim_name, file_name) = file.split(':') - if usedSim_name: - file = modifyConfigObjFile(file_name, 'SIM_NAME', new_sim_name) - ipsFilesToRemove.append(file) - sim_file_map[new_sim_name].append(file) - else: - iFile = modifyConfigObjFile(file, 'SIM_NAME', new_sim_name,) - # ipsFilesToRemove.append(file) - - if usedSim_name: - i = i + 1 - new_sim_name = simName_list[i] - file = modifyConfigObjFile(file, 'SIM_NAME', new_sim_name) - ipsFilesToRemove.append(file) - sim_file_map[new_sim_name].append(file) - - # append file to the list of cleaned names that don't contain ':' - cleaned_file_list.append(file) - - # replace cfgFile_list with a list that won't have any ':' or sim_names - cfgFile_list = cleaned_file_list - - # SIMYAN: - # Now process container files when cloning - # - if usedClone: - options.do_create_runspace = True - cleaned_file_list = [] - # iterate over the list of files - i = -1 - for clone_file in clone_list: - if multiCloneSingleSource: - for new_sim_name in simName_list: - iFile = extractIpsFile(clone_file, new_sim_name) - file = modifyConfigObjFile(iFile, 'SIM_NAME', new_sim_name, writeNew=False) - sim_file_map[new_sim_name].append(iFile) - cleaned_file_list.append(iFile) - else: - i = i + 1 - new_sim_name = simName_list[i] - print('else') - iFile = extractIpsFile(clone_file, new_sim_name) - file = modifyConfigObjFile(iFile, 'SIM_NAME', new_sim_name, writeNew=False) - sim_file_map[new_sim_name].append(iFile) - # append file to the list of cleaned names that don't contain ':' - cleaned_file_list.append(iFile) - - # replace cfgFile_list with a list that won't have any ':' or sim_names - cfgFile_list = cleaned_file_list - ipsFilesToRemove = cleaned_file_list - - # SIMYAN: - # sim_name specified without simulation or clone means - # look for the IPS file in sim_name subdirectory - # - if usedSim_name and not (usedSimulation or usedClone): - cleaned_file_list = [] - for simname in simName_list: - foundFile = "" - for testFile in glob.glob(simname + "/*.ips"): - cfg_file = ConfigObj(testFile, interpolation='template') - if "SIM_NAME" in cfg_file: - testSimName = cfg_file["SIM_NAME"] - if testSimName == simname: - foundFile = testFile - sim_file_map[sim_name].append(foundFile) - - print("FOUND FILE", foundFile) - if foundFile: - cleaned_file_list.append(foundFile) - else: - print("Cannot find ips file associated with SIM_NAME= ", simname) - return - - cfgFile_list = cleaned_file_list - - # SIMYAN: process component files - compset_list = [] - if options.component: - compset_list = options.component - - if options.do_all: - # print 'doing all steps' - options.do_create_runspace = True - options.do_run_setup = True - options.do_run = True try: - # SIMYAN: added logic to handle multiple runs in sequence - if simName_list: - for sim_name in simName_list: - cfgFile_list = sim_file_map[sim_name] - fwk = Framework(options.do_create_runspace, options.do_run_setup, options.do_run, - cfgFile_list, options.log_file, options.platform_filename, - compset_list, options.debug, options.verbose_debug, - options.cmd_nodes, options.cmd_ppn) - fwk.run() - else: - fwk = Framework(options.do_create_runspace, options.do_run_setup, options.do_run, - cfgFile_list, options.log_file, options.platform_filename, - compset_list, options.debug, options.verbose_debug, - options.cmd_nodes, options.cmd_ppn) - fwk.run() + fwk = Framework(cfgFile_list, options.log_file, options.platform_filename, + options.debug, options.verbose_debug, + options.cmd_nodes, options.cmd_ppn) + fwk.run() except Exception: raise - # SIMYAN: post running cleanup of working files, so there aren't - # leftover files from doing clones or renaming simulations - if len(ipsFilesToRemove) > 0: - for file in ipsFilesToRemove: - os.remove(file) - return 0 -# ----- end main ----- - if __name__ == "__main__": print("Starting IPS") diff --git a/ipsframework/ipsutil.py b/ipsframework/ipsutil.py index 14c9b8df..c10a64d6 100755 --- a/ipsframework/ipsutil.py +++ b/ipsframework/ipsutil.py @@ -5,7 +5,6 @@ import shutil import time import glob -import zipfile try: import Pyro4 except ImportError: @@ -108,120 +107,6 @@ def copyFiles(src_dir, src_file_list, target_dir, prefix='', keep_old=False): raise -def _ignore_exception(func): - ''' Ignore exception raised when calling a function, printing an error message - :param func: - :return: wrapped function - ''' - def new_func(*args, **kwargs): - try: - func(*args, **kwargs) - except Exception as e: - print("Ignoring exception %s in call to %s : %s" % - (e.__class__, func.__name__, e.args)) - return new_func - - -# SIMYAN: added a utility method to write to the container file -@_ignore_exception -def writeToContainer(ziphandle, src_dir, src_file_list): - """ - Write files to the ziphandle. Because when one wants to unzip the - file, one typically doesn't want the full path, this handles getting - just the shorter path name. - src_file_list can be a single string - If src_dir is specified: - relative - If it is not specified: - relative - filename in the zip file. - """ - try: - file_list = src_file_list.split() - except AttributeError: # srcFileList is not a string - file_list = src_file_list - - # The logic for directories in the current directory follows that of - # not specifying a src directory at all - if src_dir == ".": - src_dir = "" - - # print 'src_file_list = ', src_file_list - # print 'ziphandle = ', ziphandle - # print 'os.path.exists(ziphandle) = ', os.path.exists(ziphandle) - try: - if os.path.exists(ziphandle): - zin = zipfile.ZipFile(ziphandle, 'r') - zout = zipfile.ZipFile('temp.ctz', 'a') - else: - # print 'so it is None' - zin = None - zout = zipfile.ZipFile(ziphandle, 'a') - except zipfile.BadZipfile: - print('Found a bad container file, removing...') - os.remove(ziphandle) - zin = None - zout = zipfile.ZipFile(ziphandle, 'a') - - if src_dir: - # Handle possible wildcards in input files. - # Build and flatten list of lists using sum(l_of_l, []) - for sfile in sum([glob.glob(os.path.join(src_dir, p)) - for p in file_list], []): - # sfile=os.path.join(src_dir,file) - if os.path.exists(sfile): - # print 'srcdir has' - # print 'sfile =', sfile - # print 'file =', file - (head, tail) = os.path.split(os.path.abspath(sfile)) - file = tail - if not os.path.exists(file): - shutil.copy(sfile, file) - zout.write(file) - os.remove(file) - else: - zout.write(file) - else: - raise Exception('No such file : %s in directory %s' % (file, src_dir)) - else: - for file in file_list: - if os.path.exists(file): - # If it is a full path, copy it locally, write to zip, remove - if os.path.dirname(file): - absfile = os.path.abspath(file) - (sdir, sfile) = os.path.split(absfile) - # print 'srcdir has not' - # print 'absfile =', absfile - # print 'sfile =', sfile - (head, tail) = os.path.split(os.path.abspath(sfile)) - sfile = tail - if not os.path.exists(sfile): - shutil.copy(absfile, sfile) - zout.write(sfile) - os.remove(sfile) - else: - zout.write(sfile) - else: - zout.write(file) - else: - raise Exception('No such file : %s', file) - - # print 'zin is ', zin - if zin: - for item in zin.infolist(): - buffer = zin.read(item.filename) - # print 'item.filename = ', item.filename - # print 'zout.namelist() = ', zout.namelist() - # print 'item.filename in zout.namelist() = ', item.filename in zout.namelist() - if item.filename not in zout.namelist(): - zout.writestr(item, buffer) - - zin.close() - shutil.move('temp.ctz', ziphandle) - - zout.close() - - def getTimeString(timeArg=None): """ Return a string representation of *timeArg*. *timeArg* is expected diff --git a/ipsframework/runspaceInitComponent.py b/ipsframework/runspaceInitComponent.py index 899b6d1c..f3c3d111 100755 --- a/ipsframework/runspaceInitComponent.py +++ b/ipsframework/runspaceInitComponent.py @@ -34,6 +34,9 @@ def __init__(self, services, config): :py:class:`component.Component` object. """ Component.__init__(self, services, config) + # get the simRootDir + self.simRootDir = services.get_config_param('SIM_ROOT') + self.cwd = self.config['OS_CWD'] # print('Created %s' % (self.__class__)) @catch_and_go @@ -42,15 +45,8 @@ def init(self, timeStamp): Creates base directory, copies IPS and FacetsComposer input files. """ - # print('runspaceInitComponent.init() called') - services = self.services - # get the simRootDir - self.simRootDir = services.get_config_param('SIM_ROOT') - self.cwd = self.config['OS_CWD'] - # print 'cwd =', self.cwd - try: os.chdir(self.cwd) except OSError: @@ -58,7 +54,6 @@ def init(self, timeStamp): self.cwd) raise - container_ext = services.get_config_param('CONTAINER_FILE_EXT') if not self.simRootDir.startswith("/"): self.simRootDir = os.path.join(self.cwd, self.simRootDir) @@ -74,10 +69,6 @@ def init(self, timeStamp): self.config_files = services.fwk.config_file_list self.platform_file = services.fwk.platform_file_name self.main_log_file = services.get_config_param('LOG_FILE') - # DBG print 'log_file = ', self.main_log_file - - # uncomment when implemented - # self.fc_files = services.fwk.facets_composer_files # Determine where the file is...if there's not an absolute path specified, # assume that it was in the directory that the IPS was launched from. @@ -93,74 +84,9 @@ def init(self, timeStamp): (head, tail) = os.path.split(os.path.abspath(self.platform_file)) self.plat_file_loc = head - # print 'conf_file_loc =', self.conf_file_loc - # print 'plat_file_loc =', self.plat_file_loc ipsutil.copyFiles(self.conf_file_loc, self.config_files, self.simRootDir) ipsutil.copyFiles(self.plat_file_loc, self.platform_file, self.simRootDir) - # sim_comps = services.fwk.config_manager.get_component_map() - sim_comps = services.fwk.config_manager.get_all_simulation_components_map() - registry = services.fwk.comp_registry - # Redoing the same container_file name calculation as in configuration manager because I - # can't figure out where to put it such that I can get it where I need - self.container_file = os.path.basename(services.get_config_param('SIM_ROOT')) + os.path.extsep + container_ext - # Remove existing container file in case we are restarting an old simulation in the same directory - try: - os.remove(self.container_file) - except OSError as e: - if e.errno != 2: - raise - - ipsutil.writeToContainer(self.container_file, self.conf_file_loc, self.config_files) - ipsutil.writeToContainer(self.container_file, self.plat_file_loc, self.platform_file) - - # for each component_id in the list of components - for sim_name, comp_list in list(sim_comps.items()): - for comp_id in comp_list: - registry = services.fwk.comp_registry - comp_conf = registry.getEntry(comp_id).component_ref.config - if isinstance(comp_conf['INPUT_FILES'], list): - comp_conf['INPUT_FILES'] = ' '.join(comp_conf['INPUT_FILES']) - file_list = comp_conf['INPUT_FILES'].split() - for file in file_list: - ipsutil.writeToContainer(self.container_file, - os.path.relpath(comp_conf['INPUT_DIR']), - os.path.basename(file)) - -# curdir=os.path.abspath(os.path.curdir) -# try: -# print '4', self.simRootDir -# os.chdir(self.simRootDir) -# except OSError, (errno, strerror): -# self.services.debug('Working directory %s does not exist - will attempt creation', -# self.simRootDir) -# try: -# print '5', self.simRootDir -# os.makedirs(self.simRootDir) -# except OSError, (errno, strerror): -# self.services.exception('Error creating directory %s : %s' , -# workdir, strerror) -# raise - -# print 'curdir=', curdir -# print 'cwd=', os.getcwd() -# print 'cwd=', self.cwd - # os.chdir(curdir) # Get back to where you once belonged - - # uncomment when implemented - # (head, tail) = os.path.split(os.path.abspath(self.fc_files)) - # ipsutil.copyFiles(os.path.dirname(self.fc_files), - # os.path.basename(self.fc_files), simRootDir) - - return - - def validate(self, timestamp=0.0): - """ - Placeholder for future validation step of runspace management. - """ - # print('runspaceInitComponent.validate() called') - return - @catch_and_go def step(self, timestamp=0.0): """ @@ -258,31 +184,3 @@ def step(self, timestamp=0.0): self.services.exception('Error creating directory %s : %s', workdir, strerror) raise - - # print 'FINISHED RUNSPACEINIT' - return - - def checkpoint(self, timestamp=0.0): - """ - Placeholder - """ - # print('runspaceInitComponent.checkpoint() called') - - # save restart files - # services = self.services - # services.save_restart_files(timestamp, self.RESTART_FILES) - - return - - def finalize(self, timestamp=0.0): - """ - Writes final log_file and resource_usage file to the container and closes - """ - # print('runspaceInitComponent.finalize() called') - -# print self.main_log_file -# print self.simRootDir - ipsutil.writeToContainer(self.container_file, self.cwd, self.main_log_file) - ipsutil.writeToContainer(self.container_file, self.simRootDir, 'resource_usage') - - return diff --git a/ipsframework/services.py b/ipsframework/services.py index 8397e92a..ca36368f 100755 --- a/ipsframework/services.py +++ b/ipsframework/services.py @@ -2396,64 +2396,6 @@ def remove_task_pool(self, task_pool_name): task_pool = self.task_pools[task_pool_name] task_pool.terminate_tasks() del self.task_pools[task_pool_name] - return - - def create_sub_workflow(self, sub_name, config_file, override=None, input_dir=None): - - if not override: - override = {} - if sub_name in list(self.sub_flows.keys()): - self.exception("Duplicate sub flow name") - raise Exception("Duplicate sub flow name") - - # print("Creating worflow using ", config_file) - self.subflow_count += 1 - try: - sub_conf_new = ConfigObj(infile=config_file, interpolation='template', file_error=True) - sub_conf_old = ConfigObj(infile=config_file, interpolation='template', file_error=True) - except Exception: - self.exception("Error accessing sub-workflow config file %s" % config_file) - raise - # Update undefined sub workflow configuration entries using top level configuration - # only applicable to non-component entries (ones with non-dictionary values) - for (k, v) in self.sim_conf.items(): - if k not in list(sub_conf_new.keys()) and type(v).__name__ != 'dict': - sub_conf_new[k] = v - - sub_conf_new['SIM_NAME'] = self.sim_name + "::" + sub_name - sub_conf_new['SIM_ROOT'] = os.path.join(os.getcwd(), sub_name) - # sub_conf_new['SIM_ROOT'] = os.path.join(os.getcwd(), 'sub_workflow_%d' % self.subflow_count) - # Update INPUT_DIR for components to current working dir (super simulation working dir) - ports = sub_conf_new['PORTS']['NAMES'].split() - comps = [sub_conf_new['PORTS'][p]['IMPLEMENTATION'] for p in ports] - for c in comps: - if not c: - continue - if input_dir is None: - sub_conf_new[c]['INPUT_DIR'] = os.path.join(os.getcwd(), c) - else: - sub_conf_new[c]['INPUT_DIR'] = os.path.join(os.getcwd(), input_dir) - try: - override_vals = override[c] - except KeyError: - pass - else: - for (k, v) in override_vals.items(): - sub_conf_new[c][k] = v - toplevel_override = set(override.keys()) - set(comps) - for param in toplevel_override: - sub_conf_new[param] = override[param] - - sub_conf_new.filename = os.path.basename(config_file) - sub_conf_new.write() - try: - (sim_name, init_comp, driver_comp) = self._create_simulation(os.path.abspath(sub_conf_new.filename), - {}, sub_workflow=True) - except Exception: - raise - self.sub_flows[sub_name] = (sub_conf_new, sub_conf_old, init_comp, driver_comp) - self._send_monitor_event('IPS_CREATE_SUB_WORKFLOW', 'workflow_name = %s' % sub_name) - return (sim_name, init_comp, driver_comp) def create_simulation(self, config_file, override): return self._create_simulation(config_file, override, sub_workflow=False)[0] diff --git a/tests/multirun/test_basic_serial.py b/tests/multirun/test_basic_serial.py index 433cfe2f..1ad5216f 100644 --- a/tests/multirun/test_basic_serial.py +++ b/tests/multirun/test_basic_serial.py @@ -24,13 +24,9 @@ def test_basic_serial1(tmpdir, capfd): # setup 'input' files os.system(f"cd {tmpdir}; touch file1 ofile1 ofile2 sfile1 sfile2") - framework = Framework(do_create_runspace=True, - do_run_setup=True, - do_run=True, - config_file_list=[os.path.join(tmpdir, 'basic_serial1.ips')], + framework = Framework(config_file_list=[os.path.join(tmpdir, 'basic_serial1.ips')], log_file_name=os.path.join(tmpdir, 'test.log'), platform_file_name=os.path.join(tmpdir, "platform.conf"), - compset_list=[], debug=None, verbose_debug=None, cmd_nodes=0, @@ -45,12 +41,12 @@ def test_basic_serial1(tmpdir, capfd): assert captured_out[0] == "Created " assert captured_out[1] == "Created " assert captured_out[2] == "Created " - assert captured_out[4] == "small_worker : init() called" - assert captured_out[6] == "medium_worker : init() called" - assert captured_out[8] == "large_worker : init() called" - assert captured_out[10] == "Current time = 1.00" - assert captured_out[11] == "Current time = 2.00" - assert captured_out[12] == "Current time = 3.00" + assert captured_out[3] == "small_worker : init() called" + assert captured_out[5] == "medium_worker : init() called" + assert captured_out[7] == "large_worker : init() called" + assert captured_out[9] == "Current time = 1.00" + assert captured_out[10] == "Current time = 2.00" + assert captured_out[11] == "Current time = 3.00" # check files copied and created driver_files = [os.path.basename(f) for f in glob.glob(str(tmpdir.join("test_basic_serial1_0/work/drivers_testing_basic_serial1_*/*")))] @@ -66,11 +62,6 @@ def test_basic_serial1(tmpdir, capfd): assert outfile in medium_worker_files assert outfile in large_worker_files - # cleanup - for fname in ["test_basic_serial1_0.zip", "dask_preload.py"]: - if os.path.isfile(fname): - os.remove(fname) - def test_basic_serial_multi(tmpdir, capfd): # This is the same as test_basic_serial1 except that 2 simulation files are use at the same time @@ -82,14 +73,10 @@ def test_basic_serial_multi(tmpdir, capfd): # setup 'input' files os.system(f"cd {tmpdir}; touch file1 ofile1 ofile2 sfile1 sfile2") - framework = Framework(do_create_runspace=True, - do_run_setup=True, - do_run=True, - config_file_list=[os.path.join(tmpdir, 'basic_serial1.ips'), + framework = Framework(config_file_list=[os.path.join(tmpdir, 'basic_serial1.ips'), os.path.join(tmpdir, 'basic_serial2.ips')], log_file_name=os.path.join(tmpdir, 'test.log'), platform_file_name=os.path.join(tmpdir, "platform.conf"), - compset_list=[], debug=None, verbose_debug=None, cmd_nodes=0, @@ -141,8 +128,3 @@ def test_basic_serial_multi(tmpdir, capfd): assert outfile in small_worker_files assert outfile in medium_worker_files assert outfile in large_worker_files - - # cleanup - for fname in ["test_basic_serial1_0.zip", "dask_preload.py"]: - if os.path.isfile(fname): - os.remove(fname) diff --git a/tests/new/test_helloworld.py b/tests/new/test_helloworld.py index 439d1777..7f6470c1 100644 --- a/tests/new/test_helloworld.py +++ b/tests/new/test_helloworld.py @@ -37,22 +37,14 @@ def test_helloworld(tmpdir, capfd): shutil.copy(os.path.join(data_dir, "hello_driver.py"), tmpdir) shutil.copy(os.path.join(data_dir, "hello_worker.py"), tmpdir) - framework = Framework(do_create_runspace=True, # create runspace: init.init() - do_run_setup=True, # validate inputs: sim_comps.init() - do_run=True, # Main part of simulation - config_file_list=[os.path.join(tmpdir, "hello_world.ips")], + framework = Framework(config_file_list=[os.path.join(tmpdir, "hello_world.ips")], log_file_name=str(tmpdir.join('test.log')), platform_file_name=os.path.join(tmpdir, "platform.conf"), - compset_list=[], debug=None, verbose_debug=None, cmd_nodes=0, cmd_ppn=0) - assert framework.ips_dosteps['CREATE_RUNSPACE'] - assert framework.ips_dosteps['RUN_SETUP'] - assert framework.ips_dosteps['RUN'] - assert framework.log_file_name.endswith('test.log') fwk_components = framework.config_manager.get_framework_components() @@ -78,23 +70,17 @@ def test_helloworld(tmpdir, capfd): captured_out = captured.out.split('\n') assert captured_out[0] == "Created " assert captured_out[1] == "Created " - assert captured_out[2].endswith('checklist.conf" could not be found, continuing without.') - assert captured_out[3] == 'HelloDriver: init' - assert captured_out[4] == 'HelloDriver: finished worker init call' - assert captured_out[5] == 'HelloDriver: beginning step call' - assert captured_out[6] == 'Hello from HelloWorker' - assert captured_out[7] == 'HelloDriver: finished worker call' + assert captured_out[2] == 'HelloDriver: init' + assert captured_out[3] == 'HelloDriver: finished worker init call' + assert captured_out[4] == 'HelloDriver: beginning step call' + assert captured_out[5] == 'Hello from HelloWorker' + assert captured_out[6] == 'HelloDriver: finished worker call' assert captured.err == '' # check that portal didn't write anything since USE_PORTAL=False assert not os.path.exists(tmpdir.join("simulation_log")) assert not os.path.exists(tmpdir.join("www")) - # cleanup - for fname in ["test_helloworld0.zip", "dask_preload.py"]: - if os.path.isfile(fname): - os.remove(fname) - def test_helloworld_task_pool(tmpdir, capfd): data_dir = os.path.join(os.path.dirname(__file__), '..', 'helloworld') @@ -103,22 +89,14 @@ def test_helloworld_task_pool(tmpdir, capfd): shutil.copy(os.path.join(data_dir, "hello_driver.py"), tmpdir) shutil.copy(os.path.join(data_dir, "hello_worker_task_pool.py"), tmpdir) - framework = Framework(do_create_runspace=True, # create runspace: init.init() - do_run_setup=True, # validate inputs: sim_comps.init() - do_run=True, # Main part of simulation - config_file_list=[os.path.join(tmpdir, "hello_world.ips")], + framework = Framework(config_file_list=[os.path.join(tmpdir, "hello_world.ips")], log_file_name=str(tmpdir.join('test.log')), platform_file_name=os.path.join(tmpdir, "platform.conf"), - compset_list=[], debug=None, verbose_debug=None, cmd_nodes=0, cmd_ppn=0) - assert framework.ips_dosteps['CREATE_RUNSPACE'] - assert framework.ips_dosteps['RUN_SETUP'] - assert framework.ips_dosteps['RUN'] - assert framework.log_file_name.endswith('test.log') assert len(framework.config_manager.get_framework_components()) == 1 @@ -142,22 +120,21 @@ def test_helloworld_task_pool(tmpdir, capfd): assert captured_out[0] == "Created " assert captured_out[1] == "Created " - assert captured_out[2].endswith('checklist.conf" could not be found, continuing without.') - assert captured_out[3] == 'HelloDriver: init' - assert captured_out[4] == 'HelloDriver: finished worker init call' - assert captured_out[5] == 'HelloDriver: beginning step call' - assert captured_out[6] == 'Hello from HelloWorker' - assert captured_out[7] == 'ret_val = 10' - - exit_status = json.loads(captured_out[8].replace("'", '"')) + assert captured_out[2] == 'HelloDriver: init' + assert captured_out[3] == 'HelloDriver: finished worker init call' + assert captured_out[4] == 'HelloDriver: beginning step call' + assert captured_out[5] == 'Hello from HelloWorker' + assert captured_out[6] == 'ret_val = 10' + + exit_status = json.loads(captured_out[7].replace("'", '"')) assert len(exit_status) == 10 for n in range(10): assert f'task_{n}' in exit_status assert exit_status[f'task_{n}'] == 0 - assert captured_out[9] == "====== Non Blocking " + assert captured_out[8] == "====== Non Blocking " - for line in range(10, len(captured_out) - 2): + for line in range(9, len(captured_out) - 2): if "Nonblock_task" in captured_out[line]: assert captured_out[line].endswith("': 0}") @@ -165,11 +142,6 @@ def test_helloworld_task_pool(tmpdir, capfd): assert captured_out[-2] == 'HelloDriver: finished worker call' assert captured.err == '' - # cleanup - for fname in ["test_helloworld_task_pool0.zip", "dask_preload.py"]: - if os.path.isfile(fname): - os.remove(fname) - def test_helloworld_portal(tmpdir, capfd): data_dir = os.path.join(os.path.dirname(__file__), '..', 'helloworld') @@ -178,22 +150,14 @@ def test_helloworld_portal(tmpdir, capfd): shutil.copy(os.path.join(data_dir, "hello_driver.py"), tmpdir) shutil.copy(os.path.join(data_dir, "hello_worker.py"), tmpdir) - framework = Framework(do_create_runspace=True, # create runspace: init.init() - do_run_setup=True, # validate inputs: sim_comps.init() - do_run=True, # Main part of simulation - config_file_list=[os.path.join(tmpdir, "hello_world.ips")], + framework = Framework(config_file_list=[os.path.join(tmpdir, "hello_world.ips")], log_file_name=str(tmpdir.join('test.log')), platform_file_name=os.path.join(tmpdir, "platform.conf"), - compset_list=[], debug=None, verbose_debug=None, cmd_nodes=0, cmd_ppn=0) - assert framework.ips_dosteps['CREATE_RUNSPACE'] - assert framework.ips_dosteps['RUN_SETUP'] - assert framework.ips_dosteps['RUN'] - assert framework.log_file_name.endswith('test.log') fwk_components = framework.config_manager.get_framework_components() @@ -220,12 +184,11 @@ def test_helloworld_portal(tmpdir, capfd): captured_out = captured.out.split('\n') assert captured_out[0] == "Created " assert captured_out[1] == "Created " - assert captured_out[2].endswith('checklist.conf" could not be found, continuing without.') - assert captured_out[3] == 'HelloDriver: init' - assert captured_out[4] == 'HelloDriver: finished worker init call' - assert captured_out[5] == 'HelloDriver: beginning step call' - assert captured_out[6] == 'Hello from HelloWorker' - assert captured_out[7] == 'HelloDriver: finished worker call' + assert captured_out[2] == 'HelloDriver: init' + assert captured_out[3] == 'HelloDriver: finished worker init call' + assert captured_out[4] == 'HelloDriver: beginning step call' + assert captured_out[5] == 'Hello from HelloWorker' + assert captured_out[6] == 'HelloDriver: finished worker call' assert captured.err == '' # check that portal created output folders @@ -243,8 +206,3 @@ def test_helloworld_portal(tmpdir, capfd): assert '.json' in exts assert '.html' in exts assert '.eventlog' in exts - - # cleanup - for fname in ["test_helloworld_portal0.zip", "dask_preload.py"]: - if os.path.isfile(fname): - os.remove(fname) diff --git a/tests/new/test_ips_framework.py b/tests/new/test_ips_framework.py index e3331d05..8e7503b4 100644 --- a/tests/new/test_ips_framework.py +++ b/tests/new/test_ips_framework.py @@ -1,7 +1,6 @@ from ipsframework.ips import Framework import glob import json -import os def write_basic_config_and_platform_files(tmpdir): @@ -63,22 +62,14 @@ def __init__(self, services, config): def test_framework_simple(tmpdir, capfd): platform_file, config_file = write_basic_config_and_platform_files(tmpdir) - framework = Framework(do_create_runspace=True, # create runspace: init.init() - do_run_setup=True, # validate inputs: sim_comps.init() - do_run=True, # Main part of simulation - config_file_list=[str(config_file)], + framework = Framework(config_file_list=[str(config_file)], log_file_name=str(tmpdir.join('test.log')), platform_file_name=str(platform_file), - compset_list=[], debug=None, verbose_debug=None, cmd_nodes=0, cmd_ppn=0) - assert framework.ips_dosteps['CREATE_RUNSPACE'] - assert framework.ips_dosteps['RUN_SETUP'] - assert framework.ips_dosteps['RUN'] - assert framework.log_file_name.endswith('test.log') assert len(framework.config_manager.get_framework_components()) == 2 @@ -118,10 +109,5 @@ def test_framework_simple(tmpdir, capfd): assert event['sim_name'] == 'test' captured = capfd.readouterr() - assert captured.out.endswith('checklist.conf" could not be found, continuing without.\n') + assert captured.out == '' assert captured.err == '' - - # cleanup - for fname in ["test_framework_simple0.zip", "dask_preload.py"]: - if os.path.isfile(fname): - os.remove(fname) diff --git a/tests/new/test_ips_main.py b/tests/new/test_ips_main.py index 336c3bf4..e322547c 100644 --- a/tests/new/test_ips_main.py +++ b/tests/new/test_ips_main.py @@ -22,64 +22,39 @@ def test_ips_main(MockFramework): # override sys.argv for testing sys.argv = ["ips.py"] ips.main() - MockFramework.assert_called_with(True, True, True, [], 'sys.stdout', '', [], False, False, 0, 0) - - MockFramework.reset_mock() - sys.argv = ["ips.py", "--create-runspace"] - ips.main() - MockFramework.assert_called_with(True, False, False, [], 'sys.stdout', '', [], False, False, 0, 0) - - MockFramework.reset_mock() - sys.argv = ["ips.py", "--create-runspace"] - ips.main() - MockFramework.assert_called_with(True, False, False, [], 'sys.stdout', '', [], False, False, 0, 0) - - MockFramework.reset_mock() - sys.argv = ["ips.py", "--run-setup"] - ips.main() - MockFramework.assert_called_with(False, True, False, [], 'sys.stdout', '', [], False, False, 0, 0) - - MockFramework.reset_mock() - sys.argv = ["ips.py", "--run"] - ips.main() - MockFramework.assert_called_with(False, False, True, [], 'sys.stdout', '', [], False, False, 0, 0) + MockFramework.assert_called_with([], 'sys.stdout', '', False, False, 0, 0) MockFramework.reset_mock() sys.argv = ["ips.py", "--simulation=sim.cfg"] ips.main() - MockFramework.assert_called_with(True, True, True, ['sim.cfg'], 'sys.stdout', '', [], False, False, 0, 0) + MockFramework.assert_called_with(['sim.cfg'], 'sys.stdout', '', False, False, 0, 0) MockFramework.reset_mock() sys.argv = ["ips.py", "--config=sim.cfg"] ips.main() - MockFramework.assert_called_with(True, True, True, ['sim.cfg'], 'sys.stdout', '', [], False, False, 0, 0) + MockFramework.assert_called_with(['sim.cfg'], 'sys.stdout', '', False, False, 0, 0) MockFramework.reset_mock() sys.argv = ["ips.py", "--config=sim1.cfg,sim2.cfg"] ips.main() - MockFramework.assert_called_with(True, True, True, ['sim1.cfg', 'sim2.cfg'], 'sys.stdout', '', [], False, False, 0, 0) - - MockFramework.reset_mock() - sys.argv = ["ips.py", "--simulation=sim1.cfg", "--sim_name=sim1,sim2"] - ips.main() - MockFramework.assert_not_called() + MockFramework.assert_called_with(['sim1.cfg', 'sim2.cfg'], 'sys.stdout', '', False, False, 0, 0) MockFramework.reset_mock() sys.argv = ["ips.py", "--log=file.log"] ips.main() - MockFramework.assert_called_with(True, True, True, [], 'file.log', '', [], False, False, 0, 0) + MockFramework.assert_called_with([], 'file.log', '', False, False, 0, 0) MockFramework.reset_mock() sys.argv = ["ips.py", "--nodes=5", "--ppn=32"] ips.main() - MockFramework.assert_called_with(True, True, True, [], 'sys.stdout', '', [], False, False, 5, 32) + MockFramework.assert_called_with([], 'sys.stdout', '', False, False, 5, 32) MockFramework.reset_mock() sys.argv = ["ips.py", "--debug"] ips.main() - MockFramework.assert_called_with(True, True, True, [], 'sys.stdout', '', [], True, False, 0, 0) + MockFramework.assert_called_with([], 'sys.stdout', '', True, False, 0, 0) MockFramework.reset_mock() sys.argv = ["ips.py", "--verbose"] ips.main() - MockFramework.assert_called_with(True, True, True, [], 'sys.stdout', '', [], False, True, 0, 0) + MockFramework.assert_called_with([], 'sys.stdout', '', False, True, 0, 0) diff --git a/tests/new/test_module_components.py b/tests/new/test_module_components.py index b75e06f5..07ab76e2 100644 --- a/tests/new/test_module_components.py +++ b/tests/new/test_module_components.py @@ -1,5 +1,4 @@ from ipsframework.ips import Framework -import os def write_basic_config_and_platform_files(tmpdir): @@ -61,22 +60,14 @@ def write_basic_config_and_platform_files(tmpdir): def test_using_module_components(tmpdir, capfd): platform_file, config_file = write_basic_config_and_platform_files(tmpdir) - framework = Framework(do_create_runspace=True, # create runspace: init.init() - do_run_setup=True, # validate inputs: sim_comps.init() - do_run=True, # Main part of simulation - config_file_list=[str(config_file)], + framework = Framework(config_file_list=[str(config_file)], log_file_name=str(tmpdir.join('test.log')), platform_file_name=str(platform_file), - compset_list=[], debug=None, verbose_debug=None, cmd_nodes=0, cmd_ppn=0) - assert framework.ips_dosteps['CREATE_RUNSPACE'] - assert framework.ips_dosteps['RUN_SETUP'] - assert framework.ips_dosteps['RUN'] - assert framework.log_file_name.endswith('test.log') assert len(framework.config_manager.get_framework_components()) == 2 @@ -101,8 +92,3 @@ def test_using_module_components(tmpdir, capfd): assert captured_out[0] == "Created " assert captured_out[1] == "Created " assert captured.err == '' - - # cleanup - for fname in ["test_using_module_components0.zip", "dask_preload.py"]: - if os.path.isfile(fname): - os.remove(fname) From 04b4729827e7d5858efdf251399bc277b13c7f8d Mon Sep 17 00:00:00 2001 From: Ross Whitfield Date: Fri, 30 Oct 2020 16:55:14 -0400 Subject: [PATCH 2/2] Remove simyan branch from docs --- doc/component_guides/component_guides.rst | 39 -- doc/getting_started/getting_started_new.rst | 340 ------------ doc/getting_started/thermal_profiles.png | Bin 92634 -> 0 bytes doc/index.rst | 3 - doc/user_guides/advanced_guide_new.rst | 85 --- doc/user_guides/config_file.rst | 4 - doc/user_guides/config_file_new.rst | 549 -------------------- doc/user_guides/platform.rst | 16 - doc/user_guides/user_guides.rst | 8 - 9 files changed, 1044 deletions(-) delete mode 100644 doc/component_guides/component_guides.rst delete mode 100644 doc/getting_started/getting_started_new.rst delete mode 100644 doc/getting_started/thermal_profiles.png delete mode 100644 doc/user_guides/advanced_guide_new.rst delete mode 100644 doc/user_guides/config_file_new.rst diff --git a/doc/component_guides/component_guides.rst b/doc/component_guides/component_guides.rst deleted file mode 100644 index 2fb5e5a9..00000000 --- a/doc/component_guides/component_guides.rst +++ /dev/null @@ -1,39 +0,0 @@ -Component Guides -================ - -This directory contains documentation for the use and development of the various components. - -To component documentation writers: - Documentation should include - - * Information on how to use components in the IPS - - * config file settings - * other component inputs - * component outputs - * point(s) of contact - * location of relevant files - * settings and modes of execution - * execution characteristics or limitations - * (brief) description of the role it plays in a coupled simulation - - * Pointers - - * underlying application documentation - * other relevant information - -List of Components: - - * Monitor Component - (location of documentation) (brief description) - * Portal Bridge Component - (location of documentation) (brief description) - * Runspace Init Component - (location of documentation) (brief description) - * FTB Bridge Component - (location of documentation) (brief description) - * AORSA - (location of documentation) (brief description) - * TSC - (location of documentation) (brief description) - * NUBEAM - (location of documentation) (brief description) - * GENRAY - (location of documentation) (brief description) - - -.. toctree:: - :hidden: - diff --git a/doc/getting_started/getting_started_new.rst b/doc/getting_started/getting_started_new.rst deleted file mode 100644 index 03e83802..00000000 --- a/doc/getting_started/getting_started_new.rst +++ /dev/null @@ -1,340 +0,0 @@ -Getting Started with the Simyan Branch -====================================== - -This document will guide you through the process of running an IPS -simulation and describe the overall structure of the IPS. It is -designed to help you build and run your first IPS simulation. It will -serve as a tutorial on how to get, build, and run your first IPS -simulation, but not serve as a general reference for constructing and -running IPS simulations. See the :doc:`Basic User -Guides<../user_guides/user_guides>` for a handy reference on running and -constructing simulations in general, and for more in-depth explanations -of how and why the IPS works. - -^^^^^^^^^^^^^^^^^^^ -Dependencies -^^^^^^^^^^^^^^^^^^^ - -**IPS Proper** - -The valtools repo will build the entire dependency tree for IPS, -related repos, and the dependencies using Bilder_. See the main -documentation for the dependencies. - -For more inf - -**Portal** - -The portal is a web interface for monitoring IPS runs and requires only -a connection to the internet and a web browser. Advanced features on -the portal require an OpenID account backed by ORNL's XCAMS. -Information on getting an XCAMS backed OpenID can be found on the SWIM -website. There are also visualization utilities that can be accessed -that require Elvis_ or PCMF (see below). - -:::::::::::::::: -Other Utilities -:::::::::::::::: - -**PCMF** - This utility generates plots from monitor component files for visual analysis of runs. It can be run locally on your machine and generates plots like this one of the thermal profiles of an ITER run: - - Requires: Matplotlib_ (which requires `Numpy/Scipy`_) - - - .. image:: thermal_profiles.png - :alt: thermal profiles of an ITER run - -**Resource Usage Simulator (RUS)** - This is a utility for simulation the execution of tasks in the IPS - for research purposes. - - Requires: Matplotlib_ (which requires `Numpy/Scipy`_) - -**Documentation** - The documentation you are reading now was created by a Python-based - tool called Sphinx. - - Requires: Sphinx_ (which requires docutils_) - - -***Plus*** anything that the components or underlying codes that you are using need (e.g., MPI, math libraries, compilers). For the example in this tutorial, all packages that are needed are already available on the target machines and the shell configuration script sets up your environment to use them. - -.. _Sphinx: http://sphinx.pocoo.org/ -.. _Matplotlib: http://matplotlib.sourceforge.net/ -.. _Numpy/Scipy: http://numpy.scipy.org/ -.. _Elvis: http://w3.pppl.gov/elvis/ -.. _docutils: http://docutils.sourceforge.net/ -.. _ConfigObj: http://www.voidspace.org.uk/python/configobj.html -.. _Python: http://python.org/ -.. _processing: http://pypi.python.org/pypi/processing -.. _multiprocessing: http://docs.python.org/library/multiprocessing.html -.. _Bilder: https://ice.txcorp.com/trac/bilder - -===================================================== -Building and Setting up Your Environment Using Bilder -===================================================== - -The IPS code is currently located in the SWIM project's Subversion (SVN) -repository. In this documentation, we will discuss using it from the -Tech-X repos to discuss the new repos. IPS is currently part of the -Simyan repo which is svn:external'd from the ValtoolsAll repo. The -valtoolsall repo is for enabling a simplified environment for python -packages. To check out the valtoolsall repo:: - - svn co https://ice.txcorp.com/svnrepos/code/valtoolsall/trunk valtoolsall - -Using bilder and the valtoolsall project:: - - cd valtoolsall - ./mkvaltoolsall-default.sh -n - -After running bilder, the output file will tell you where things are -installed () and where things are built (). To -configure your environment, you need to source the configuration files. -For bash users:: - - source /valtoolsall.sh - -For tcsh users:: - - source /valtoolsall.csh - -To test the builds:: - - cd /simyan/ser - make test - -This will run all of the tests. The documentation, assuming the `-D` -flag was passed to bilder, will be in the webdocs build directory -(`/simyan/webdocs`). - - -=========================================================== -Building and Setting up Your Environment direct from repo -=========================================================== - -Assuming you have the dependencies installed, you can jst check out the -repo and configure directly. To obtain the rep:: - - https://ice.txcorp.com/svnrepos/code/simyan/trunk ips - -#. Assuming your dependencies are installed in /usr/local or /contrib, - you can configure the file in a build subdirectory - -:: - - mkdir build - cd build - cmake \ - -DCMAKE_INSTALL_PREFIX:PATH=$INSTALL_DIR/simyan \ - -DCMAKE_BUILD_TYPE:STRING=RELEASE \ - -DCMAKE_VERBOSE_MAKEFILE:BOOL=TRUE \ - -DCMAKE_INSTALL_ALWAYS:BOOL=TRUE \ - -DSUPRA_SEARCH_PATH='/usr/local;/contrb' \ - -DENABLE_WEBDOCS:BOOL=ON \ - -DMPIRUN:STRING=aprun \ - -DNODE_DETECTION:STRING=manual \ - -DCORES_PER_NODE:INTEGER=4 \ - -DSOCKETS_PER_NODE:INTEGER=2 \ - -DNODE_ALLOCATION_MODE:SHARED=shared \ - $PWD/.. - -# After configuring, to build IPS, the documentation, and run the tests -respectively - -:: - - make - make docs - make test - make install - -The documentation may be found at docs/html/index.html. The -tests are located in the tests subdirectory. - -Now you are ready to set up your configuration files, and run simulations. - - -=================================== -Running Your First IPS Simulations -=================================== - -This section will take you step-by-step through running a "hello world" example -and a "model physics" example. These examples contain all of the -configuration, batch script, component, executables and input files needed to -run them. To run IPS simulations in general, these will need to be borrowed, -modified or created. See the :doc:`Basic User -Guides<../user_guides/user_guides>` for more information. - -Before getting started, you will want to make sure you have a copy of the ips checked out and built on either Franklin or Stix. - - On **Franklin** you will want to work in your ``$SCRATCH`` directory and move to having the output from more important runs placed in the ``/project/projectdirs/m876/*`` directory. - - On **Stix** you will want to work in a directory within ``/p/swim1/`` that you own. You can keep important runs there or in ``/p/swim1/data/``. - -^^^^^^^^^^^^^^^^^^^^ -Hello World Example -^^^^^^^^^^^^^^^^^^^^ - -This example simply uses the IPS to print "Hello World," using a single driver -component and worker component. The driver component (hello_driver.py) invokes -the worker component (hello_worker.py) that then prints a message. The -implementations of these components reside in -``ips/components/drivers/hello/``, if you would like to examine them. In this -example, the *call()* and *launch_task()* interfaces are demonstrated. In this -tutorial, we are focusing on running simulations and will cover the internals -of components and constructing simulation scenarios in the various User Guides -(see :doc:`Index<../index>`). - -1. Copy the following files to your working directory: - - * Configuration file:: - - /ips/doc/examples/hello_world.config - - * Batch script:: - - /ips/doc/examples//sample_batchscript. - -2. Edit the configuration file: - - * Set the location of your web-enabled directory for the portal to watch and for you to access your data via the portal. If you do not have a web-enabled directory, you will have to create one using the following convention: on Franklin: ``/project/projectdirs/m876/www/``; on Stix: ``/p/swim/w3_html/``. - - Franklin:: - - USER_W3_DIR = /project/projectdirs/m876/www/ - USER_W3_BASEURL = http://portal.nersc.gov/project/m876/ - - Stix:: - - USER_W3_DIR = /p/swim/w3_html/ - USER_W3_BASEURL = http://w2.pppl.gov/swim/ - - This step allows the framework to talk to the portal, and for the portal to access the data generated by this run. - - * Edit the *IPS_ROOT* to be the absolute path to the IPS checkout that you built. This tells the framework where the IPS scripts are:: - - IPS_ROOT = /path/to/ips - - - * Edit the *SIM_ROOT* to be the absolute path to the output tree that will be generated by this simulation. Within that tree, there will be work directories for each of the components to execute for each time step, along with other logging files. For this example you will likely want to place the *SIM_ROOT* as the directory where you are launching your simulations from, and name it using the *SIM_NAME*:: - - SIM_ROOT = /current/path/${SIM_NAME} - - * Edit the *USER* entry that is used by the portal, identifying you as the owner of the run:: - - USER = - - -3. Edit the batch script such that *IPS_ROOT* is set to the location of your IPS checkout:: - - IPS_ROOT=/path/to/ips - -4. Launch batch script:: - - head_node: ~ > qsub hello_batchscript. - - -Once your job is running, you can monitor is on the portal_. - -.. image:: swim_portal.png - :alt: Screen shot of SWIM Portal - -When the simulation has finished, the output file should contain:: - - Starting IPS - Created - Created - HelloDriver: beginning step call - Hello from HelloWorker - HelloDriver: finished worker call - -^^^^^^^^^^^^^^^^^^^^^^ -Model Physics Example -^^^^^^^^^^^^^^^^^^^^^^ - -This simulation is intended to look almost like a real simulation, short of requiring actual physics codes and input data. Instead typical simulation-like data is generated from simple analytic (physics-less) models for most of the plasma state quantities that are followed by the *monitor* component. This "model" simulation includes time stepping, time varying scalars and profiles, and checkpoint/restart. - -The following components are used in this simulation: - - * ``minimal_state_init.py`` : simulation initialization for this model case - * ``generic_driver.py`` : general driver for many different simulations - * ``model_epa_ps_file_init.py`` : model equilibrium and profile advance component that feeds back data from a file in lieu of computation - * ``model_RF_IC_2_mcmd.py`` : model ion cyclotron heating - * ``model_NB_2_mcmd.py`` : model neutral beam heating - * ``model_FUS_2_mcmd.py`` : model fusion heating and reaction products - * ``monitor_comp.py`` : real monitor component used by many simulations that helps with processing of data and visualizations that are produced after a run - -First, we will run the simulation from time 0 to 20 with checkpointing turned on, and then restart it from a checkpoint taken at time 12. - -1. Copy the following files to your working directory: - - * Configuration files:: - - /ips/doc/examples/seq_model_sim.config - /ips/doc/examples/restart_12_sec.config - - * Batch scripts:: - - /ips/doc/examples/model_sim_bs. - /ips/doc/examples/restart_bs. - -2. Edit the configuration files (you will need to do this in BOTH files, unless otherwise noted): - - * Set the location of your web-enabled directory for the portal to watch and for you to access your data via the portal. - - Franklin:: - - USER_W3_DIR = /project/projectdirs/m876/www/ - USER_W3_BASEURL = http://portal.nersc.gov/project/m876/ - - Stix:: - - USER_W3_DIR = /p/swim/w3_html/ - USER_W3_BASEURL = http://w2.pppl.gov/swim/ - - This step allows the framework to talk to the portal, and for the portal to access the data generated by this run. - - * Edit the *IPS_ROOT* to be the absolute path to the IPS checkout that you built. This tells the framework where the IPS scripts are:: - - IPS_ROOT = /path/to/ips - - - * Edit the *SIM_ROOT* to be the absolute path to the output tree that will be generated by this simulation. Within that tree, there will be work directories for each of the components to execute for each time step, along with other logging files. For this example you will likely want to place the *SIM_ROOT* as the directory where you are launching your simulations from, and name it using the *SIM_NAME*:: - - SIM_ROOT = /current/path/${SIM_NAME} - - * Edit the *RESTART_ROOT* in ``restart_12_sec.config`` to be the *SIM_ROOT* of ``seq_model_sim.config``. - - * Edit the *USER* entry that is used by the portal, identifying you as the owner of the run:: - - USER = - - -3. Edit the batch script such that *IPS_ROOT* is set to the location of your IPS checkout:: - - IPS_ROOT=/path/to/ips - -4. Launch batch script for the original simulation:: - - head_node: ~ > qsub model_sim_bs. - - -Once your job is running, you can monitor is on the portal_ and it should look like this: - -.. image:: swim_portal_orig.png - :alt: Screenshot of model run - -When the simulation has finished, you can run the restart version to restart the simulation from time 12:: - - head_node: ~ > qsub restart_bs. - -The job on the portal should look like this when it is done: - -.. image:: swim_portal_restart.png - :alt: Screenshot of restart run - - -.. _Franklin: http://www.nersc.gov/users/computational-systems/franklin/ -.. _portal: http://swim.gat.com:8080/display/ diff --git a/doc/getting_started/thermal_profiles.png b/doc/getting_started/thermal_profiles.png deleted file mode 100644 index fb5f9b8724951eb900afba0964edc5ef2b6d5470..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 92634 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

H- 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 FV1ft**;oJo diff --git a/doc/index.rst b/doc/index.rst index 6bfe357f..31305e6e 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -13,10 +13,7 @@ Contents: intro getting_started/getting_started - getting_started/getting_started_new user_guides/user_guides - component_guides/component_guides - the_code Indexes and tables diff --git a/doc/user_guides/advanced_guide_new.rst b/doc/user_guides/advanced_guide_new.rst deleted file mode 100644 index a75f30b0..00000000 --- a/doc/user_guides/advanced_guide_new.rst +++ /dev/null @@ -1,85 +0,0 @@ -Developing Drivers and Components for IPS Simulations -===================================================== - -This section is for those who wish to modify and write drivers and components to construct a new simulation scenario. It is expected that readers are familiar with IPS terminology, the directory structure and have looked at some existing drivers and components before attempting to modify or create new ones. This guide will describe the elements of a simulation, how they work together, the structure of drivers and components, IPS services API, and a discussion of control flow, data flow and fault management. - --------------------------- -Elements of a Simulation --------------------------- - -When constructing a new simulation scenario, writing a new component or even making small modifications to existing components and drivers, it is important to consider and understand how the pieces of an IPS simulation work together. An IPS simulation scenario is specified in the *configuration file*. This file tells the framework how to set up the output tree for the data files, which components are needed and where the implementation is located, time loop and checkpoint parameters, and input and output files for each component and the simulation as a whole are specified. The *framework* uses this information to find the pieces of code and data that come together to form the simulation, as well as provide this information to the components and driver to manage the simulation and execution of tasks. - -The framework provides *services* that are used by components to perform data, task, resource and configuration management, and provides an event service for exchanging messages with internal and external entities. While these services are provided as a single API to component writers, the documentation (and underlying implementation) divides them into groups of methods to perform related actions. *Data management* services include staging input, output and plasma state files, changing directories, and saving task restart files, among others. The framework will perform these actions for the calling component based on the files specified in the configuration file and within the method call maintaining coherent directory spaces for each component's work, previous steps, checkpoints and globally accessible data to insure that name collisions do not corrupt data and that global files are accessed in a well-defined manner. - -Services for *task management* include methods for component method invocations, or *calls*, and executable launch on compute nodes, or *task launches*. The task management portion of the framework works in conjunction with the IPS resource manager to execute multiple parallel executables within a single batch allocation, allowing IPS simulations to efficiently utilize compute resources, as data dependencies allow. The IPS task manager provides blocking and non-blocking versions of ``call`` and ``launch_task``, including a notion of *task pools* and the ability to wait for the completion of any or all calls or tasks in a group. These different invocation and launch methods allow a component writer to manage the control flow and implement data dependencies between components and tasks. - -This task management interface hides the resource management, platform specific, task scheduling, and process interactions that are performed by the framework, allowing component writers to express their simulations and component coupling more simply. The *configuration manager* primarily reads the configuration file and instantiates the components for the simulation so that they can interact over the course of the simulation. It also provides an interface for accessing key data elements from the configuration file, such as the time loop, handles to components and any component specific items listed in the configuration file. - ------------------ -Workflow Concepts ------------------ - -The following sections provide descriptions for how to use IPS to manage complex workflows and multiple runs. - -^^^^^^^^^^^^^^^^^^^^^^ -Creating New Runspaces -^^^^^^^^^^^^^^^^^^^^^^ - -Creating new runspaces from a simulation configuration file is simple by invoking the following from the command line: - - ``ips.py --create-runspace --simulation=simulation_conf.ips`` - -This will create a new directory using the simulation contained in the ``SIM_NAME`` variable. Within this directory, a subdirectory named ``work`` will be created that will house the working directories for all of the components used in the simulation. In addition, a directory named ``simulation_setup`` will be created that houses the component scripts for the simulation. - -All configuration files, the ``platform.conf`` and ``simulation_conf.ips`` files, will be copied into the simulation base directory. Additionally, any data files that are needed by the simulation's components will be staged into the working directories for their respective components. - -^^^^^^^^^ -Run-setup -^^^^^^^^^ - -After a runspace has been created, additional tasks may need to be performed in the parsing of input files, interpolating of data, or trasforming of datapoints between coordinate spaces. These steps may be time or computation intensive, and consist of all of the data preparation that is necessary before a run can begin. As such, these steps should be relegated to a run-setup stage of a workflow. - -Run-setup steps that need to be performed should be contained in the ``init()`` implementation of simulation driver component. To perform only the run-setup step, invoke the following on the command line: - - ``ips.py --run-setup --simulation=simulation_conf.ips`` - -This will invoke only the ``driver.init()`` method, which will perform only run-setup related tasks. - -^^^^^^^ -Running -^^^^^^^ - -Once run-setup has been completed, a run can be performed by invoking the following on the command line: - - ``ips.py --run --simulation=simulation_conf.ips`` - -This will invoke the ``step()`` and ``finalize()`` methods on the driver component, which will execute the run. - -^^^^^^^ -Cloning -^^^^^^^ - -All of the inputs necessary to duplicate a run are stored in a container file named with the ``SIM_NAME`` variable. Given a container file, a clone of a run can be created by invoking the following on the command line: - - ``ips.py --clone=simulation.ctz --sim_name=new_simulation`` - -This creates a new simulation_conf.ips with the name passed in the ``sim_name`` command line option.The Framework will open the container file, copy the ``simulation_conf.ips`` file contained within, replace the ``SIM_NAME`` value in this file with the passed in ``sim_name`` value, and unzip the required files into the new directory. - -^^^^^^^^^^^^^ -Multiple Runs -^^^^^^^^^^^^^ - -From a single command line invocation, multiple runs can be performed by utilizing comma-separated values. In the following example: - - ``ips.py --create-runspace --simulation=a.ips,b.ips`` - -Two simulation files, ``a.ips`` and ``b.ips``, are used to create runspaces for two new simulations. - -The Framework can also allow the handling of multiple clones, as in the following example: - - ``ips.py --clone=a.ctz,b.ctz --sim_name=x,y`` - -Two container files ``a.ctz`` and ``b.ctz`` are used to clone two simulations ``a`` and ``b``, and the clones are renamed ``x`` and ``y`` respectively. - - - diff --git a/doc/user_guides/config_file.rst b/doc/user_guides/config_file.rst index b5f1efbc..7ef6677a 100644 --- a/doc/user_guides/config_file.rst +++ b/doc/user_guides/config_file.rst @@ -26,10 +26,6 @@ It is possible for the configuration file to override entries in the platform co #HOST = #MPIRUN = -#PHYS_BIN_ROOT = -#DATA_TREE_ROOT = -#PORTAL_URL = -#RUNID_URL = #NODE_ALLOCATION_MODE = diff --git a/doc/user_guides/config_file_new.rst b/doc/user_guides/config_file_new.rst deleted file mode 100644 index f298ba9c..00000000 --- a/doc/user_guides/config_file_new.rst +++ /dev/null @@ -1,549 +0,0 @@ -=================================== -The Configuration Files - Explained -=================================== - -This section will detail the different sections and fields of the -configuration file and how they relate to a simulation. The -configuration file is designed to let the user to easily set data items -used by the framework, components, tasks, and the portal from run to -run. There are user specific, platform specific, and component specific -entries that need to be modified or verified before running the IPS in -the given configuration. After a short overview of the syntax of the -package used by the framework to make sense of the configuration file, a -detailed explanation of each line of the configuration file is -presented. - -------------------------------- -Syntax and the ConfigObj module -------------------------------- - -ConfigObj_ is a Python package for reading and writing config files. -The syntax is similar to shell syntax (e.g., use of $ to reference -variables), uses square brackets to create named sections and nested -subsections, comma-separated lists and comments indicated by a "#". - -In the example configuration file below, curly braces (``{}``) are used -to clarify references to variables with underscores (``_``). Any -left-hand side value can be used as a variable after it is defined. -Additionally, any platform configuration value can be referenced as a -variable in the configuration file as well. - -.. _ConfigObj : http://www.voidspace.org.uk/python/configobj.html - - ----------------------------------- -The 3 files in a nutshell ----------------------------------- - -There are 3 types of configuration files: - - 1. `platform.conf` file: This specifies things specific for a platform file. - It changes very rarely and depends on the installation of that - platforms on the installation of that platform. - - 1. `component-*.conf` file: This specifies things specific to a component, or collection of components - - 1. simulation.ips file: This is changes from simulation-to-simulation - and is meant to be editted frequently. - -The files are parsed sequentially, and parameters from the previous -files can be overwritten in the subsequent files. Thus, it can be -important to understand how to locate your platform.conf file and know -the parameters in the file to understand how to override those values. -This is discussed further below. - ---------------------------- -Platform Configuration File ---------------------------- - -The platform configuration file contains platform specific information -that the framework needs. Typically it does not need to be changed for -one user to another or one run to another (except for manual -specification of allocation resources). For *most* of the platforms -above, you will find platform configuration files of the form -``/share/platform.conf`` (or if testing a build directory, -it will be -``/share/platform.conf`` - -:: - - ####################################### - # framework setting for node allocation - ####################################### - # MUST ADHERE TO THE PLATFORM'S CAPABILITIES - # * EXCLUSIVE : only one task per node - # * SHARED : multiple tasks may share a node - # For single node jobs, this can be overridden allowing multiple - # tasks per node. - NODE_ALLOCATION_MODE = EXCLUSIVE # SHARED | EXCLUSIVE - - ####################################### - # LOCATIONS - ####################################### - IPS_ROOT = /scratch/scratchdirs/skruger/software/simyan - PORTAL_URL = http://swim.gat.com:8080/monitor - RUNID_URL = http://swim.gat.com:4040/runid.esp - - ####################################### - # Parallel environment - ####################################### - MPIRUN = aprun - NODE_DETECTION = checkjob - CORES_PER_NODE = 4 - SOCKETS_PER_NODE = 1 - NODE_ALLOCATION_MODE = @NODE_ALLOCATION_MODE@ - - ####################################### - # Provenance - ####################################### - HOST = Franklin - USER = skruger - HOME = /u/global/skruger - SCRATCH = /scratch/scratchdirs/skruger - -**HOST** - name of the platform. Used by the portal. -**MPIRUN** - command to launch parallel applications. Used by the task - manager to launch parallel tasks on compute nodes. If you - would like to launch a task directly without the parallel - launcher (say, on a SMP style machine or workstation), set - this to "eval" -- it tells the task manager to directly launch the task as `` ``. -**\*_ROOT** - locations of data and binaries. Used by the configuration - file and components to run the tasks of the simulation. -**\*_URL** - portal URLs. Used to connect to and communicate with the - portal. -**NODE_DETECTION** - method to use to detect the number of nodes and processes in - the allocation. If the value is "manual," then the manual - allocation description is used. If nothing is specified, all - of the methods are attempted and the first one to succeed will - be used. Note, if the allocation detection fails, the - framework will abort, killing the job. -**CORES_PER_NODE** - number of cores per node [#nochange]_. -**SOCKETS_PER_NODE** - number of sockets per node [#nochange]_. -**NODE_ALLOCATION_MODE** - 'EXCLUSIVE' for one task per node, and 'SHARED' if more than - one task can share a node [#nochange]_. Simulations, - components and tasks can set their node usage allocation - policies in the configuration file and on task launch. - - -.. [#nochange] This value should not change unless the machine is - upgraded to a different architecture or implements different - allocation policies. - -.. [#manual_alloc_ppn] Used in manual allocation detection and will - override any detected ppn value (if smaller than the machine - maximum ppn). - -.. [#manual_alloc_node] Only used if manual allocation is specified, - or if no detection mechanism is specified and none of the other - mechansims work first. It is the *users* responsibility for this - value to make sense. - -.. note : the node allocation and detection values in this file can be overriden by command line options to the ips ``--nodes`` and ``--ppn``. *Both* values must be specified, otherwise the platform configuration values are used. - ----------------------------------- -Components file ----------------------------------- - -The component configuration file contains information on where to -find the location of physics binaries for a given collection of -components. The purpose is to enable flexibility in installing -components in different locations; e.g., either for a different -collection, or for a different set of builds (pathscale versus pgi -for example. They can be found at: -``/share/component-generic.conf`` (or if testing a build directory, -it will be -``/share/component-generic.conf`` -They should be renamed according to the component collection; e.g., -``/share/component-facets.conf``. One can then specify the -component collection at runtime (by default, it will use the generic -one). - -An example component collection will look like this:: - - ####################################### - # CONTAINTER FILE - ####################################### - CONTAINER_FILE_EXT = .ctz - - ####################################### - # LOCATIONS - ####################################### - BIN_PATH = /scratch/scratchdirs/ssfoley/ips/bin - PHYS_BIN_ROOT = /project/projectdirs/m876/bin - DATA_TREE_ROOT = /project/projectdirs/m876/data - -These are not strictly required, but rather are useful convention for -use in the simulation files as discussed below - ------------------------------------ -Simulation file File - Line by Line ------------------------------------ - -User Data Section -~~~~~~~~~~~~~~~~~ - -The following items are specific to the user and should be changed -accordingly. They will help you to identify your runs in the portal -(*USER*), and also store the data from your runs in particular -web-enabled locations for post-processing (*USER_W3_DIR* on the local -machine, *USER_W3_BASEURL* on the portal). All of the items in this -section are optional. - -:: - - USER_W3_DIR = /project/projectdirs/m876/www/ssfoley - USER_W3_BASEURL = http://portal.nersc.gov/project/m876/ssfoley - USER = ssfoley # Optional, if missing the unix username is used - - -Simulation Information Section -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -These items describe this configuration and is used for describing and -locating its output, information for the portal, and location of the -source code of the IPS. - -\*\* Mandatory items: *SIM_ROOT*, *SIM_NAME*, *LOG_FILE*, *RUN_COMMENT* - -*RUN_ID*, *TOKOMAK_ID*, *SHOT_NUMBER* - identifiers for the simulation -that are helpful for SWIM users. They ore often used to form a -hierarchical name for the simulation, identifying related runs. - -*OUTPUT_PREFIX* - used to prevent collisions and overwriting of -different simulations using the same *SIM_ROOT*. - -*SIM_NAME* - used to identify the simulation on the portal, and often to -name the output tree. - -*SIM_ROOT* - location of output tree, which should include the SIM_NAME -(see below). This directory will be created if -it does not exist. If the directory already exists, then data files -will be added, possibly overwriting existing data. - -*LOG_FILE* - name of the log file for this simulation. The framework -log file is specified at the command line. - -*LOG_LEVEL* - sets the logging level for the simulation. If empty, the -framework log level is used, which defaults to *WARNING*. See -:ref:`logging-api` for details on the logging capabilities in the IPS. -Possible values: *DEBUG*, *INFO*, *WARNING*, *ERROR*, *EXCEPTION*, -*CRITICAL*. - - -In the example below, we show how to use non-needed variables (e.g., -RUN_ID) to construct needed variables. Considerable flexibility is -provided to the user. - -:: - - RUN_ID = Model_seq # Identifier for this simulation run - TOKAMAK_ID = ITER - SHOT_NUMBER = 1 # Identifier for specific case for this tokamak - - SIM_NAME = ${RUN_ID}_${TOKAMAK_ID}_${SHOT_NUMBER} - # Simulation root - path of the simulation directory that will be constructed - # by the framework - SIM_ROOT = ${SCRATCH}/${SIM_NAME} # Scratch comes from platform.conf - - OUTPUT_PREFIX = - LOG_FILE = ${RUN_ID}_sim.log - LOG_LEVEL = DEBUG # Default = WARNING - - # Description of the simulation for the portal - SIMULATION_DESCRIPTION = sequential model simulation using generic driver.py - RUN_COMMENT = sequential model simulation using generic driver.py - TAG = sequential_model # for grouping related runs - - -Simulation Mode -~~~~~~~~~~~~~~~ - -This section describes the mode in which to run the simulation. All values are optional. - -*SIMULATION_MODE* - describes whether the simulation is starting from -*init* (*NORMAL*) or restarting from a checkpoint (*RESTART*). The -default is *NORMAL*. For RESTART, a restart time and directory must be -specified. These values are used by the driver to control how the -simulation is initialized. *RESTART_TIME* must coincide with a -checkpoint save time. *RESTART_DIRECTORY* may be $SIM_ROOT if there is -an existing current simulation there, and the new work will be appended, -such that it looks like a seamless simulation. - -:: - - SIMULATION_MODE = NORMAL # NORMAL | RESTART - RESTART_TIME = 12 # time step to restart from - RESTART_ROOT = ${SIM_ROOT} - - -**Parallel Parameters** - - -**TOTAL_PROCS** - number of processes in the allocation [#manual_alloc_node]_. -**NODES** - number of nodes in the allocation [#manual_alloc_node]_. -**PROCS_PER_NODE** - number of processes per node (ppn) for the framework - [#manual_alloc_ppn]_. -**Platform Configuration Override Section** - It is possible for the configuration file to override entries in the platform configuration file. It is rare and users should use caution when overriding these values. See :doc:`Platform Configuration File - Explained` for details on these values. - -**Plasma State Section** - -The locations and names of the plasma state files are specified here, along with the directory where the global plasma state files are located in the simulation tree. It is common to specify groups of plasma state files for use in the component configuration sections. These files should contain all the shared data values for the simulation so that they can be managed by the driver. - -:: - - PLASMA_STATE_WORK_DIR = ${SIM_ROOT}/work/plasma_state - - # Config variables defining simulation specific names for plasma state files - CURRENT_STATE = ${SIM_NAME}_ps.cdf - PRIOR_STATE = ${SIM_NAME}_psp.cdf - NEXT_STATE = ${SIM_NAME}_psn.cdf - CURRENT_EQDSK = ${SIM_NAME}_ps.geq - CURRENT_CQL = ${SIM_NAME}_ps_CQL.dat - CURRENT_DQL = ${SIM_NAME}_ps_DQL.nc - CURRENT_JSDSK = ${SIM_NAME}_ps.jso - - # List of files that constitute the plasma state - PLASMA_STATE_FILES1 = ${CURRENT_STATE} ${PRIOR_STATE} ${NEXT_STATE} ${CURRENT_EQDSK} - PLASMA_STATE_FILES2 = ${CURRENT_CQL} ${CURRENT_DQL} ${CURRENT_JSDSK} - PLASMA_STATE_FILES = ${PLASMA_STATE_FILES1} ${PLASMA_STATE_FILES2} - - -**Ports Section** - -The ports section identifies which ports and their associated -implementations that are to be used for this simulation. The ports -section is defined by ``[PORTS]``. *NAMES* is a list of port names, -where each needs to appear as a subsection (e.g., ``[[DRIVER]]``). Each -port definition section must contain the entry *IMPLEMENTATION* whose -value is the name of a component definition section. These are case -sensitive names and should be named such that someone familiar the -components of this project has an understanding of what is being -modeled. The only mandatory port is *DRIVER*. It should be named -*DRIVER*, but the implementation can be anything, as long as it is -defined. If no *INIT* port is defined, then the framework will produce -a warning to that effect. There may be more port definitions than -listed in *NAMES*. - -:: - - [PORTS] - NAMES = INIT DRIVER MONITOR EPA - - # Required ports - DRIVER and INIT - [[DRIVER]] - IMPLEMENTATION = GENERIC_DRIVER - - [[INIT]] - IMPLEMENTATION = minimal_state_init - - [[MONITOR]] - IMPLEMENTATION = monitor_comp_4 - - # Physics ports - [[EPA]] - IMPLEMENTATION = model_EPA - - -**Component Configuration Section** - -Component definition and configuration is done in this "section." Each -component configuration section is defined as a section (e.g., -``[model_EPA]``). Each entry in the component configuration section -is available to the component at runtime using that name (e.g., -*self.NPROC*), thus these values can be used to create specific -simulation cases using generic components. Variables defined within a -component configuration section are local to that section, but values -may be defined in terms of the simulation values defined above (e.g., -*PLASMA_STATE_FILES*, and *IPS_ROOT*). - -\*\* Mandatory entries: *SCRIPT*, *NAME*, *BIN_PATH*, *INPUT_DIR* - -*CLASS* - commonly this is the port name or the first directory name in -the path to the component implementation in ``ips/components/``. - -*SUB_CLASS* - commonly this is the name of the code or method used to -model this port, or the second directory name in the path to the -component implementation in ``ips/components/``. - -*NAME* - name of the class in the Python script that implements this component. - -*NPROC* - number of processes on which to launch tasks. - -*BIN_PATH* - path to script and any other helper scripts and binaries. This is used by the framework and component to find and execute helper scripts and binaries. - -*BINARY* - the binary to launch as a task. Typically, these binaries are found in the - -*PHYS_BIN* or some subdirectory therein. Otherwise, you can make your own variable and put the directory where the binary is located there. - -*INPUT_DIR* - directory where the input files (listed below) are found. -This is used during initialization to copy the input files to the work -directory of the component. - -*INPUT_FILES* - list of files (relative to *INPUT_DIR*) that need to be -copied to the component work directory on initialization.
 -*OUTPUT_FILES* - list of output files that are produced that need to be -protected and archived on a call to -:py:meth:`services.ServicesProxy.stage_output_files`. - -*PLASMA_STATE_FILES* - list of plasma state files used and modified by -this component. If not present, then the files specified in the -simulation entry *PLASMA_STATE_FILES* is used. - -*RESTART_FILES* - list of files that need to be archived as the -checkpoint of this component. - -*NODE_ALLOCATION_MODE* - sets the default execution mode for tasks in -this component. If the value is *EXCLUSIVE*, then tasks are assigned -whole nodes. If the value is *SHARED*, sub-node allocation is used so -tasks can share nodes thus using the allocation more efficiently. If no -value or entry is present, the simulation value for -*NODE_ALLOCATION_MODE* is used. It is the users responsibility to -understand how node sharing will impact the performance of their tasks. -This can be overridden using the *whole_nodes* and *whole_sockets* -arguments to :py:meth:`services.ServicesProxy.launch_task`. - -Additional values that are specific to the component may be added as -needed, for example certain data values like *PPN*, paths to and names -of other executables used by the component or alternate *NPROC* values -are examples. It is the responsibility of the component writer to make -sure users know what values are required by the component and what the -valid values are for each. - -:: - - [model_EPA] - CLASS = epa - SUB_CLASS = model_epa - NAME = model_EPA - NPROC = 1 - BIN_PATH = ${IPS_ROOT}/bin - INPUT_DIR = ${DATA_TREE_ROOT}/model_epa/ITER/hy040510/t20.0 - INPUT_STATE_FILE = hy040510_002_ps_epa__tsc_4_20.000.cdf - INPUT_EQDSK_FILE = hy040510_002_ps_epa__tsc_4_20.000.geq - INPUT_FILES = model_epa_input.nml ${INPUT_STATE_FILE} ${INPUT_EQDSK_FILE} - OUTPUT_FILES = internal_state_data.nml - PLASMA_STATE_FILES = ${CURRENT_STATE} ${NEXT_STATE} ${CURRENT_EQDSK} - RESTART_FILES = ${INPUT_FILES} internal_state_data.nml - SCRIPT = ${BIN_PATH}/model_epa_ps_file_init.py - - [monitor_comp_4] - CLASS = monitor - SUB_CLASS = - NAME = monitor - NPROC = 1 - W3_DIR = ${USER_W3_DIR} # Note this is user specific - W3_BASEURL = ${USER_W3_BASEURL} # Note this is user specific - TEMPLATE_FILE= basic_time_traces.xml - BIN_PATH = ${IPS_ROOT}/bin - INPUT_DIR = ${IPS_ROOT}/components/monitor/monitor_4 - INPUT_FILES = basic_time_traces.xml - OUTPUT_FILES = monitor_file.nc - PLASMA_STATE_FILES = ${CURRENT_STATE} - RESTART_FILES = ${INPUT_FILES} monitor_restart monitor_file.nc - SCRIPT = ${BIN_PATH}/monitor_comp.py - - -**Checkpoint Section** - -This section describes when checkpoints should be taken by the -simulation. Drivers should be written such that at the end of each step -there is a call to -:py:meth:`services.ServicesProxy.checkpoint_components`. This way the -services use the settings in this section to either take a checkpoint or -not. - -Selectively checkpoint components in *comp_id_list* based on the -configuration section *CHECKPOINT*. If *Force* is ``True``, the -checkpoint will be taken even if the conditions for taking the -checkpoint are not met. If *Protect* is ``True``, then the data from -the checkpoint is protected from clean up. *Force* and *Protect* are -optional and default to ``False``. - -The *CHECKPOINT_MODE* option controls determines if the components -checkpoint methods are invoked. Possible *MODE* options are: - -WALLTIME_REGULAR: - checkpoints are saved upon invocation of the service call - ``checkpoint_components()``, when a time interval greater than, or - equal to, the value of the configuration parameter - WALLTIME_INTERVAL had passed since the last checkpoint. A - checkpoint is assumed to have happened (but not actually stored) - when the simulation starts. Calls to ``checkpoint_components()`` - before WALLTIME_INTERVAL seconds have passed since the last - successful checkpoint result in a NOOP. - -WALLTIME_EXPLICIT: - checkpoints are saved when the simulation wall clock time exceeds - one of the (ordered) list of time values (in seconds) specified in - the variable WALLTIME_VALUES. Let [t_0, t_1, ..., t_n] be the list - of wall clock time values specified in the configuration parameter - WALLTIME_VALUES. Then checkpoint(T) = True if T >= t_j, for some j - in [0,n] and there is no other time T_1, with T > T_1 >= T_j such - that checkpoint(T_1) = True. If the test fails, the call results - in a NOOP. - -PHYSTIME_REGULAR: - checkpoints are saved at regularly spaced - "physics time" intervals, specified in the configuration parameter - PHYSTIME_INTERVAL. Let PHYSTIME_INTERVAL = PTI, and the physics - time stamp argument in the call to checkpoint_components() be - pts_i, with i = 0, 1, 2, ... Then checkpoint(pts_i) = True if - pts_i >= n PTI , for some n in 1, 2, 3, ... and - pts_i - pts_prev >= PTI, where checkpoint(pts_prev) = True and - pts_prev = max (pts_0, pts_1, ..pts_i-1). If the test fails, the - call results in a NOOP. - -PHYSTIME_EXPLICIT: - checkpoints are saved when the physics time - equals or exceeds one of the (ordered) list of physics time values - (in seconds) specified in the variable PHYSTIME_VALUES. Let [pt_0, - pt_1, ..., pt_n] be the list of physics time values specified in - the configuration parameter PHYSTIME_VALUES. Then - checkpoint(pt) = True if pt >= pt_j, for some j in [0,n] and there - is no other physics time pt_k, with pt > pt_k >= pt_j such that - checkpoint(pt_k) = True. If the test fails, the call results in a - NOOP. - -The configuration parameter NUM_CHECKPOINT controls how many -checkpoints to keep on disk. Checkpoints are deleted in a FIFO manner, -based on their creation time. Possible values of NUM_CHECKPOINT are: - - * NUM_CHECKPOINT = n, with n > 0 --> Keep the most recent n checkpoints - * NUM_CHECKPOINT = 0 --> No checkpoints are made/kept (except when *Force* = ``True``) - * NUM_CHECKPOINT < 0 --> Keep ALL checkpoints - -Checkpoints are saved in the directory ``${SIM_ROOT}/restart`` - -:: - - [CHECKPOINT] - MODE = WALLTIME_REGULAR - WALLTIME_INTERVAL = 15 - NUM_CHECKPOINT = 2 - PROTECT_FREQUENCY = 5 - -**Time Loop Section** - -The time loop specifies how time progresses for the simulation in the driver. It is not required by the framework, but may be required by the driver. Most simulations use the time loop section to specify the number and frequency of time steps for the simulation as opposed to hard coding it into the driver. It is a helpful tool to control the runtime of each step and the overall simulation. It can also be helpful when looking at a small portion of time in the simulation for debugging purposes. - -*MODE* - defines the following entries. If mode is *REGULAR* -- *START*, *FINISH* and *NSTEP* are used to generate a list of times of length *NSTEP* starting at *START* and ending at *FINISH*. If mode is *EXPLICIT* -- *VALUES* contains the (whitespace separated) list of times that are are to be modeled. - -:: - - [TIME_LOOP] - MODE = REGULAR - START = 0.0 - FINISH = 20.0 - NSTEP = 5 diff --git a/doc/user_guides/platform.rst b/doc/user_guides/platform.rst index 425abc12..025731fb 100644 --- a/doc/user_guides/platform.rst +++ b/doc/user_guides/platform.rst @@ -365,11 +365,6 @@ The platform configuration file contains platform specific information that the HOST = franklin MPIRUN = aprun - PHYS_BIN_ROOT = /project/projectdirs/m876/phys-bin/phys/ - DATA_TREE_ROOT = /project/projectdirs/m876/data - DATA_ROOT = /project/projectdirs/m876/data/ - PORTAL_URL = http://swim.gat.com:8080/monitor - RUNID_URL = http://swim.gat.com:4040/runid.esp ####################################### # resource detection method @@ -407,12 +402,6 @@ The platform configuration file contains platform specific information that the would like to launch a task directly without the parallel launcher (say, on a SMP style machine or workstation), set this to "eval" -- it tells the task manager to directly launch the task as `` ``. -**\*_ROOT** - locations of data and binaries. Used by the configuration - file and components to run the tasks of the simulation. -**\*_URL** - portal URLs. Used to connect to and communicate with the - portal. **NODE_DETECTION** method to use to detect the number of nodes and processes in the allocation. If the value is "manual," then the manual @@ -474,11 +463,6 @@ In addition to these files, there is ``ips/workstation.conf``, a sample platform HOST = workstation MPIRUN = mpirun # eval - PHYS_BIN_ROOT = /home//phys-bin - DATA_TREE_ROOT = /home//swim_data - DATA_ROOT = /home//swim_data - #PORTAL_URL = http://swim.gat.com:8080/monitor - #RUNID_URL = http://swim.gat.com:4040/runid.esp ####################################### # resource detection method diff --git a/doc/user_guides/user_guides.rst b/doc/user_guides/user_guides.rst index 41d1e041..5c815b32 100644 --- a/doc/user_guides/user_guides.rst +++ b/doc/user_guides/user_guides.rst @@ -13,9 +13,6 @@ This directory has all of the user guides for using the IPS (see the component a :doc:`Introduction to the IPS` A handy reference for constructing and running applications, this document helps users through the process of running a simulation based on existing components. It also includes: terminology, examples, and guidance on how to translate a computational or scientific question into a series of IPS runs. -:doc:`The Configuration Files (simulation, component and platform files) Explained`: - Annotated version of the configuration file with explanations of how and why the values are used in the framework and components. - :doc:`The Configuration File - Explained`: Annotated version of the configuration file with explanations of how and why the values are used in the framework and components. @@ -28,9 +25,6 @@ This directory has all of the user guides for using the IPS (see the component a :doc:`The IPS for Driver and Component Developers`: This guide contains the elements of components and drivers, suggestions on how to construct a simulation, how to add the new component to a simulation and the repository, as well as, an IPS services guide to have handy when writing components and drivers. This guide is for components and drivers based on the *generic driver* model. More sophisticated logic and execution models are covered in the following document. -:doc:`The New IPS for Simulators and Developers`: - This guide contains the elements of a simulation and an introduction to complex workflow management and the use of the Framework for managing multiple runs. - .. **User Guides Table of Contents** .. toctree:: @@ -39,8 +33,6 @@ This directory has all of the user guides for using the IPS (see the component a basic_guide advanced_guide - advanced_guide_new config_file - config_file_new platform