diff --git a/easyvvuq/actions/__init__.py b/easyvvuq/actions/__init__.py index 68f15647e..c3180fc62 100644 --- a/easyvvuq/actions/__init__.py +++ b/easyvvuq/actions/__init__.py @@ -1,8 +1,9 @@ from .base import BaseAction -from .execute_local import ExecuteLocal, ExecuteLocalV2, ExecutePython +from .execute_local import ExecuteLocal, ExecutePython, CreateRunDirectory, Encode, Decode +from .execute_local import CleanUp, Actions from .execute_kubernetes import ExecuteKubernetes from .execute_slurm import ExecuteSLURM -from .action_statuses import ActionStatuses +from .action_statuses import ActionPool __copyright__ = """ diff --git a/easyvvuq/actions/action_statuses.py b/easyvvuq/actions/action_statuses.py index a08a38c2f..8daf46043 100644 --- a/easyvvuq/actions/action_statuses.py +++ b/easyvvuq/actions/action_statuses.py @@ -1,5 +1,5 @@ -import time -from concurrent.futures import ThreadPoolExecutor +from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor, as_completed +import copy __copyright__ = """ @@ -24,7 +24,7 @@ __license__ = "LGPL" -class ActionStatuses: +class ActionPool: """A class that tracks statuses of a list of actions. Parameters @@ -37,37 +37,31 @@ class ActionStatuses: """ - def __init__(self, statuses, batch_size=8, poll_sleep_time=1): - self.statuses = list(statuses) - self.actions = [] - self.poll_sleep_time = poll_sleep_time - self.pool = ThreadPoolExecutor(batch_size) + def __init__(self, campaign, actions, inits, max_workers=None, sequential=False): + self.campaign = campaign + self.actions = actions + self.inits = inits + self.max_workers = max_workers + self.sequential = sequential + self.futures = [] + self.results = [] - def job_handler(self, status): - """Will handle the execution of this action status. - - Parameters - ---------- - status: ActionStatus - ActionStatus of an action to be executed. - """ - status.start() - while not status.finished(): - time.sleep(self.poll_sleep_time) - if status.succeeded(): - status.finalise() - return True - else: - return False - - def start(self): + def start(self, executor=ThreadPoolExecutor): """Start the actions. Returns ------- A list of Python futures represending action execution. """ - self.actions = [self.pool.submit(self.job_handler, status) for status in self.statuses] + self.pool = executor(max_workers=self.max_workers) + for previous in self.inits: + previous = copy.copy(previous) + if self.sequential: + result = self.actions.start(previous) + self.results.append(result) + else: + future = self.pool.submit(self.actions.start, previous) + self.futures.append(future) return self def progress(self): @@ -81,11 +75,11 @@ def progress(self): running = 0 done = 0 failed = 0 - for action in self.actions: - if action.running(): + for future in self.futures: + if future.running(): running += 1 - elif action.done(): - if not action.result(): + elif future.done(): + if not future.result(): failed += 1 else: done += 1 @@ -93,13 +87,14 @@ def progress(self): ready += 1 return {'ready': ready, 'active': running, 'finished': done, 'failed': failed} - def wait(self, poll_interval=1): - """A command that will automatically poll job statuses. For use in scripts. - - Parameters - ---------- - poll_interval: int - Polling interval in seconds. + def collate(self): + """A command that will block untill all futures in the pool have finished. """ - while self.progress()['ready'] > 0: - time.sleep(poll_interval) + if self.sequential: + for result in self.results: + self.campaign.campaign_db.store_result(result['run_id'], result) + else: + for future in as_completed(self.futures): + result = future.result() + self.campaign.campaign_db.store_result(result['run_id'], result) + self.campaign.campaign_db.session.commit() diff --git a/easyvvuq/actions/execute_kubernetes.py b/easyvvuq/actions/execute_kubernetes.py index dc10a27c0..198c77b56 100644 --- a/easyvvuq/actions/execute_kubernetes.py +++ b/easyvvuq/actions/execute_kubernetes.py @@ -24,6 +24,8 @@ import logging import uuid import copy +import time + from kubernetes.client.api import core_v1_api from kubernetes import config from kubernetes.client import V1ConfigMap, V1ObjectMeta @@ -54,7 +56,7 @@ logger = logging.getLogger(__name__) -class ActionStatusKubernetes(): +class ExecuteKubernetes(): """Provides a way to track the status of an on-going Kubernetes action. @@ -71,31 +73,56 @@ class ActionStatusKubernetes(): outfile : str a filename to write the output of the simulation """ - - def __init__(self, api, body, config_names, namespace, outfile): - self.core_v1 = api - self.body = dict(body) - self.pod_name = body['metadata']['name'] - self.config_names = config_names - self.namespace = namespace - self.outfile = outfile + def __init__(self, image, command, input_file_names=None, output_file_name=None): + pod_name = str(uuid.uuid4()) + container_name = str(uuid.uuid4()) + self.body = { + 'apiVersion': 'v1', 'kind': 'Pod', 'metadata': {'name': pod_name}, + 'spec': { + 'restartPolicy': 'Never', + 'containers': [ + { + 'name': container_name, + 'image': image, + 'command': ['/bin/sh', '-c'], + 'args': [command] + } + ] + } + } + self.input_file_names = input_file_names + self.output_file_name = output_file_name + config.load_kube_config() + self.core_v1 = core_v1_api.CoreV1Api() + self.pod_name = self.body['metadata']['name'] + self.namespace = "default" self._succeeded = False self._started = False - def start(self): + def start(self, previous=None): """Will create the Kubernetes pod and hence start the action. """ - if self.started(): - raise RuntimeError('The pod has already started!') + target_dir = previous['rundir'] + if self.input_file_names is None: + self.input_file_names = [previous['encoder_filename']] + if self.output_file_name is None: + self.output_file_name = previous['decoder_filename'] + file_names = [(os.path.join(target_dir, input_file_name), str(uuid.uuid4())) + for input_file_name in self.input_file_names] + self.config_names = file_names + dep = copy.deepcopy(self.body) + dep['metadata']['name'] = str(uuid.uuid4()) self.create_config_maps(self.config_names) - self.create_volumes(self.config_names, self.body) - self.core_v1.create_namespaced_pod(body=self.body, namespace="default") + print('configmaps created') + self.create_volumes(self.config_names, dep) + print('configs and volumes created') + self.core_v1.create_namespaced_pod(body=dep, namespace="default") self._started = True - - def started(self): - """Will return true if start() was called. - """ - return self._started + self.result = previous + while not self.finished(): + time.wait(5) + self.finalise() + return previous def finished(self): """Will return True if the pod has finished, otherwise will return False. @@ -160,71 +187,5 @@ def create_config_maps(self, file_names): data={os.path.basename(file_name): data}, metadata=metadata ) + print(configmap) self.core_v1.create_namespaced_config_map(namespace='default', body=configmap) - - -class ExecuteKubernetes(BaseAction): - """ Provides an action element to run a shell command in a specified - directory. - - Parameters - ---------- - - pod_config : str - Filename of the YAML file with the Kubernetes Pod configuration. - input_file_names : list of str - A list of input file names for your simulation. - output_file_name : str - An output file name for the output of the simulation. - """ - - def __init__(self, image, command, input_file_names=None, output_file_name=None): - if os.name == 'nt': - msg = ('Local execution is provided for testing on Posix systems' - 'only. We detect you are using Windows.') - logger.error(msg) - raise NotImplementedError(msg) - # with open(pod_config, 'r') as fd: - # self.dep = yaml.load(fd, Loader=yaml.BaseLoader) - #import pdb; pdb.set_trace() - pod_name = str(uuid.uuid4()) - container_name = str(uuid.uuid4()) - self.dep = {'apiVersion': 'v1', 'kind': 'Pod', 'metadata': {'name': pod_name}, - 'spec': { - 'restartPolicy': 'Never', - 'containers': [ - { - 'name': container_name, - 'image': image, - 'command': ['/bin/sh', '-c'], - 'args': [command] - } - ] - } - } - self.input_file_names = input_file_names - self.output_file_name = output_file_name - config.load_kube_config() - #c = Configuration() - #c.assert_hostname = False - # Configuration.set_default(c) - self.core_v1 = core_v1_api.CoreV1Api() - - def act_on_dir(self, target_dir): - """Executes a dockerized simulation on input files found in `target_dir`. - - target_dir : str - Directory in which to execute simulation. - """ - # this is suboptimal and a better interface is needed to get those filenames - if self.input_file_names is None: - self.input_file_names = [self.campaign._active_app_encoder.target_filename] - if self.output_file_name is None: - self.output_file_name = self.campaign._active_app_decoder.target_filename - file_names = [(os.path.join(target_dir, input_file_name), str(uuid.uuid4())) - for input_file_name in self.input_file_names] - dep = copy.deepcopy(self.dep) - dep['metadata']['name'] = str(uuid.uuid4()) - return ActionStatusKubernetes( - self.core_v1, dep, file_names, 'default', - os.path.join(target_dir, self.output_file_name)) diff --git a/easyvvuq/actions/execute_local.py b/easyvvuq/actions/execute_local.py index 48b8adf19..93f36af7a 100644 --- a/easyvvuq/actions/execute_local.py +++ b/easyvvuq/actions/execute_local.py @@ -2,170 +2,191 @@ """ import os -import sys -import logging +from pathlib import Path +import shutil import subprocess -from . import BaseAction +import dill +import copy -__copyright__ = """ - - Copyright 2018 Robin A. Richardson, David W. Wright - - This file is part of EasyVVUQ - - EasyVVUQ is free software: you can redistribute it and/or modify - it under the terms of the Lesser GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - EasyVVUQ is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - Lesser GNU General Public License for more details. - - You should have received a copy of the Lesser GNU General Public License - along with this program. If not, see . - -""" __license__ = "LGPL" -logger = logging.getLogger(__name__) +class CreateRunDirectory(): + def __init__(self, root): + self.root = root + + def start(self, previous=None): + run_id = previous['run_id'] + level1_a, level1_b = (int(run_id / 100 ** 4) * 100 ** 4, + int(run_id / 100 ** 4 + 1) * 100 ** 4) + level2_a, level2_b = (int(run_id / 100 ** 3) * 100 ** 3, + int(run_id / 100 ** 3 + 1) * 100 ** 3) + level3_a, level3_b = (int(run_id / 100 ** 2) * 100 ** 2, + int(run_id / 100 ** 2 + 1) * 100 ** 2) + level4_a, level4_b = (int(run_id / 100 ** 1) * 100 ** 1, + int(run_id / 100 ** 1 + 1) * 100 ** 1) + level1_dir = "runs_{}-{}/".format(level1_a, level1_b) + level2_dir = "runs_{}-{}/".format(level2_a, level2_b) + level3_dir = "runs_{}-{}/".format(level3_a, level3_b) + level4_dir = "runs_{}-{}/".format(level4_a, level4_b) + level5_dir = "runs_{}".format(int(run_id)) + path = os.path.join(self.root, previous['campaign_dir'], 'runs', + level1_dir, level2_dir, level3_dir, level4_dir, level5_dir) + Path(path).mkdir(parents=True, exist_ok=True) + previous['rundir'] = path + self.result = previous + return self.result + def finished(self): + return True -class ActionStatusLocal(): - def __init__(self): + def finalise(self): pass - def start(self): - return None + def succeeded(self): + return True + +class Encode(): + def __init__(self, encoder): + self.encoder = encoder + + def start(self, previous=None): + self.encoder.encode( + params=previous['run_info']['params'], + target_dir=previous['rundir']) + try: + previous['encoder_filename'] = self.encoder.target_filename + except AttributeError: + pass + return previous def finished(self): return True def finalise(self): - return None + pass - def succeeded(self): + def succeeeded(self): return True +class Decode(): + def __init__(self, decoder): + self.decoder = decoder -class ExecuteLocal(BaseAction): + def start(self, previous=None): + run_info = copy.copy(previous['run_info']) + run_info['run_dir'] = previous['rundir'] + result = self.decoder.parse_sim_output(run_info) + previous['result'] = result + previous['decoder_filename'] = self.decoder.target_filename + return previous - def __init__(self, run_cmd, interpret=None): - """ - Provides an action element to run a shell command in a specified - directory. + def finished(self): + return True - Parameters - ---------- + def finalise(self): + pass - run_cmd : str - Command to execute. - interpret : str or None - Interpreter to use to execute cmd. + def succeeded(self): + return True - """ +class CleanUp(): + def __init__(self): + pass - if os.name == 'nt': - msg = ('Local execution is provided for testing on Posix systems' - 'only. We detect you are using Windows.') - logger.error(msg) - raise NotImplementedError(msg) + def start(self, previous=None): + if not ('rundir' in previous.keys()): + raise RuntimeError('must be used with actions that create a directory structure') + shutil.rmtree(previous['rundir']) + return previous + + def finished(self): + return True - # Need to expand users, get absolute path and dereference symlinks - self.run_cmd = os.path.realpath(os.path.expanduser(run_cmd)) - self.interpreter = interpret + def finalise(self): + pass - def act_on_dir(self, target_dir): - """ - Executes `self.run_cmd` in the shell in `target_dir`. + def succeeded(self): + return True - target_dir : str - Directory in which to execute command. +class ExecutePython(): + def __init__(self, function): + self.function = dill.dumps(function) + self.params = None + self.eval_result = None - """ + def start(self, previous=None): + function = dill.loads(self.function) + self.eval_result = function(previous['run_info']['params']) + previous['result'] = self.eval_result + return previous - if self.interpreter is None: - full_cmd = f'cd "{target_dir}"\n{self.run_cmd}\n' + def finished(self): + if self.eval_result is None: + return False else: - full_cmd = f'cd "{target_dir}"\n{self.interpreter} {self.run_cmd}\n' - result = os.system(full_cmd) - if result != 0: - sys.exit(f'Non-zero exit code from command "{full_cmd}"\n') - return ActionStatusLocal() - - -class ExecutePython(BaseAction): - def __init__(self, function): - self.function = function + return True - def act_on_dir(self, target_dir): - self.function(target_dir) - return ActionStatusLocal() + def finalise(self): + pass + def succeeded(self): + if not self.finished(): + raise RuntimeError('action did not finish yet') + else: + return True -class ActionStatusLocalV2(): - def __init__(self, full_cmd, target_dir): - self.full_cmd = full_cmd - self.target_dir = target_dir +class ExecuteLocal(): + def __init__(self, full_cmd): + self.full_cmd = full_cmd.split() self.popen_object = None self.ret = None self._started = False - def start(self): - self.popen_object = subprocess.Popen(self.full_cmd, cwd=self.target_dir) - self._started = True - - def started(self): - return self._started + def start(self, previous=None): + target_dir = previous['rundir'] + self.ret = subprocess.run(self.full_cmd, cwd=target_dir) + return previous def finished(self): - """Returns true if action is finished. In this case if calling poll on - the popen object returns a non-None value. - """ - if self.popen_object is None: - return False - ret = self.popen_object.poll() - if ret is not None: - self.ret = ret - return True - else: - return False + return True def finalise(self): """Performs clean-up if necessary. In this case it isn't. I think. """ - return None + pass def succeeded(self): """Will return True if the process finished successfully. It judges based on the return code and will return False if that code is not zero. """ - if not self.started(): - return False if self.ret != 0: return False else: return True +class Actions(): + def __init__(self, *args): + self.actions = list(args) + + def start(self, previous=None): + for action in self.actions: + if not hasattr(action, 'start'): + raise RuntimeError('action in the actions list does not provide a start method') + previous = copy.copy(previous) + run_id = previous['run_id'] + for action in self.actions: + previous = action.start(previous) + self.result = previous + assert(self.result['run_id'] == run_id) + return previous -class ExecuteLocalV2(ExecuteLocal): - """An improvement over ExecuteLocal that uses Popen and provides the non-blocking - execution that allows you to track progress. In line with other Action classes in EasyVVUQ. - """ - - def act_on_dir(self, target_dir): - """ - Executes `self.run_cmd` in the shell in `target_dir`. - - target_dir : str - Directory in which to execute command. + def finished(self): + return all([action.finished() for action in self.actions]) - """ + def finalise(self): + for action in self.actions: + action.finalise() - if self.interpreter is None: - full_cmd = self.run_cmd.split() - else: - full_cmd = [self.interpreter] + self.run_cmd.split() - return ActionStatusLocalV2(full_cmd, target_dir) + def succeeded(self): + return all([action.succeeded() for action in self.actions]) diff --git a/easyvvuq/campaign.py b/easyvvuq/campaign.py index 268d4149e..5870b5f6a 100644 --- a/easyvvuq/campaign.py +++ b/easyvvuq/campaign.py @@ -8,13 +8,13 @@ import tempfile import json import easyvvuq +from concurrent.futures import ProcessPoolExecutor from easyvvuq import ParamsSpecification, constants from easyvvuq.constants import default_campaign_prefix, Status from easyvvuq.data_structs import RunInfo, CampaignInfo, AppInfo from easyvvuq.sampling import BaseSamplingElement -from easyvvuq.actions import ActionStatuses +from easyvvuq.actions import ActionPool import easyvvuq.db.sql as db -from cerberus import Validator __copyright__ = """ @@ -61,11 +61,6 @@ class Campaign: name : :obj:`str`, optional params : dict Description of the parameters to associate with the application. - encoder : :obj:`easyvvuq.encoders.base.BaseEncoder` - Encoder element to convert parameters into application run inputs. - decoder : :obj:`easyvvuq.decoders.base.BaseDecoder` - Decoder element to convert application run output into data for - VVUQ analysis. db_type : str, default="sql" Type of database to use for CampaignDB. db_location : :obj:`str`, optional @@ -75,10 +70,6 @@ class Campaign: Path to working directory - used to store campaign directory. state_file : :obj:`str`, optional Path to serialised state - used to initialise the Campaign. - relocate: dict or None - Relocation dictionary. If specified will try to relocate the campaign according - to the information within the dictionary, dictionary format: {'work_dir': str, - 'campaign_dir': str, 'db_location': str}. The parameter db_location is optional. change_to_state : bool, optional, default=False Should we change to the directory containing any specified `state_file` in order to make relative paths work. @@ -112,14 +103,6 @@ class Campaign: Info about currently set app _active_app_name: str Name of currently set app - _active_app_encoder: easyvvuq.encoders.BaseEncoder - The current Encoder object being used, from the currently set app - _active_app_decoder: easyvvuq.decoders.BaseDecoder - The current Decoder object being used, from the currently set app - _active_app_collater: easyvvuq.collate.BaseCollationElement - The current Collater object assigned to this campaign - _active_sampler: easyvvuq.sampling.BaseSamplingElement - The currently set Sampler object _active_sampler_id: int The database id of the currently set Sampler object @@ -129,20 +112,14 @@ def __init__( self, name=None, params=None, - encoder=None, - decoder=None, + actions=None, db_type="sql", db_location=None, work_dir="./", state_file=None, - relocate=None, change_to_state=False, verify_all_runs=True ): - if relocate is not None and state_file is None: - raise RuntimeError('please use relocate with a state_file') - - self._relocate_dict = relocate self.work_dir = os.path.realpath(os.path.expanduser(work_dir)) self.verify_all_runs = verify_all_runs @@ -159,9 +136,7 @@ def __init__( self._active_app = None self._active_app_name = None - self._active_app_encoder = None - self._active_app_decoder = None - self._active_app_collater = None + self._active_app_actions = None self._active_sampler = None self._active_sampler_id = None @@ -170,16 +145,15 @@ def __init__( # campaign with a new campaign database if state_file is not None: self._state_dir = self.init_from_state_file( - state_file, relocate=relocate) + state_file) if change_to_state: os.chdir(self._state_dir) - # self.relocate(self.campaign_dir) else: self.init_fresh(name, db_type, db_location, self.work_dir) self._state_dir = None - if (params is not None) and (encoder is not None) and (decoder is not None): - self.add_app(name=name, params=params, encoder=encoder, decoder=decoder) + if (params is not None) and (actions is not None): + self.add_app(name=name, params=params, actions=actions) @property def campaign_dir(self): @@ -253,18 +227,13 @@ def init_fresh(self, name, db_type='sql', self.campaign_name = name self.campaign_id = self.campaign_db.get_campaign_id(self.campaign_name) - def init_from_state_file(self, state_file, relocate=None): + def init_from_state_file(self, state_file): """Load campaign state from file. Parameters ---------- state_file : str Path to the file containing the campaign state. - relocate: dict or None - Relocation dictionary. If specified will try to relocate the campaign according - to the information within the dictionary, dictionary format: {'work_dir': str, - 'campaign_dir': str, 'db_location': str}. The parameter db_location is optional. - Returns ------- @@ -287,7 +256,7 @@ def init_from_state_file(self, state_file, relocate=None): state_dir = os.path.dirname(full_state_path) logger.info(f"Loading campaign from state file '{full_state_path}'") - self.load_state(full_state_path, relocate) + self.load_state(full_state_path) if self.db_type == 'sql': from .db.sql import CampaignDB @@ -332,17 +301,13 @@ def save_state(self, state_filename): with open(state_filename, "w") as outfile: json.dump(output_json, outfile, indent=4) - def load_state(self, state_filename, relocate=None): + def load_state(self, state_filename): """Load up a Campaign state from the specified JSON file Parameters ---------- state_filename : str Name of file from which to load the state - relocate: dict or None - Relocation dictionary. If specified will try to relocate the campaign according - to the information within the dictionary, dictionary format: {'work_dir': str, - 'campaign_dir': str, 'db_location': str}. The parameter db_location is optional. Returns ------- @@ -357,29 +322,13 @@ def load_state(self, state_filename, relocate=None): self.campaign_name = input_json["campaign_name"] self._campaign_dir = input_json["campaign_dir"] - if relocate is not None: - assert(isinstance(relocate, dict)) - try: - self.db_location = relocate['db_location'] - except KeyError: - pass - try: - self.work_dir = os.path.realpath(os.path.expanduser(relocate['work_dir'])) - except KeyError: - raise RuntimeError('please specify work_dir when relocating') - try: - self._campaign_dir = relocate['campaign_dir'] - except KeyError: - raise RuntimeError('please specify campaign_dir when relocating') - if not os.path.exists(self.campaign_dir): message = (f"Campaign directory in state_file {state_filename}" f" ({self.campaign_dir}) does not exist.") logger.critical(message) raise RuntimeError(message) - def add_app(self, name=None, params=None, decoderspec=None, - encoder=None, decoder=None, set_active=True): + def add_app(self, name=None, params=None, actions=None, set_active=True): """Add an application to the CampaignDB. Parameters @@ -411,9 +360,7 @@ def add_app(self, name=None, params=None, decoderspec=None, app = AppInfo( name=name, paramsspec=paramsspec, - decoderspec=decoderspec, - encoder=encoder, - decoder=decoder + actions=actions, ) self.campaign_db.add_app(app) @@ -440,8 +387,7 @@ def set_app(self, app_name): self._active_app = self.campaign_db.app(name=app_name) # Resurrect the app encoder, decoder and collation elements - (self._active_app_encoder, - self._active_app_decoder) = self.campaign_db.resurrect_app(app_name) + self._active_app_actions = self.campaign_db.resurrect_app(app_name) def set_sampler(self, sampler, update=False): """Set active sampler. @@ -631,45 +577,6 @@ def all_complete(self): return True return False - def populate_runs_dir(self): - """Populate run directories based on runs in the CampaignDB. - - This calls the encoder element defined for the current application to - create input files for it in each run directory, usually with varying - input (scientific) parameters. Note that this method will also create - the directories in the file system. The directory names will be formed - in relation to the work_dir argument you have specified when creating - the Campaign object. - - Returns - ------- - a list with run ids - - """ - - # Get the encoder for this app. If none is set, only the directory structure - # will be created. - active_encoder = self._active_app_encoder - if active_encoder is None: - logger.warning('No encoder set for this app. Creating directory structure only.') - - run_ids = [] - - for run_id, run_data in self.campaign_db.runs( - status=Status.NEW, app_id=self._active_app['id']): - - # Make directory for this run's output - os.makedirs(run_data['run_dir']) - - # Encode run - if active_encoder is not None: - active_encoder.encode(params=run_data['params'], - target_dir=run_data['run_dir']) - - run_ids.append(run_id) - self.campaign_db.set_run_statuses(run_ids, Status.ENCODED) - return run_ids - def get_campaign_runs_dir(self): """Get the runs directory from the CampaignDB. @@ -693,14 +600,26 @@ def relocate(self, campaign_dir): raise RuntimeError("specified directory does not exist: {}".format(campaign_dir)) self.campaign_db.relocate(campaign_dir, self.campaign_name) - def call_for_each_run(self, fn, status=Status.ENCODED): - - # Loop through all runs in this campaign with the specified status, - # and call the specified user function for each. - for run_id, run_data in self.campaign_db.runs(status=status, app_id=self._active_app['id']): - fn(run_id, run_data) + def execute(self, max_workers=None, nsamples=0, mark_invalid=False, sequential=False): + """This will draw samples and execute the action on those samples. + + Parameters + ---------- + nsamples : int + Number of samples to draw. + action : BaseAction + An action to be executed. + batch_size : int + Number of actions to be executed at the same time. + mark_invalid : bool + Mark runs that go outside the specified input parameter range as INVALID. + """ + self.draw_samples(nsamples, mark_invalid=mark_invalid) + action_pool = self.apply_for_each_sample( + self._active_app_actions, max_workers=max_workers, sequential=sequential) + return action_pool.start() - def apply_for_each_run_dir(self, action, status=Status.ENCODED, batch_size=1): + def apply_for_each_sample(self, action, max_workers=None, sequential=False): """ For each run in this Campaign's run list, apply the specified action (an object of type Action) @@ -721,39 +640,17 @@ def apply_for_each_run_dir(self, action, status=Status.ENCODED, batch_size=1): # Loop through all runs in this campaign with status ENCODED, and # run the specified action on each run's dir - assert(isinstance(status, Status)) - action.campaign = self - action_statuses = [] - for run_id, run_data in self.campaign_db.runs(status=status, app_id=self._active_app['id']): - action_statuses.append(action.act_on_dir(run_data['run_dir'])) - return ActionStatuses(action_statuses, batch_size=batch_size) - - def sample_and_apply(self, action, batch_size=8, nsamples=0, mark_invalid=False): - """This will draw samples, populated the runs directories and run the specified action. - This is a convenience method. - - Parameters - ---------- - nsamples : int - number of samples to draw - action : BaseAction - an action to be executed - batch_size : int - number of actions to be executed at the same time - mark_invalid : bool - Mark runs that go outside the specified input parameter range as INVALID. - - Returns - ------- - action_statuses: ActionStatuses - An object containing ActionStatus instances to track action execution - """ - self.draw_samples(nsamples, mark_invalid=mark_invalid) - self.populate_runs_dir() - action_statuses = self.apply_for_each_run_dir(action, batch_size=batch_size) - return action_statuses - - def iterate(self, action, batch_size=8, nsamples=0, mark_invalid=False): + def inits(): + for run_id, run_data in self.campaign_db.runs(status=Status.NEW, app_id=self._active_app['id']): + previous = {} + previous['run_id'] = run_id + previous['campaign_dir'] = self._campaign_dir + previous['run_info'] = run_data + yield previous + return ActionPool(self, action, inits=inits(), max_workers=max_workers, sequential=sequential).start() + + + def iterate(self, max_workers=None, nsamples=0, mark_invalid=False, sequential=False): """This is the equivalent of sample_and_apply for methods that rely on the output of the previous sampling stage (primarily MCMC). @@ -763,7 +660,9 @@ def iterate(self, action, batch_size=8, nsamples=0, mark_invalid=False): number of samples to draw action : BaseAction an action to be executed - batch_size : int + *args : args + arguments to action + max_workers : int number of actions to be executed at the same time mark_invalid : bool Mark runs that go outside the specified input parameter range as INVALID. @@ -775,10 +674,9 @@ def iterate(self, action, batch_size=8, nsamples=0, mark_invalid=False): """ while True: self.draw_samples(nsamples, mark_invalid=mark_invalid) - self.populate_runs_dir() - action_statuses = self.apply_for_each_run_dir(action, batch_size=batch_size) - yield action_statuses - self.collate() + action_pool = self.apply_for_each_sample( + self._active_app_actions, max_workers=max_workers, sequential=sequential) + yield action_pool.start() result = self.get_collation_result(last_iteration=True) invalid = self.get_invalid_runs(last_iteration=True) ignored_runs = self._active_sampler.update(result, invalid) @@ -788,39 +686,6 @@ def iterate(self, action, batch_size=8, nsamples=0, mark_invalid=False): update({'status': constants.Status.IGNORED}) self.campaign_db.session.commit() - def collate(self): - """Combine the output from all runs associated with the current app. - - Returns - ------- - - """ - app_id = self._active_app['id'] - decoder = self._active_app_decoder - processed_run_IDs = [] - processed_run_results = [] - for run_id, run_info in self.campaign_db.runs( - status=constants.Status.ENCODED, app_id=app_id): - # use decoder to check if run has completed (in general application-specific) - if decoder.sim_complete(run_info=run_info): - # get the output of the simulation from the decoder - run_data = decoder.parse_sim_output(run_info=run_info) - if self._active_app['decoderspec'] is not None: - v = Validator() - v.schema = self._active_app['decoderspec'] - if not v.validate(run_data): - raise RuntimeError( - "the output of he decoder failed to validate: {}".format(run_data)) - processed_run_IDs.append(run_id) - processed_run_results.append(run_data) - # update run statuses to "collated" - # self.campaign_db.set_run_statuses(processed_run_IDs, constants.Status.COLLATED) - # add the results to the database - self.campaign_db.store_results( - self._active_app_name, zip( - processed_run_IDs, processed_run_results)) - return self.get_collation_result() - def recollate(self): """Clears the current collation table, changes all COLLATED status runs back to ENCODED, then runs collate() again @@ -900,7 +765,7 @@ def analyse(self, **kwargs): **kwargs - dict Argument to the analysis class constructor (after sampler). """ - collation_result = self.collate() + collation_result = self.get_collation_result() try: analysis = self._active_sampler.analysis_class(sampler=self._active_sampler, **kwargs) return analysis.analyse(collation_result) diff --git a/easyvvuq/data_structs.py b/easyvvuq/data_structs.py index 610dfa1b2..42c442e2b 100644 --- a/easyvvuq/data_structs.py +++ b/easyvvuq/data_structs.py @@ -5,8 +5,6 @@ import logging import json from easyvvuq import constants -from easyvvuq.encoders import BaseEncoder -from easyvvuq.decoders import BaseDecoder from easyvvuq.utils.helpers import easyvvuq_serialize import numpy @@ -209,53 +207,17 @@ class AppInfo: Human readable application name. paramsspec : ParamsSpecification or None Description of possible parameter values. - decoderspec : dict - Description of the output format of the decoder. - input_encoder : :obj:`easyvvuq.encoders.base.BaseEncoder` - Encoder element for application. - output_decoder : :obj:`easyvvuq.decoders.base.BaseDecoder` - Decoder element for application. """ def __init__( self, name=None, paramsspec=None, - decoderspec=None, - encoder=None, - decoder=None): + actions=None): self.name = name - self.input_encoder = encoder - self.output_decoder = decoder self.paramsspec = paramsspec - self.decoderspec = decoderspec - - @property - def input_encoder(self): - return self._input_encoder - - @input_encoder.setter - def input_encoder(self, encoder): - if not isinstance(encoder, BaseEncoder): - msg = f"Provided 'encoder' must be derived from type BaseEncoder" - logger.error(msg) - raise Exception(msg) - - self._input_encoder = encoder - - @property - def output_decoder(self): - return self._output_decoder - - @output_decoder.setter - def output_decoder(self, decoder): - if not isinstance(decoder, BaseDecoder): - msg = f"Provided 'decoder' must be derived from type BaseDecoder" - logger.error(msg) - raise Exception(msg) - - self._output_decoder = decoder + self.actions = actions def to_dict(self, flatten=False): """Convert to a dictionary (optionally flatten to single level) @@ -283,9 +245,7 @@ def to_dict(self, flatten=False): out_dict = { 'name': self.name, 'params': self.paramsspec, - 'decoderspec': self.decoderspec, - 'input_encoder': easyvvuq_serialize(self.input_encoder), - 'output_decoder': easyvvuq_serialize(self.output_decoder) + 'actions': easyvvuq_serialize(self.actions), } return out_dict diff --git a/easyvvuq/db/sql.py b/easyvvuq/db/sql.py index c2bb84b50..bf48c907b 100644 --- a/easyvvuq/db/sql.py +++ b/easyvvuq/db/sql.py @@ -4,7 +4,9 @@ import json import logging import pandas as pd +import numpy as np import ast +from sqlalchemy.sql import case from sqlalchemy import create_engine, Column, Integer, String, ForeignKey from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker @@ -75,11 +77,8 @@ class AppTable(Base): __tablename__ = 'app' id = Column(Integer, primary_key=True) name = Column(String) - input_encoder = Column(String) - output_decoder = Column(String) - collater = Column(String) params = Column(String) - decoderspec = Column(String) + actions = Column(String) class RunTable(Base): @@ -93,6 +92,7 @@ class RunTable(Base): status = Column(Integer) run_dir = Column(String) result = Column(String, default="{}") + execution_info = Column(String, default="{}") campaign = Column(Integer, ForeignKey('campaign_info.id')) sampler = Column(Integer, ForeignKey('sampler.id')) iteration = Column(Integer, default=0) @@ -121,6 +121,8 @@ def __init__(self, location=None, new_campaign=False, name=None, info=None): else: self.engine = create_engine('sqlite://') + self.commit_counter = 0 + session_maker = sessionmaker(bind=self.engine) self.session = session_maker() @@ -192,19 +194,12 @@ def app(self, name=None): raise RuntimeError(message) selected_app = selected[0] - - decoderspec = selected_app.decoderspec - if decoderspec is not None: - decoderspec = ast.literal_eval(selected_app.decoderspec) - + app_dict = { 'id': selected_app.id, 'name': selected_app.name, - 'input_encoder': selected_app.input_encoder, - 'output_decoder': selected_app.output_decoder, - 'collater': selected_app.collater, 'params': ParamsSpecification.deserialize(selected_app.params), - 'decoderspec': decoderspec + 'actions': selected_app.actions, } return app_dict @@ -321,9 +316,8 @@ def resurrect_app(self, app_name): app_info = self.app(app_name) - encoder = easyvvuq_deserialize(app_info['input_encoder']) - decoder = easyvvuq_deserialize(app_info['output_decoder']) - return encoder, decoder + actions = easyvvuq_deserialize(app_info['actions']) + return actions def add_runs(self, run_info_list=None, run_prefix='Run_', iteration=0): """ @@ -429,15 +423,15 @@ def set_dir_for_run(self, run_name, run_dir, campaign=None, sampler=None): selected.run_dir = run_dir self.session.commit() - def get_run_status(self, run_name, campaign=None, sampler=None): + def get_run_status(self, run_id, campaign=None, sampler=None): """ Return the status (enum) for the run with name 'run_name' (and, optionally, filtering for campaign and sampler by id) Parameters ---------- - run_name: str - Name of the run + run_id: int + id of the run campaign: int ID of the desired Campaign sampler: int @@ -449,12 +443,11 @@ def get_run_status(self, run_name, campaign=None, sampler=None): Status of the run. """ - filter_options = {'run_name': run_name} + filter_options = {'id': run_id} if campaign: filter_options['campaign'] = campaign if sampler: filter_options['sampler'] = sampler - selected = self.session.query(RunTable).filter_by(**filter_options) if selected.count() != 1: @@ -464,29 +457,24 @@ def get_run_status(self, run_name, campaign=None, sampler=None): return constants.Status(selected.status) - def set_run_statuses(self, run_name_list, status): + def set_run_statuses(self, run_id_list, status): """ - Set the specified 'status' (enum) for all runs in the list run_name_list + Set the specified 'status' (enum) for all runs in the list run_id_list Parameters ---------- - run_name_list: list of str - A list of run names run names (format is usually: prefix + int) + run_id_list: list of int + a list of run ids status: enum(Status) The new status all listed runs should now have Returns ------- """ - max_entries = 900 - - for i in range(0, len(run_name_list), max_entries): - selected = self.session.query(RunTable).filter( - RunTable.run_name.in_(set(run_name_list[i:i + max_entries]))).all() - - for run in selected: - run.status = status - self.session.commit() + self.session.query(RunTable).filter( + RunTable.id.in_(run_id_list)).update( + {RunTable.status: status}, synchronize_session='fetch') + self.session.commit() def campaigns(self): """Get list of campaigns for which information is stored in the @@ -735,7 +723,7 @@ def runs(self, campaign=None, sampler=None, status=None, not_status=None, app_id app_id=app_id) for r in selected: - yield r.run_name, self._run_to_dict(r) + yield r.id, self._run_to_dict(r) def run_ids(self, campaign=None, sampler=None, status=None, not_status=None, app_id=None): """ @@ -816,6 +804,22 @@ def runs_dir(self, campaign_name=None): return self._get_campaign_info(campaign_name=campaign_name).runs_dir + def store_result(self, run_id, result): + self.commit_counter += 1 + def convert_nonserializable(obj): + if isinstance(obj, np.int64): + return int(obj) + raise TypeError('Unknown type:', type(obj)) + result_ = result['result'] + result.pop('result') + result.pop('run_info') + self.session.query(RunTable).\ + filter(RunTable.id == run_id).\ + update({'result': json.dumps(result_, default=convert_nonserializable), + 'status': constants.Status.COLLATED}) + if self.commit_counter % COMMIT_RATE == 0: + self.session.commit() + def store_results(self, app_name, results): """Stores the results from a given run in the database. @@ -831,16 +835,16 @@ def store_results(self, app_name, results): except IndexError: raise RuntimeError("app with the name {} not found".format(app_name)) commit_counter = 0 - for run_name, result in results: + for run_id, result in results: try: self.session.query(RunTable).\ - filter(RunTable.run_name == run_name, RunTable.app == app_id).\ + filter(RunTable.id == run_id, RunTable.app == app_id).\ update({'result': json.dumps(result), 'status': constants.Status.COLLATED}) commit_counter += 1 if commit_counter % COMMIT_RATE == 0: self.session.commit() except IndexError: - raise RuntimeError("no runs with name {} found".format(run_name)) + raise RuntimeError("no runs with name {} found".format(run_id)) self.session.commit() def get_results(self, app_name, sampler_id, status=constants.Status.COLLATED, iteration=-1): @@ -907,14 +911,4 @@ def relocate(self, new_path, campaign_name): filter(CampaignTable.id == campaign_id).\ update({'campaign_dir': str(new_path), 'runs_dir': str(os.path.join(new_path, runs_dir))}) - for app_info in self.session.query(AppTable): - for run in self.runs(app_id=app_info.id): - path, run_dir = os.path.split(run[1]['run_dir']) - path, runs_dir = os.path.split(path) - new_path_ = os.path.join(new_path, runs_dir, run_dir) - self.session.query(RunTable).\ - filter(RunTable.campaign == campaign_id).\ - filter(RunTable.run_name == run[0]).\ - filter(RunTable.app == app_info.id).\ - update({'run_dir': new_path_}) self.session.commit() diff --git a/easyvvuq/decoders/base.py b/easyvvuq/decoders/base.py index f57cf4cbf..7b43787db 100644 --- a/easyvvuq/decoders/base.py +++ b/easyvvuq/decoders/base.py @@ -100,18 +100,3 @@ def parse_sim_output(self, run_info=None): """ raise NotImplementedError - - def element_category(self): - return "decoding" - - def element_name(self): - return self.decoder_name - - def is_restartable(self): - return True - - @staticmethod - def deserialize(decoderstr): - decoderdict = json.loads(decoderstr) - decoder = AVAILABLE_DECODERS[decoderdict["element_name"]](**decoderdict["state"]) - return decoder diff --git a/easyvvuq/decoders/json.py b/easyvvuq/decoders/json.py index 75854c1a9..41285dc6c 100644 --- a/easyvvuq/decoders/json.py +++ b/easyvvuq/decoders/json.py @@ -108,10 +108,3 @@ def get_value(data, path): def _get_raw_data(self, out_path): with open(out_path) as fd: return json.load(fd) - - def get_restart_dict(self): - return {"target_filename": self.target_filename, - "output_columns": self.output_columns} - - def element_version(self): - return "0.1" diff --git a/easyvvuq/decoders/simple_csv.py b/easyvvuq/decoders/simple_csv.py index 1b789adcb..5391da7eb 100644 --- a/easyvvuq/decoders/simple_csv.py +++ b/easyvvuq/decoders/simple_csv.py @@ -83,10 +83,3 @@ def parse_sim_output(self, run_info={}): raise RuntimeError('column not found in the csv file: {}'.format(column)) return results - - def get_restart_dict(self): - return {"target_filename": self.target_filename, - "output_columns": self.output_columns} - - def element_version(self): - return "0.1" diff --git a/easyvvuq/encoders/base.py b/easyvvuq/encoders/base.py index 037f5142c..f82a712de 100644 --- a/easyvvuq/encoders/base.py +++ b/easyvvuq/encoders/base.py @@ -85,17 +85,3 @@ def encode(self, params=None, target_dir=''): """ raise NotImplementedError - def element_category(self): - return "encoding" - - def element_name(self): - return self.encoder_name - - def is_restartable(self): - return True - - @staticmethod - def deserialize(encoderstr): - encoderdict = json.loads(encoderstr) - encoder = AVAILABLE_ENCODERS[encoderdict["element_name"]](**encoderdict["state"]) - return encoder diff --git a/easyvvuq/encoders/generic_template.py b/easyvvuq/encoders/generic_template.py index 1e8952334..8c4d99ac6 100644 --- a/easyvvuq/encoders/generic_template.py +++ b/easyvvuq/encoders/generic_template.py @@ -43,10 +43,8 @@ class GenericEncoder(BaseEncoder, encoder_name="generic_template"): """ - def __init__(self, template_fname, delimiter='$', - target_filename="app_input.txt"): - - self.encoder_delimiter = delimiter + def __init__(self, template_fname, delimiter='$', target_filename="app_input.txt"): + self.delimiter = delimiter self.target_filename = target_filename self.template_fname = template_fname @@ -65,8 +63,7 @@ def encode(self, params={}, target_dir=''): try: with open(self.template_fname, 'r') as template_file: template_txt = template_file.read() - self.template = get_custom_template( - template_txt, custom_delimiter=self.encoder_delimiter) + self.template = Template(template_txt) except FileNotFoundError: raise RuntimeError( "the template file specified ({}) does not exist".format(self.template_fname)) @@ -95,11 +92,3 @@ def _log_substitution_failure(self, exception): logging.error(reasoning) raise KeyError(reasoning) - - def get_restart_dict(self): - return {"delimiter": self.encoder_delimiter, - "target_filename": self.target_filename, - "template_fname": self.template_fname} - - def element_version(self): - return "0.1" diff --git a/easyvvuq/encoders/multiencoder.py b/easyvvuq/encoders/multiencoder.py index b3a47752f..ecd6d608c 100644 --- a/easyvvuq/encoders/multiencoder.py +++ b/easyvvuq/encoders/multiencoder.py @@ -28,33 +28,18 @@ class MultiEncoder(BaseEncoder, encoder_name="multiencoder"): - def __init__(self, *encoders, serialized_list_of_encoders=None): + def __init__(self, *encoders): """ Expects one or more encoders """ + self.encoders = encoders - # If no serialized encoders list passed, generate one. Else deserialize the passed encoders. - if serialized_list_of_encoders is None: - self.encoders = encoders - self.serialized_list_of_encoders = [encoder.serialize() for encoder in self.encoders] - else: - self.serialized_list_of_encoders = serialized_list_of_encoders - self.encoders = [] - for serialized_encoder in self.serialized_list_of_encoders: - self.encoders.append(BaseEncoder.deserialize(serialized_encoder)) - - def encode(self, params=None, target_dir=''): + def encode(self, params={}, target_dir=''): """ Applies all encoders in the list of encoders. """ for encoder in self.encoders: encoder.encode(params=params, target_dir=target_dir) - def element_version(self): - return "0.1" - def is_restartable(self): return True - - def get_restart_dict(self): - return {'serialized_list_of_encoders': self.serialized_list_of_encoders} diff --git a/tests/cannonsim/test_input/cannonsim.template b/tests/cannonsim/test_input/cannonsim.template index a040367b7..7b44d1cb2 100644 --- a/tests/cannonsim/test_input/cannonsim.template +++ b/tests/cannonsim/test_input/cannonsim.template @@ -1,8 +1,8 @@ CANONSIM_INPUT_FILE: -gravity = #gravity -mass = #mass -velocity = #velocity -angle = #angle -height = #height -air_resistance = #air_resistance -time_step = #time_step +gravity = $gravity +mass = $mass +velocity = $velocity +angle = $angle +height = $height +air_resistance = $air_resistance +time_step = $time_step diff --git a/tests/test_actions_action_pool.py b/tests/test_actions_action_pool.py new file mode 100644 index 000000000..376e8e8ac --- /dev/null +++ b/tests/test_actions_action_pool.py @@ -0,0 +1,30 @@ +import pytest +import time +import chaospy as cp +from unittest.mock import MagicMock +from easyvvuq.actions import ActionPool, ExecutePython, Actions +from easyvvuq import Campaign +from easyvvuq.sampling import RandomSampler + +@pytest.fixture +def campaign(): + def model(params): + return {'y' : params['x'] + 1} + actions = Actions(ExecutePython(model)) + sampler = RandomSampler({'x': cp.Uniform(0, 1)}) + campaign = Campaign('test', {'x': {'default' : 0}}, actions) + campaign.set_sampler(sampler) + return campaign + +def test_action_pool_start(campaign): + action_pool = campaign.execute(nsamples=3) + assert(len(action_pool.futures) == 3) + action_pool.collate() + assert(len(action_pool.campaign.get_collation_result()) == 3) + assert(action_pool.progress() == {'ready': 0, 'active': 0, 'finished': 3, 'failed': 0}) + +def test_action_pool_start_sequential(campaign): + action_pool = campaign.execute(nsamples=3, sequential=True) + assert(len(action_pool.results) == 3) + action_pool.collate() + assert(len(action_pool.campaign.get_collation_result()) == 3) diff --git a/tests/test_actions_action_statuses.py b/tests/test_actions_action_statuses.py deleted file mode 100644 index ef2e5f781..000000000 --- a/tests/test_actions_action_statuses.py +++ /dev/null @@ -1,31 +0,0 @@ -import pytest -import time -from unittest.mock import MagicMock -from easyvvuq.actions import ActionStatuses - - -def test_action_status_kubernetes(): - status1, status2, status3 = (MagicMock(), MagicMock(), MagicMock()) - status1.finished.return_value = False - status2.finished.return_value = True - status3.finished.return_value = True - status1.succeeded.return_value = False - status2.succeeded.return_value = False - status3.succeeded.return_value = True - statuses = ActionStatuses([status1, status2, status3], 3) - statuses.start() - time.sleep(1) - stats = statuses.progress() - assert(stats['active'] == 1) - assert(stats['finished'] == 1) - assert(stats['failed'] == 1) - assert(not status1.finalise.called) - assert(not status2.finalise.called) - assert(status3.finalise.called) - status1.finished.return_value = True - status2.finished.return_value = True - status3.finished.return_value = True - status1.succeeded.return_value = True - status2.succeeded.return_value = True - status3.succeeded.return_value = True - statuses.wait(1) diff --git a/tests/test_actions_execute_kubernetes.py b/tests/test_actions_execute_kubernetes.py deleted file mode 100644 index 4c402f8f6..000000000 --- a/tests/test_actions_execute_kubernetes.py +++ /dev/null @@ -1,52 +0,0 @@ -import pytest -from unittest.mock import MagicMock -import easyvvuq.actions.execute_kubernetes as execute_kubernetes -from easyvvuq.actions.execute_kubernetes import ActionStatusKubernetes -import os - - -# Monkey patch some stuff - -execute_kubernetes.config = MagicMock() -execute_kubernetes.core_v1_api = MagicMock() -execute_kubernetes.Configuration = MagicMock() -execute_kubernetes.V1ConfigMap = MagicMock() -execute_kubernetes.V1ObjectMeta = MagicMock() - - -def test_execute_kubernetes(): - action = execute_kubernetes.ExecuteKubernetes( - 'tests/kubernetes/epidemic.yaml', ['epidemic.json'], 'out.csv') - action.campaign = MagicMock() - action.campaign._active_app_encoder.target_filename = 'test.json' - action.campaign._active_app_decoder.target_filename = 'test.csv' - action.act_on_dir('tests/kubernetes') - - -def test_action_status_kubernetes(): - api = MagicMock() - pod_name = 'test' - config_names = [('a', 'b'), ('c', 'd')] - namespace = 'test_namespace' - outfile = 'test.csv' - status = ActionStatusKubernetes( - api, {'metadata': {'name': 'test'}}, - config_names, namespace, outfile) - resp = MagicMock() - resp.status.phase = 'Pending' - api.read_namespaced_pod.return_value = resp - assert(not status.finished()) - with pytest.raises(RuntimeError): - status.finalise() - assert(not status.succeeded()) - resp.status.phase = 'Succeeded' - assert(status.finished()) - api.read_namespaced_pod_log.return_value = 'testing' - status.finalise() - assert(os.path.isfile('test.csv')) - with open('test.csv', 'r') as fd: - assert(fd.read() == 'testing') - os.remove('test.csv') - assert(api.delete_namespaced_config_map.called_with('b', 'test_namespace')) - assert(api.delete_namespaced_config_map.called_with('d', 'test_namespace')) - assert(api.delete_namespaced_pod.called_with('test', 'test_namespace')) diff --git a/tests/test_anisotropic_order.py b/tests/test_anisotropic_order.py index 131c34136..9c0fa972e 100644 --- a/tests/test_anisotropic_order.py +++ b/tests/test_anisotropic_order.py @@ -1,6 +1,7 @@ import chaospy as cp import numpy as np import easyvvuq as uq +from easyvvuq.actions import CreateRunDirectory, Encode, ExecuteLocal, Decode, Actions import os import matplotlib.pyplot as plt @@ -41,12 +42,13 @@ def test_anisotropic_order(tmpdir): target_filename='ade_in.json') decoder = uq.decoders.SimpleCSV(target_filename=output_filename, output_columns=output_columns) + execute = ExecuteLocal("{} ade_in.json".format(os.path.abspath('tests/sc/sc_model.py'))) + actions = Actions(CreateRunDirectory('/tmp'), Encode(encoder), execute, Decode(decoder)) # Add the SC app (automatically set as current app) my_campaign.add_app(name="sc", params=params, - encoder=encoder, - decoder=decoder) + actions=actions) # Create the sampler vary = { @@ -61,15 +63,7 @@ def test_anisotropic_order(tmpdir): # Associate the sampler with the campaign my_campaign.set_sampler(sampler) - # Will draw all (of the finite set of samples) - my_campaign.draw_samples() - my_campaign.populate_runs_dir() - - # Use this instead to run the samples using EasyVVUQ on the localhost - my_campaign.apply_for_each_run_dir(uq.actions.ExecuteLocal( - "tests/sc/sc_model.py ade_in.json")) - - my_campaign.collate() + my_campaign.execute().collate() # Post-processing analysis analysis = uq.analysis.SCAnalysis(sampler=sampler, qoi_cols=output_columns) diff --git a/tests/test_campaign.py b/tests/test_campaign.py index 5416e20e6..31ece5a5f 100644 --- a/tests/test_campaign.py +++ b/tests/test_campaign.py @@ -1,4 +1,5 @@ import easyvvuq as uq +from easyvvuq.actions import CreateRunDirectory, Encode, ExecuteLocal, Decode, Actions import chaospy as cp import os import logging @@ -51,15 +52,15 @@ def campaign(tmpdir): } encoder = uq.encoders.GenericEncoder( template_fname=f'{TEST_PATH}/cannonsim/test_input/cannonsim.template', - delimiter='#', target_filename='in.cannon') decoder = uq.decoders.SimpleCSV( target_filename='output.csv', output_columns=[ 'Dist', 'lastvx', 'lastvy']) + execute = ExecuteLocal(f"{TEST_PATH}/cannonsim/bin/cannonsim in.cannon output.csv") campaign = uq.Campaign(name='test', work_dir=tmpdir) - campaign.add_app(name='test', params=params, encoder=encoder, decoder=decoder) + actions = Actions(CreateRunDirectory('/tmp'), Encode(encoder), execute, Decode(decoder)) + campaign.add_app(name='test', params=params, actions=actions) campaign.set_app('test') - action = uq.actions.ExecuteLocal(f"{TEST_PATH}/cannonsim/bin/cannonsim in.cannon output.csv") stats = uq.analysis.BasicStats(qoi_cols=['Dist', 'lastvx', 'lastvy']) # Make a random sampler vary = { @@ -70,17 +71,9 @@ def campaign(tmpdir): } sampler = uq.sampling.RandomSampler(vary=vary) campaign.set_sampler(sampler) - campaign.draw_samples(num_samples=100) - campaign.populate_runs_dir() - campaign.apply_for_each_run_dir(action) + campaign.execute(nsamples=100, sequential=True).collate() return campaign - -def test_no_input_state(tmp_path): - with pytest.raises(RuntimeError): - uq.Campaign(name='test', work_dir=tmp_path, relocate={}) - - def test_invalid_db_type(tmp_path): with pytest.raises(RuntimeError): uq.Campaign(name='test', work_dir=tmp_path, db_type='pen&paper') @@ -143,16 +136,16 @@ def test_get_active_app(campaign): assert campaign.get_active_app() == campaign._active_app -def test_relocate_campaign(campaign, tmpdir): - runs = campaign.campaign_db.runs() - runs_dir = campaign.campaign_db.runs_dir() - for run in runs: - assert(run[1]['run_dir'].startswith(runs_dir)) - with pytest.raises(RuntimeError): - campaign.relocate('/test/test') - campaign.relocate(tmpdir) - for run in campaign.campaign_db.runs(): - assert(run[1]['run_dir'] == os.path.join(tmpdir, 'runs', run[0])) +# def test_relocate_campaign(campaign, tmpdir): +# runs = campaign.campaign_db.runs() +# runs_dir = campaign.campaign_db.runs_dir() +# for run in runs: +# assert(run[1]['run_dir'].startswith(runs_dir)) +# with pytest.raises(RuntimeError): +# campaign.relocate('/test/test') +# campaign.relocate(tmpdir) +# for run in campaign.campaign_db.runs(): +# assert(run[1]['run_dir'] == os.path.join(tmpdir, 'runs', run[0])) # def test_relocate_full(tmp_path): diff --git a/tests/test_db.py b/tests/test_db.py index 79d53260b..eabdea77d 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -5,12 +5,14 @@ from easyvvuq.db.sql import CampaignDB from easyvvuq.data_structs import CampaignInfo, RunInfo, AppInfo from easyvvuq.constants import Status +from easyvvuq.actions import Actions, ExecutePython import pandas as pd import numpy as np @pytest.fixture def app_info(): + actions = Actions(ExecutePython(lambda x: {})) app_info = AppInfo('test', uq.ParamsSpecification({ "temp_init": { "type": "float", @@ -30,14 +32,15 @@ def app_info(): "out_file": { "type": "string", "default": "output.csv"}}), - None, - uq.encoders.GenericEncoder( - template_fname='tests/cooling/cooling.template', - delimiter='$', - target_filename='cooling_in.json'), - uq.decoders.SimpleCSV( - target_filename='output.csv', - output_columns=["te"])) + actions) +# None, +# uq.encoders.GenericEncoder( +# template_fname='tests/cooling/cooling.template', +# delimiter='$', +# target_filename='cooling_in.json'), +# uq.decoders.SimpleCSV( +# target_filename='output.csv', +# output_columns=["te"])) return app_info @@ -53,7 +56,6 @@ def campaign(tmp_path, app_info): new_campaign=True, name='test', info=info) campaign.tmp_path = str(tmp_path) runs = [RunInfo('run', '.', 1, {'a': 1}, 1, 1) for _ in range(1010)] - run_names = ['Run_{}'.format(i) for i in range(1, 1011)] campaign.add_runs(runs) campaign.add_app(app_info) return campaign @@ -64,10 +66,10 @@ def test_db_file_created(campaign): def test_get_and_set_status(campaign): - run_names = ['Run_{}'.format(i) for i in range(1, 1011)] - assert(all([campaign.get_run_status(name) == Status.NEW for name in run_names])) - campaign.set_run_statuses(run_names, Status.ENCODED) - assert(all([campaign.get_run_status(name) == Status.ENCODED for name in run_names])) + run_ids = list(range(1, 1011)) + assert(all([campaign.get_run_status(id_) == Status.NEW for id_ in run_ids])) + campaign.set_run_statuses(run_ids, Status.ENCODED) + assert(all([campaign.get_run_status(id_) == Status.ENCODED for id_ in run_ids])) def test_get_num_runs(campaign): @@ -302,10 +304,10 @@ def test_mv_collation(tmp_path, app_info): new_campaign=True, name='test', info=info) campaign.tmp_path = str(tmp_path) runs = [RunInfo('run', '.', 1, params, 1, 1)] - run_names = ['Run_1'] + run_ids = [0] campaign.add_runs(runs) campaign.add_app(app_info) - results = [('Run_1', mv_data), ('Run_2', mv_data)] + results = [(0, mv_data), (1, mv_data)] campaign.store_results('test', results) assert(not campaign.get_results('test', 1).empty) return campaign diff --git a/tests/test_db_benchmark.py b/tests/test_db_benchmark.py index c724ad5fc..c62271260 100644 --- a/tests/test_db_benchmark.py +++ b/tests/test_db_benchmark.py @@ -2,6 +2,7 @@ import easyvvuq as uq import chaospy as cp import numpy as np +from easyvvuq.actions import Actions, Encode, Decode, CreateRunDirectory def pytest_namespace(): @@ -23,7 +24,9 @@ def test_draw(benchmark): delimiter='$', target_filename='input.json') decoder = uq.decoders.SimpleCSV(target_filename='output.csv', output_columns=['I']) - campaign = uq.Campaign(name='sir_benchmark', params=params, encoder=encoder, decoder=decoder) + execute = uq.actions.ExecuteLocal('test') + actions = Actions(execute) + campaign = uq.Campaign(name='sir_benchmark', params=params, actions=actions) pytest.shared = campaign vary = { "beta": cp.Uniform(0.15, 0.25), diff --git a/tests/test_decoders_simple_csv.py b/tests/test_decoders_simple_csv.py index ce1b9f083..0e4403858 100644 --- a/tests/test_decoders_simple_csv.py +++ b/tests/test_decoders_simple_csv.py @@ -196,12 +196,6 @@ def test_simple_csv(decoder): assert(df['Value'][5] == 25.950662) -def test_get_restart_dict(decoder): - restart_dict = decoder.get_restart_dict() - assert(restart_dict['target_filename'] == 'test.csv') - assert(restart_dict['output_columns'] == ['Step', 'Value']) - - def test_sim_complete(decoder): assert(decoder.sim_complete({'run_dir': os.path.join('tests', 'simple_csv')})) diff --git a/tests/test_dimension_adaptive_SC.py b/tests/test_dimension_adaptive_SC.py index e966864aa..c35633372 100755 --- a/tests/test_dimension_adaptive_SC.py +++ b/tests/test_dimension_adaptive_SC.py @@ -2,9 +2,10 @@ import numpy as np import easyvvuq as uq import matplotlib.pyplot as plt +import os import logging import pytest - +from easyvvuq.actions import CreateRunDirectory, Encode, ExecuteLocal, Decode, Actions plt.close('all') @@ -62,12 +63,13 @@ def adaptive_campaign(): target_filename='poly_in.json') decoder = uq.decoders.SimpleCSV(target_filename=output_filename, output_columns=output_columns) - + execute = ExecuteLocal(os.path.abspath("tests/sc/poly_model_anisotropic.py") + " poly_in.json") + actions = Actions(CreateRunDirectory('/tmp'), Encode(encoder), execute, Decode(decoder)) + # Add the SC app (automatically set as current app) campaign.add_app(name="sc", params=params, - encoder=encoder, - decoder=decoder) + actions=actions) # Create the sampler vary = {} @@ -80,11 +82,7 @@ def adaptive_campaign(): midpoint_level1=True, dimension_adaptive=True) campaign.set_sampler(sampler) - campaign.draw_samples() - campaign.populate_runs_dir() - campaign.apply_for_each_run_dir(uq.actions.ExecuteLocal( - "tests/sc/poly_model_anisotropic.py poly_in.json")) - campaign.collate() + campaign.execute().collate() data_frame = campaign.get_collation_result() analysis = uq.analysis.SCAnalysis(sampler=sampler, qoi_cols=output_columns) @@ -93,17 +91,13 @@ def adaptive_campaign(): for i in range(number_of_adaptations): sampler.look_ahead(analysis.l_norm) - campaign.draw_samples() - campaign.populate_runs_dir() - campaign.apply_for_each_run_dir(uq.actions.ExecuteLocal( - "tests/sc/poly_model_anisotropic.py poly_in.json")) - campaign.collate() + campaign.execute().collate() data_frame = campaign.get_collation_result() analysis.adapt_dimension('f', data_frame) campaign.apply_analysis(analysis) - print(analysis.l_norm) - print(sampler.admissible_idx) + logging.debug(analysis.l_norm) + logging.debug(sampler.admissible_idx) results = campaign.get_last_analysis() diff --git a/tests/test_empty_collate.py b/tests/test_empty_collate.py deleted file mode 100644 index 5da3e517f..000000000 --- a/tests/test_empty_collate.py +++ /dev/null @@ -1,138 +0,0 @@ -import easyvvuq as uq -import chaospy as cp -import os -import sys -import pytest -from pprint import pprint - -__copyright__ = """ - - Copyright 2018 Robin A. Richardson, David W. Wright - - This file is part of EasyVVUQ - - EasyVVUQ is free software: you can redistribute it and/or modify - it under the terms of the Lesser GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - EasyVVUQ is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - Lesser GNU General Public License for more details. - - You should have received a copy of the Lesser GNU General Public License - along with this program. If not, see . - -""" -__license__ = "LGPL" - - -# If cannonsim has not been built (to do so, run the Makefile in tests/cannonsim/src/) -# then skip this test -if not os.path.exists("tests/cannonsim/bin/cannonsim"): - pytest.skip( - "Skipping cannonsim test (cannonsim is not installed in tests/cannonsim/bin/)", - allow_module_level=True) - -CANNONSIM_PATH = os.path.realpath(os.path.expanduser("tests/cannonsim/bin/cannonsim")) - - -def test_empty_collate(tmpdir): - - # Set up a fresh campaign called "cannon" - my_campaign = uq.Campaign(name='cannon', work_dir=tmpdir) - - # Define parameter space for the cannonsim app - params = { - "angle": { - "type": "float", - "min": 0.0, - "max": 6.28, - "default": 0.79}, - "air_resistance": { - "type": "float", - "min": 0.0, - "max": 1.0, - "default": 0.2}, - "height": { - "type": "float", - "min": 0.0, - "max": 1000.0, - "default": 1.0}, - "time_step": { - "type": "float", - "min": 0.0001, - "max": 1.0, - "default": 0.01}, - "gravity": { - "type": "float", - "min": 0.0, - "max": 1000.0, - "default": 9.8}, - "mass": { - "type": "float", - "min": 0.0001, - "max": 1000.0, - "default": 1.0}, - "velocity": { - "type": "float", - "min": 0.0, - "max": 1000.0, - "default": 10.0}} - - # Create an encoder, decoder and collater for the cannonsim app - encoder = uq.encoders.GenericEncoder( - template_fname='tests/cannonsim/test_input/cannonsim.template', - delimiter='#', - target_filename='in.cannon') - decoder = uq.decoders.SimpleCSV( - target_filename='output.csv', output_columns=[ - 'Dist', 'lastvx', 'lastvy']) - - # Add the cannonsim app - my_campaign.add_app(name="cannonsim", - params=params, - encoder=encoder, - decoder=decoder) - - # Set the active app to be cannonsim (this is redundant when only one app - # has been added) - my_campaign.set_app("cannonsim") - - # Set up samplers - vary = { - "gravity": cp.Uniform(1.0, 9.8), - "mass": cp.Uniform(2.0, 10.0), - } - sampler = uq.sampling.RandomSampler(vary=vary, max_num=5) - - # Set the campaign to use this sampler - my_campaign.set_sampler(sampler) - - # Test reloading - my_campaign.save_state(tmpdir + "test_multisampler.json") - reloaded_campaign = uq.Campaign(state_file=tmpdir + "test_multisampler.json", work_dir=tmpdir) - - # Draw all samples - my_campaign.draw_samples() - - # Encode - my_campaign.populate_runs_dir() - - # Do an early collation, before anything has been executed. This means the collation element - # may attempt to add an empty dataframe to the database (which will cause issues upon subsequent - # collates due to an empty set of columns (Issue 163). - my_campaign.collate() - - # Execute - my_campaign.apply_for_each_run_dir( - uq.actions.ExecuteLocal("tests/cannonsim/bin/cannonsim in.cannon output.csv")) - - # Attempt to collate() again, now that the runs have been executed. If Issue 163 is not - # fixed then an error will occur here. - my_campaign.collate() - - -if __name__ == "__main__": - test_empty_collate("/tmp/") diff --git a/tests/test_hierarchical_sparse_grid_sc.py b/tests/test_hierarchical_sparse_grid_sc.py index 906600cc0..c194506aa 100755 --- a/tests/test_hierarchical_sparse_grid_sc.py +++ b/tests/test_hierarchical_sparse_grid_sc.py @@ -4,7 +4,7 @@ import chaospy as cp import pytest import logging - +from easyvvuq.actions import CreateRunDirectory, Encode, ExecuteLocal, Decode, Actions def exact_sobols_g_func(d=2, a=[0.0, 0.5, 3.0, 9.0, 99.0]): # for the Sobol g function, the exact (1st-order) @@ -57,12 +57,13 @@ def sparse_campaign(): target_filename='poly_in.json') decoder = uq.decoders.SimpleCSV(target_filename=output_filename, output_columns=output_columns) + execute = ExecuteLocal(os.path.abspath("tests/sc/sobol_model.py") + " poly_in.json") + actions = Actions(CreateRunDirectory('/tmp'), Encode(encoder), execute, Decode(decoder)) # Add the SC app (automatically set as current app) campaign.add_app(name="sc", params=params, - encoder=encoder, - decoder=decoder) + actions=actions) # Create the sampler vary = { @@ -78,17 +79,9 @@ def sparse_campaign(): # Associate the sampler with the campaign campaign.set_sampler(sampler) - print('Number of samples:', sampler.n_samples) - - # Will draw all (of the finite set of samples) - campaign.draw_samples() - campaign.populate_runs_dir() - - # Use this instead to run the samples using EasyVVUQ on the localhost - campaign.apply_for_each_run_dir(uq.actions.ExecuteLocal( - "tests/sc/sobol_model.py poly_in.json")) + logging.debug('Number of samples:', sampler.n_samples) - campaign.collate() + campaign.execute().collate() # Post-processing analysis analysis = uq.analysis.SCAnalysis(sampler=sampler, qoi_cols=output_columns) @@ -100,15 +93,8 @@ def sparse_campaign(): for i in range(n_adaptations): # update the sparse grid to the next level sampler.next_level_sparse_grid() + campaign.execute().collate() - # draw the new samples - campaign.draw_samples() - campaign.populate_runs_dir() - - campaign.apply_for_each_run_dir(uq.actions.ExecuteLocal( - "tests/sc/sobol_model.py poly_in.json")) - - campaign.collate() campaign.apply_analysis(analysis) results = campaign.get_last_analysis() @@ -140,6 +126,6 @@ def test_results(sparse_campaign): # check the computed Sobol indices against the analytical result for i in range(ref_sobols.size): computed_sobol = results._get_sobols_first('f', 'x%d' % (i + 1)) - print('Exact Sobol indices x%d = %.4f' % (i + 1, ref_sobols[i])) - print('Computed Sobol indices x%d = %.4f' % (i + 1, computed_sobol)) + logging.debug('Exact Sobol indices x%d = %.4f' % (i + 1, ref_sobols[i])) + logging.debug('Computed Sobol indices x%d = %.4f' % (i + 1, computed_sobol)) assert(ref_sobols[i] == pytest.approx(computed_sobol, abs=0.01)) diff --git a/tests/test_integration.py b/tests/test_integration.py index 233fdb269..ab91794eb 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -7,6 +7,8 @@ from pprint import pformat, pprint from .gauss.encoder_gauss import GaussEncoder from .gauss.decoder_gauss import GaussDecoder +from easyvvuq.actions import CreateRunDirectory, Encode, ExecuteLocal, Decode, Actions + __copyright__ = """ @@ -48,16 +50,6 @@ def execute_cannonsim(path, params): logging.basicConfig(level=logging.CRITICAL) -@pytest.fixture -def campaign_test(): - class CampaignTest: - def __init__(self, campaign_name, work_dir, db_type='sql'): - self.campaign = uq.Campaign(name=campaign_name, work_dir=work_dir, db_type=db_type) - - def run_tests(self): - pass - - @pytest.fixture def campaign(): def _campaign(work_dir, campaign_name, app_name, params, encoder, decoder, sampler, @@ -65,34 +57,14 @@ def _campaign(work_dir, campaign_name, app_name, params, encoder, decoder, sampl call_fn=None): my_campaign = uq.Campaign(name=campaign_name, work_dir=work_dir, db_type=db_type) # Add the cannonsim app + actions_ = Actions(CreateRunDirectory('/tmp'), Encode(encoder), actions, Decode(decoder)) my_campaign.add_app(name=app_name, params=params, - encoder=encoder, - decoder=decoder) + actions=actions_) my_campaign.set_app(app_name) # Set the campaign to use this sampler my_campaign.set_sampler(sampler) - # Draw 5 samples - my_campaign.draw_samples(num_samples=num_samples, replicas=replicas) - # Print the list of runs now in the campaign db - logging.debug("List of runs added:") - logging.debug(pformat(my_campaign.list_runs())) - logging.debug("---") - # Encode all runs into a local directory - logging.debug(pformat( - f"Encoding all runs to campaign runs dir {my_campaign.get_campaign_runs_dir()}")) - my_campaign.populate_runs_dir() - assert(len(my_campaign.get_campaign_runs_dir()) > 0) - assert(os.path.exists(my_campaign.get_campaign_runs_dir())) - assert(os.path.isdir(my_campaign.get_campaign_runs_dir())) - if call_fn is not None: - my_campaign.call_for_each_run(call_fn) - # Local execution - if actions is not None: - my_campaign.apply_for_each_run_dir(actions) - # Collate all data into one pandas data frame - my_campaign.collate() - logging.debug("data: %s", str(my_campaign.get_collation_result())) + my_campaign.execute(nsamples=num_samples, sequential=True).collate() # Save the state of the campaign state_file = work_dir + "{}_state.json".format(app_name) my_campaign.save_state(state_file) @@ -100,29 +72,12 @@ def _campaign(work_dir, campaign_name, app_name, params, encoder, decoder, sampl # Load state in new campaign object reloaded_campaign = uq.Campaign(state_file=state_file, work_dir=work_dir) reloaded_campaign.set_app(app_name) + reloaded_campaign.execute(nsamples=num_samples).collate() # Draw 3 more samples, execute, and collate onto existing dataframe - logging.debug("Running 3 more samples...") - reloaded_campaign.draw_samples(num_samples=num_samples, replicas=replicas) - logging.debug("List of runs added:") - logging.debug(pformat(reloaded_campaign.list_runs())) - logging.debug("---") - reloaded_campaign.populate_runs_dir() - if call_fn is not None: - reloaded_campaign.call_for_each_run(call_fn) - if actions is not None: - reloaded_campaign.apply_for_each_run_dir(actions) - logging.debug("Completed runs:") - logging.debug(pformat(reloaded_campaign.scan_completed())) - logging.debug("All completed? %s", str(reloaded_campaign.all_complete())) - reloaded_campaign.collate() - logging.debug("data:\n %s", str(reloaded_campaign.get_collation_result())) - logging.debug(reloaded_campaign) - # Create a BasicStats analysis element and apply it to the campaign + #reloaded_campaign.draw_samples(num_samples=num_samples, replicas=replicas) + #reloaded_campaign.collate() if stats is not None: reloaded_campaign.apply_analysis(stats) - logging.debug("stats:\n %s", str(reloaded_campaign.get_last_analysis())) - # Print the campaign log - logging.debug("All completed? %s", str(reloaded_campaign.all_complete())) return _campaign @@ -174,7 +129,7 @@ def test_cannonsim(tmpdir, campaign): target_filename='output.csv', output_columns=[ 'Dist', 'lastvx', 'lastvy']) # Create a collation element for this campaign - actions = uq.actions.ExecuteLocal("tests/cannonsim/bin/cannonsim in.cannon output.csv") + actions = uq.actions.ExecuteLocal(os.path.abspath("tests/cannonsim/bin/cannonsim") + " in.cannon output.csv") stats = uq.analysis.BasicStats(qoi_cols=['Dist', 'lastvx', 'lastvy']) # Make a random sampler vary = { @@ -185,18 +140,18 @@ def test_cannonsim(tmpdir, campaign): } sampler = uq.sampling.RandomSampler(vary=vary) campaign( - tmpdir, - 'cannon', - 'cannonsim', - params, - encoder, - decoder, - sampler, - actions, - stats, - vary, - 5, - 1) + work_dir=tmpdir, + campaign_name='cannon', + app_name='cannonsim', + params=params, + encoder=encoder, + decoder=decoder, + sampler=sampler, + actions=actions, + stats=stats, + vary=vary, + num_samples=5, + replicas=1) # Make a sweep sampler sweep = { "angle": [0.1, 0.2, 0.3], @@ -205,18 +160,18 @@ def test_cannonsim(tmpdir, campaign): } sampler = uq.sampling.BasicSweep(sweep=sweep) campaign( - tmpdir, - 'cannonsim', - 'cannonsim', - params, - encoder, - decoder, - sampler, - actions, - None, - sweep, - 5, - 1) + work_dir=tmpdir, + campaign_name='cannonsim', + app_name='cannonsim', + params=params, + encoder=encoder, + decoder=decoder, + sampler=sampler, + actions=actions, + stats=None, + vary=sweep, + num_samples=5, + replicas=1) # def test_gauss(tmpdir, campaign): @@ -321,7 +276,7 @@ def test_pce(tmpdir, campaign): } sampler = uq.sampling.PCESampler(vary=vary, polynomial_order=3) - actions = uq.actions.ExecuteLocal("tests/cooling/cooling_model.py cooling_in.json") + actions = uq.actions.ExecuteLocal(os.path.abspath("tests/cooling/cooling_model.py") + " cooling_in.json") stats = uq.analysis.PCEAnalysis(sampler=sampler, qoi_cols=output_columns) campaign(tmpdir, 'pce', 'pce', params, encoder, decoder, sampler, actions, stats, vary, 0, 1) @@ -355,7 +310,7 @@ def test_sc(tmpdir, campaign): "f": cp.Normal(1.0, 0.1) } sampler = uq.sampling.SCSampler(vary=vary, polynomial_order=1) - actions = uq.actions.ExecuteLocal(f"tests/sc/sc_model.py sc_in.json") + actions = uq.actions.ExecuteLocal(os.path.abspath("tests/sc/sc_model.py") + " sc_in.json") stats = uq.analysis.SCAnalysis(sampler=sampler, qoi_cols=output_columns) campaign(tmpdir, 'sc', 'sc', params, encoder, decoder, sampler, actions, stats, vary, 0, 1) diff --git a/tests/test_jinja_encoder.py b/tests/test_jinja_encoder.py index 3d439b382..1c338b7b3 100644 --- a/tests/test_jinja_encoder.py +++ b/tests/test_jinja_encoder.py @@ -4,6 +4,7 @@ import sys import pytest from easyvvuq.encoders.jinja_encoder import JinjaEncoder +from easyvvuq.actions import CreateRunDirectory, Encode, ExecuteLocal, Decode, Actions __copyright__ = """ @@ -131,15 +132,7 @@ def test_jinjaencoder(tmpdir): decoder = uq.decoders.SimpleCSV( target_filename='results.csv', output_columns=output_columns) - my_campaign.add_app(name="dales", - params=params, - encoder=encoder, - decoder=decoder) - my_campaign.verify_all_runs = False # to prevent errors on integer quantities - my_campaign.set_sampler(my_sampler) - my_campaign.draw_samples() - my_campaign.populate_runs_dir() - + if __name__ == "__main__": test_jinjaencoder("/tmp/") diff --git a/tests/test_jsondecoder.py b/tests/test_jsondecoder.py index 334c85811..de9d9349c 100644 --- a/tests/test_jsondecoder.py +++ b/tests/test_jsondecoder.py @@ -45,15 +45,6 @@ def test_missing_column(): assert("['root1', 'node1', 'abcd']" in str(excinfo.value)) -def test_get_restart_dict(): - decoder = JSONDecoder('nested.json', - [['root1', 'node1', 'leaf1'], ['root1', 'leaf2'], 'leaf3']) - restart_dict = decoder.get_restart_dict() - assert(restart_dict['target_filename'] == 'nested.json') - assert(restart_dict['output_columns'] == - [['root1', 'node1', 'leaf1'], ['root1', 'leaf2'], 'leaf3']) - - def test_sim_complete(): decoder = JSONDecoder('nested.json', [['root1', 'node1', 'leaf1'], ['root1', 'leaf2'], 'leaf3']) diff --git a/tests/test_mcmc.py b/tests/test_mcmc.py index 3cbb014e7..b19942343 100644 --- a/tests/test_mcmc.py +++ b/tests/test_mcmc.py @@ -5,22 +5,16 @@ import json import matplotlib.pyplot as plt import sys +from easyvvuq.actions import ExecutePython, Actions HOME = os.path.abspath(os.path.dirname(__file__)) -def rosenbrock(directory): - json_input = os.path.join(directory, 'input.json') - if not os.path.isfile(json_input): - sys.exit(json_input + " does not exist.") - with open(json_input, "r") as fd: - inputs = json.load(fd) +def rosenbrock(inputs): x1 = float(inputs['x1']) x2 = float(inputs['x2']) - output_filename = os.path.join(directory, inputs['outfile']) y = (1.0 - x1) ** 2 + 100.0 * (x2 - x1 ** 2) ** 2 - with open(output_filename, 'w') as fd: - json.dump({'value': 300.0 - y}, fd) + return {'value': 300.0 - y} def test_mcmc(tmp_path): @@ -34,7 +28,8 @@ def test_mcmc(tmp_path): encoder = uq.encoders.GenericEncoder(template_fname=os.path.abspath( "tutorials/rosenbrock.template"), delimiter="$", target_filename="input.json") decoder = uq.decoders.JSONDecoder("output.json", ["value"]) - campaign.add_app(name="mcmc", params=params, encoder=encoder, decoder=decoder) + actions = Actions(ExecutePython(rosenbrock)) + campaign.add_app(name="mcmc", params=params, actions=actions) vary_init = { "x1": [-1.0, 0.0, 1.0, 0.5, 0.1], "x2": [1.0, 0.0, 0.5, 1.0, 0.2] @@ -45,10 +40,9 @@ def q(x, b=1): np.random.seed(1969) sampler = uq.sampling.MCMCSampler(vary_init, q, 'value', 5) campaign.set_sampler(sampler) - action = uq.actions.ExecutePython(rosenbrock) - iterator = campaign.iterate(action) + iterator = campaign.iterate() for _ in range(200): - next(iterator).start() + next(iterator).collate() df = campaign.get_collation_result() analysis = uq.analysis.MCMCAnalysis(sampler, 'value') result = analysis.analyse(df) diff --git a/tests/test_multiapp.py b/tests/test_multiapp.py index bcf326eb2..383b078f9 100644 --- a/tests/test_multiapp.py +++ b/tests/test_multiapp.py @@ -1,4 +1,5 @@ import easyvvuq as uq +from easyvvuq.actions import Actions, Encode, Decode, CreateRunDirectory import chaospy as cp import os import sys @@ -88,10 +89,11 @@ def setup_cannonsim_app(): "mass": cp.Uniform(2.0, 10.0), } cannon_sampler = uq.sampling.RandomSampler(vary=vary, max_num=5) - cannon_action = uq.actions.ExecuteLocal("tests/cannonsim/bin/cannonsim in.cannon output.csv") + cannon_action = uq.actions.ExecuteLocal(os.path.abspath("tests/cannonsim/bin/cannonsim") + + " in.cannon output.csv") cannon_stats = uq.analysis.BasicStats(qoi_cols=['Dist', 'lastvx', 'lastvy']) - - return params, encoder, decoder, cannon_sampler, cannon_action, cannon_stats + actions = Actions(CreateRunDirectory('/tmp'), Encode(encoder), cannon_action, Decode(decoder)) + return params, cannon_sampler, actions, cannon_stats def setup_cooling_app(): @@ -129,10 +131,11 @@ def setup_cooling_app(): "t_env": cp.Uniform(15, 25) } cooling_sampler = uq.sampling.PCESampler(vary=vary, polynomial_order=3) - cooling_action = uq.actions.ExecuteLocal("tests/cooling/cooling_model.py cooling_in.json") + cooling_action = uq.actions.ExecuteLocal(os.path.abspath("tests/cooling/cooling_model.py") + + " cooling_in.json") cooling_stats = uq.analysis.PCEAnalysis(sampler=cooling_sampler, qoi_cols=output_columns) - - return params, encoder, decoder, cooling_sampler, cooling_action, cooling_stats + actions = Actions(CreateRunDirectory('/tmp'), Encode(encoder), cooling_action, Decode(decoder)) + return params, cooling_sampler, actions, cooling_stats def test_multiapp(tmpdir): @@ -140,57 +143,29 @@ def test_multiapp(tmpdir): campaign = uq.Campaign(name='multiapp', work_dir=tmpdir, db_location='sqlite:///') # Add the cannonsim app to the campaign - (params, encoder, decoder, cannon_sampler, - cannon_action, cannon_stats) = setup_cannonsim_app() + (params, cannon_sampler, cannon_action, cannon_stats) = setup_cannonsim_app() campaign.add_app(name="cannonsim", params=params, - encoder=encoder, - decoder=decoder) + actions=cannon_action) campaign.set_app("cannonsim") campaign.set_sampler(cannon_sampler) # Add the cooling app to the campaign - (params, encoder, decoder, cooling_sampler, - cooling_action, cooling_stats) = setup_cooling_app() + (params, cooling_sampler, cooling_action, cooling_stats) = setup_cooling_app() campaign.add_app(name="cooling", params=params, - encoder=encoder, - decoder=decoder) - - # Set campaign to cannonsim, apply sampler, draw all samples - campaign.set_app("cannonsim") - campaign.set_sampler(cannon_sampler) - campaign.draw_samples() - - # Set campaign to cooling model, apply sampler, draw all samples - campaign.set_app("cooling") - campaign.set_sampler(cooling_sampler) - campaign.draw_samples() + actions=cooling_action) # Populate the runs dirs for runs belonging to the cannonsim app campaign.set_app("cannonsim") - campaign.populate_runs_dir() + campaign.set_sampler(cannon_sampler) + campaign.execute().collate() # Populate the runs dirs for runs belonging to the cooling app campaign.set_app("cooling") - campaign.populate_runs_dir() - - # Execute all the cannon runs - campaign.set_app("cannonsim") - campaign.apply_for_each_run_dir(cannon_action) - - # Execute all the cooling runs - campaign.set_app("cooling") - campaign.apply_for_each_run_dir(cooling_action) - - # Collate cannon results - campaign.set_app("cannonsim") - campaign.collate() - - # Collate cooling results - campaign.set_app("cooling") - campaign.collate() + campaign.set_sampler(cooling_sampler) + campaign.execute().collate() campaign.set_app("cannonsim") @@ -201,11 +176,17 @@ def test_multiapp(tmpdir): campaign.set_sampler(cannon_sampler, True) campaign.apply_analysis(cannon_stats) + cannonsim_df = campaign.get_collation_result() + # Apply analysis for cooling app campaign.set_app("cooling") campaign.set_sampler(cooling_sampler, True) campaign.apply_analysis(cooling_stats) + cooling_df = campaign.get_collation_result + + assert(not cannonsim_df.equals(cooling_df)) + if __name__ == "__main__": test_multiapp("/tmp/") diff --git a/tests/test_multiencoder.py b/tests/test_multiencoder.py index dac9cb927..be32ffa9b 100644 --- a/tests/test_multiencoder.py +++ b/tests/test_multiencoder.py @@ -1,4 +1,5 @@ import easyvvuq as uq +from easyvvuq.actions import Actions, Encode, Decode, CreateRunDirectory import chaospy as cp import os import sys @@ -105,12 +106,14 @@ def test_multiencoder(tmpdir): decoder = uq.decoders.SimpleCSV( target_filename='output.csv', output_columns=[ 'Dist', 'lastvx', 'lastvy']) - + actions = Actions(CreateRunDirectory('/tmp'), Encode(multiencoder), + uq.actions.ExecuteLocal( + os.path.abspath("tests/cannonsim/bin/cannonsim dir5/dir6/in.cannon.2") + " output.csv"), + Decode(decoder)) # Add the cannonsim app my_campaign.add_app(name="cannonsim", params=params, - encoder=multiencoder, - decoder=decoder) + actions=actions) # Set the active app to be cannonsim (this is redundant when only one app # has been added) @@ -131,16 +134,7 @@ def test_multiencoder(tmpdir): my_campaign.save_state(tmpdir + "test_multiencoder.json") reloaded_campaign = uq.Campaign(state_file=tmpdir + "test_multiencoder.json", work_dir=tmpdir) - # Draw all samples - my_campaign.draw_samples() - - # Encode and execute. - my_campaign.populate_runs_dir() - my_campaign.apply_for_each_run_dir( - uq.actions.ExecuteLocal("tests/cannonsim/bin/cannonsim dir5/dir6/in.cannon.2 output.csv")) - - # Collate all data into one pandas data frame - my_campaign.collate() + my_campaign.execute(sequential=True).collate() # Create a BasicStats analysis element and apply it to the campaign stats = uq.analysis.BasicStats(qoi_cols=['Dist', 'lastvx', 'lastvy']) diff --git a/tests/test_multisampler.py b/tests/test_multisampler.py index c6761fd03..5a7b550b3 100644 --- a/tests/test_multisampler.py +++ b/tests/test_multisampler.py @@ -1,4 +1,5 @@ import easyvvuq as uq +from easyvvuq.actions import Actions, Encode, Decode, CreateRunDirectory import chaospy as cp import os import sys @@ -88,12 +89,12 @@ def test_multisampler(tmpdir): decoder = uq.decoders.SimpleCSV( target_filename='output.csv', output_columns=[ 'Dist', 'lastvx', 'lastvy']) - + execute = uq.actions.ExecuteLocal(os.path.abspath("tests/cannonsim/bin/cannonsim") + " in.cannon output.csv") + actions = Actions(CreateRunDirectory('/tmp'), Encode(encoder), execute, Decode(decoder)) # Add the cannonsim app my_campaign.add_app(name="cannonsim", params=params, - encoder=encoder, - decoder=decoder) + actions = actions) # Set the active app to be cannonsim (this is redundant when only one app # has been added) @@ -128,17 +129,8 @@ def test_multisampler(tmpdir): my_campaign.save_state(tmpdir + "test_multisampler.json") reloaded_campaign = uq.Campaign(state_file=tmpdir + "test_multisampler.json", work_dir=tmpdir) - # Draw all samples - my_campaign.draw_samples() - - # Encode and execute. - my_campaign.populate_runs_dir() - my_campaign.apply_for_each_run_dir( - uq.actions.ExecuteLocal("tests/cannonsim/bin/cannonsim in.cannon output.csv")) - - # Collate all data into one pandas data frame - my_campaign.collate() - + my_campaign.execute().collate() + # Create a BasicStats analysis element and apply it to the campaign stats = uq.analysis.BasicStats(qoi_cols=['Dist', 'lastvx', 'lastvy']) my_campaign.apply_analysis(stats) diff --git a/tests/test_recollate.py b/tests/test_recollate.py deleted file mode 100644 index 846517a46..000000000 --- a/tests/test_recollate.py +++ /dev/null @@ -1,131 +0,0 @@ -import easyvvuq as uq -import chaospy as cp -import os -import sys -import pytest -import logging -from pprint import pformat, pprint - -__copyright__ = """ - - Copyright 2018 Robin A. Richardson, David W. Wright - - This file is part of EasyVVUQ - - EasyVVUQ is free software: you can redistribute it and/or modify - it under the terms of the Lesser GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - EasyVVUQ is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - Lesser GNU General Public License for more details. - - You should have received a copy of the Lesser GNU General Public License - along with this program. If not, see . - -""" -__license__ = "LGPL" - - -# If cannonsim has not been built (to do so, run the Makefile in tests/cannonsim/src/) -# then skip this test -if not os.path.exists("tests/cannonsim/bin/cannonsim"): - pytest.skip( - "Skipping cannonsim test (cannonsim is not installed in tests/cannonsim/bin/)", - allow_module_level=True) - -cannonsim_path = os.path.realpath(os.path.expanduser("tests/cannonsim/bin/cannonsim")) - -logging.basicConfig(level=logging.CRITICAL) - - -def test_recollate(tmpdir): - - num_samples = 10 - ignore_list = ['Run_1', 'Run_5', 'Run_10'] - - # Define parameter space for the cannonsim app - params = { - "angle": { - "type": "float", - "min": 0.0, - "max": 6.28, - "default": 0.79}, - "air_resistance": { - "type": "float", - "min": 0.0, - "max": 1.0, - "default": 0.2}, - "height": { - "type": "float", - "min": 0.0, - "max": 1000.0, - "default": 1.0}, - "time_step": { - "type": "float", - "min": 0.0001, - "max": 1.0, - "default": 0.01}, - "gravity": { - "type": "float", - "min": 0.0, - "max": 1000.0, - "default": 9.8}, - "mass": { - "type": "float", - "min": 0.0001, - "max": 1000.0, - "default": 1.0}, - "velocity": { - "type": "float", - "min": 0.0, - "max": 1000.0, - "default": 10.0}} - - # Create an encoder and decoder for the cannonsim app - encoder = uq.encoders.GenericEncoder( - template_fname='tests/cannonsim/test_input/cannonsim.template', - delimiter='#', - target_filename='in.cannon') - output_cols = ['Dist', 'lastvx', 'lastvy'] - decoder = uq.decoders.SimpleCSV( - target_filename='output.csv', output_columns=output_cols) - - # Set up samplers - vary = { - "gravity": cp.Uniform(1.0, 9.8), - "mass": cp.Uniform(2.0, 10.0), - } - sampler = uq.sampling.RandomSampler(vary=vary, max_num=num_samples) - - my_campaign = uq.Campaign(name='test', work_dir=tmpdir, db_location='sqlite:///') - my_campaign.add_app(name="cannon", - params=params, - encoder=encoder, - decoder=decoder) - my_campaign.set_app("cannon") - - my_campaign.set_sampler(sampler) - my_campaign.draw_samples() - my_campaign.populate_runs_dir() - - actions = uq.actions.ExecuteLocal("tests/cannonsim/bin/cannonsim in.cannon output.csv") - my_campaign.apply_for_each_run_dir(actions) - my_campaign.collate() - - # Set some runs to be IGNORED, then recollate all - my_campaign.ignore_runs(ignore_list) - my_campaign.recollate() - - # Check that the right number of rows are in the collation dataframe - assert(len(my_campaign.get_collation_result().index) == num_samples - len(ignore_list)) - - # Rerun some runs - my_campaign.rerun(['Run_2', 'Run_3', 'Run_4']) - my_campaign.apply_for_each_run_dir(actions) - - -if __name__ == "__main__": - test_recollate('/tmp/') diff --git a/tests/test_vector.py b/tests/test_vector.py index 6679b1708..dde3c9067 100644 --- a/tests/test_vector.py +++ b/tests/test_vector.py @@ -8,6 +8,7 @@ from .gauss.encoder_gauss import GaussEncoder from .gauss.decoder_gauss import GaussDecoder from easyvvuq.decoders.json import JSONDecoder +from easyvvuq.actions import Actions, Encode, Decode, CreateRunDirectory __copyright__ = """ @@ -74,21 +75,16 @@ def test_gauss_vector_sc(tmpdir): target_filename='gauss_in.json') decoder = uq.decoders.SimpleCSV(target_filename="output.csv", output_columns=["numbers"]) - actions = uq.actions.ExecuteLocal("tests/gauss/gauss_json.py gauss_in.json") + execute = uq.actions.ExecuteLocal(os.path.abspath("tests/gauss/gauss_json.py") + " gauss_in.json") + actions = Actions(CreateRunDirectory('/tmp'), Encode(encoder), execute, Decode(decoder)) sampler = uq.sampling.SCSampler(vary=vary, polynomial_order=4) my_campaign = uq.Campaign(name='gauss_vector', work_dir=tmpdir) my_campaign.add_app(name="gauss_vector", params=params, - encoder=encoder, - decoder=decoder) + actions=actions) my_campaign.set_sampler(sampler) - my_campaign.draw_samples() - my_campaign.populate_runs_dir() - my_campaign.apply_for_each_run_dir(actions) - my_campaign.collate() - + my_campaign.execute().collate() data = my_campaign.get_collation_result() - print("===== DATA:\n ", data) analysis = uq.analysis.SCAnalysis(sampler=sampler, qoi_cols=["numbers"]) my_campaign.apply_analysis(analysis) results = my_campaign.get_last_analysis() @@ -132,21 +128,17 @@ def test_gauss_vector_pce(tmpdir): #decoder = JSONDecoder(target_filename='output.csv.json', output_columns=['numbers']) decoder = uq.decoders.SimpleCSV(target_filename="output.csv", output_columns=["numbers"]) - actions = uq.actions.ExecuteLocal("tests/gauss/gauss_json.py gauss_in.json") + execute = uq.actions.ExecuteLocal(os.path.abspath("tests/gauss/gauss_json.py") + " gauss_in.json") + actions = Actions(CreateRunDirectory('/tmp'), Encode(encoder), execute, Decode(decoder)) sampler = uq.sampling.PCESampler(vary=vary, polynomial_order=4) my_campaign = uq.Campaign(name='gauss_vector', work_dir=tmpdir) my_campaign.add_app(name="gauss_vector", params=params, - encoder=encoder, - decoder=decoder) + actions=actions) my_campaign.set_sampler(sampler) - my_campaign.draw_samples() - my_campaign.populate_runs_dir() - my_campaign.apply_for_each_run_dir(actions) - my_campaign.collate() + my_campaign.execute().collate() data = my_campaign.get_collation_result() - print("===== DATA:\n ", data) analysis = uq.analysis.PCEAnalysis(sampler=sampler, qoi_cols=["numbers"]) my_campaign.apply_analysis(analysis) results = my_campaign.get_last_analysis() diff --git a/tests/test_worker.py b/tests/test_worker.py deleted file mode 100644 index 135659bec..000000000 --- a/tests/test_worker.py +++ /dev/null @@ -1,169 +0,0 @@ -import easyvvuq as uq -import chaospy as cp -import os -import sys -import pytest -from easyvvuq.constants import default_campaign_prefix, Status -import subprocess - -__copyright__ = """ - - Copyright 2018 Robin A. Richardson, David W. Wright - - This file is part of EasyVVUQ - - EasyVVUQ is free software: you can redistribute it and/or modify - it under the terms of the Lesser GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - EasyVVUQ is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - Lesser GNU General Public License for more details. - - You should have received a copy of the Lesser GNU General Public License - along with this program. If not, see . - -""" -__license__ = "LGPL" - - -# If cannonsim has not been built (to do so, run the Makefile in tests/cannonsim/src/) -# then skip this test -if not os.path.exists("tests/cannonsim/bin/cannonsim"): - pytest.skip( - "Skipping cannonsim test (cannonsim is not installed in tests/cannonsim/bin/)", - allow_module_level=True) - -CANNONSIM_PATH = os.path.realpath(os.path.expanduser("tests/cannonsim/bin/cannonsim")) - - -def test_worker(tmpdir): - - # Set up a fresh campaign called "cannon" - my_campaign = uq.Campaign(name='cannon', work_dir=tmpdir) - - # Define parameter space for the cannonsim app - params = { - "angle": { - "type": "float", - "min": 0.0, - "max": 6.28, - "default": 0.79}, - "air_resistance": { - "type": "float", - "min": 0.0, - "max": 1.0, - "default": 0.2}, - "height": { - "type": "integer", - "min": 0, - "max": 1000, - "default": 1}, - "time_step": { - "type": "float", - "min": 0.0001, - "max": 1.0, - "default": 0.01}, - "gravity": { - "type": "float", - "min": 0.0, - "max": 1000.0, - "default": 9.8}, - "mass": { - "type": "float", - "min": 0.0001, - "max": 1000.0, - "default": 1.0}, - "velocity": { - "type": "float", - "min": 0.0, - "max": 1000.0, - "default": 10.0}} - - # Create an encoder, decoder and collater for the cannonsim app - encoder = uq.encoders.GenericEncoder( - template_fname='tests/cannonsim/test_input/cannonsim.template', - delimiter='#', - target_filename='in.cannon') - decoder = uq.decoders.SimpleCSV( - target_filename='output.csv', output_columns=[ - 'Dist', 'lastvx', 'lastvy']) - - # Add the cannonsim app - my_campaign.add_app(name="cannonsim", - params=params, - encoder=encoder, - decoder=decoder) - - # Set the active app to be cannonsim (this is redundant when only one app - # has been added) - my_campaign.set_app("cannonsim") - - # Make a random sampler - vary = { - "angle": cp.Uniform(0.0, 1.0), - "height": cp.DiscreteUniform(0, 100), - "velocity": cp.Normal(10.0, 1.0), - "mass": cp.Uniform(1.0, 5.0) - } - sampler1 = uq.sampling.RandomSampler(vary=vary) - - # Set the campaign to use this sampler - my_campaign.set_sampler(sampler1) - - # Draw 5 samples - my_campaign.draw_samples(num_samples=5) - - # User defined function - def encode_and_execute_cannonsim(run_id, run_data): - enc_args = [ - my_campaign.db_type, - my_campaign.db_location, - 'FALSE', - "cannon", - "cannonsim", - run_id - ] - encoder_path = os.path.realpath(os.path.expanduser("easyvvuq/tools/external_encoder.py")) - try: - subprocess.run(['python3', encoder_path] + enc_args, check=True) - except subprocess.CalledProcessError as e: - sys.exit(f"Failed during encoding of run: f{e}") - - try: - subprocess.run([CANNONSIM_PATH, "in.cannon", "output.csv"], - cwd=run_data['run_dir'], check=True) - except subprocess.CalledProcessError as e: - sys.exit(f"Failed during execution of run: f{e}") - - my_campaign.campaign_db.set_run_statuses([run_id], Status.ENCODED) # see note further down - - # Encode and execute. Note to call function for all runs with status NEW (and not ENCODED) - my_campaign.call_for_each_run(encode_and_execute_cannonsim, status=uq.constants.Status.NEW) - - #### - # Important note: In this example the execution is done with subprocess which is blocking. - # However, in practice this will be some sort of middleware (e.g. PJM) which is generally - # non-blocking. In such a case it is the job of the middleware section to keep track of - # which runs have been encoded, and updating the database (all at the end if need be) to - # indicate this to EasyVVUQ _before_ trying to run the collation/analysis section. If - # EasyVVUQ has not been informed that runs have been encoded, it will most likely just tell - # you that 'nothing has been collated' or something to that effect. - #### - - # Collate all data into one pandas data frame - my_campaign.collate() - - # Create a BasicStats analysis element and apply it to the campaign - stats = uq.analysis.BasicStats(qoi_cols=['Dist', 'lastvx', 'lastvy']) - my_campaign.apply_analysis(stats) - - bootstrap = uq.analysis.EnsembleBoot(groupby=['Dist'], qoi_cols=['lastv']) - with pytest.raises(RuntimeError, match=r".* lastv"): - my_campaign.apply_analysis(bootstrap) - - -if __name__ == "__main__": - test_worker("/tmp/") diff --git a/tests/test_yamldecoder.py b/tests/test_yamldecoder.py index 6ee4ece85..ae9c79fef 100644 --- a/tests/test_yamldecoder.py +++ b/tests/test_yamldecoder.py @@ -30,15 +30,6 @@ def test_yaml_nested(): assert((data['leaf3'] == np.array([0.2, 0.3])).all().all()) -def test_get_restart_dict(): - decoder = YAMLDecoder('nested.yml', - [['root1', 'node1', 'leaf1'], ['root1', 'leaf2'], 'leaf3']) - restart_dict = decoder.get_restart_dict() - assert(restart_dict['target_filename'] == 'nested.yml') - assert(restart_dict['output_columns'] == - [['root1', 'node1', 'leaf1'], ['root1', 'leaf2'], 'leaf3']) - - def test_sim_complete(): decoder = YAMLDecoder('nested.yml', [['root1', 'node1', 'leaf1'], ['root1', 'leaf2'], 'leaf3']) diff --git a/tutorials/basic_tutorial.ipynb b/tutorials/basic_tutorial.ipynb index 820ccc0fc..394d28140 100644 --- a/tutorials/basic_tutorial.ipynb +++ b/tutorials/basic_tutorial.ipynb @@ -60,7 +60,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "supposed-master", "metadata": {}, "outputs": [], @@ -70,7 +70,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "designing-spain", "metadata": {}, "outputs": [], @@ -121,7 +121,8 @@ "source": [ "import easyvvuq as uq\n", "import chaospy as cp\n", - "import matplotlib.pyplot as plt" + "import matplotlib.pyplot as plt\n", + "from easyvvuq.actions import CreateRunDirectory, Encode, Decode, CleanUp, ExecuteLocal, Actions" ] }, { @@ -168,7 +169,10 @@ "outputs": [], "source": [ "encoder = uq.encoders.GenericEncoder(template_fname='beam.template', delimiter='$', target_filename='input.json')\n", - "decoder = uq.decoders.JSONDecoder(target_filename='output.json', output_columns=['g1'])" + "decoder = uq.decoders.JSONDecoder(target_filename='output.json', output_columns=['g1'])\n", + "execute = ExecuteLocal('/Users/di73kuj2/Programming/EasyVVUQ/tutorials/beam input.json')\n", + "actions = Actions(CreateRunDirectory('/tmp'), \n", + " Encode(encoder), execute, Decode(decoder))" ] }, { @@ -186,7 +190,7 @@ "metadata": {}, "outputs": [], "source": [ - "campaign = uq.Campaign(name='beam', params=params, encoder=encoder, decoder=decoder)" + "campaign = uq.Campaign(name='beam', params=params, actions=actions)" ] }, { @@ -227,7 +231,7 @@ "metadata": {}, "outputs": [], "source": [ - "campaign.set_sampler(uq.sampling.SCSampler(vary=vary, polynomial_order=3))" + "campaign.set_sampler(uq.sampling.PCESampler(vary=vary, polynomial_order=3))" ] }, { @@ -242,10 +246,12 @@ "cell_type": "code", "execution_count": 7, "id": "military-struggle", - "metadata": {}, + "metadata": { + "scrolled": false + }, "outputs": [], "source": [ - "execution = campaign.sample_and_apply(action=uq.actions.ExecuteLocalV2(\"beam input.json\"), batch_size=8).start()" + "campaign.execute().collate()" ] }, { @@ -258,23 +264,244 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 8, "id": "tight-budget", "metadata": {}, "outputs": [ { "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
run_iditerationFLaDdEoutfileg1
0000000000
0100.7665591.4766560.7347160.7624540.1200000output.json-0.000008
1200.7665591.4766560.7347160.7868890.1200000output.json-0.000007
2300.7665591.4766560.7347160.8131110.1200000output.json-0.000006
3400.7665591.4766560.7347160.8375460.1200000output.json-0.000005
4500.7665591.4766560.8650050.7624540.1200000output.json-0.000007
.................................
25125201.2334411.5233441.0349950.8375460.1200000output.json-0.000007
25225301.2334411.5233441.1652840.7624540.1200000output.json-0.000007
25325401.2334411.5233441.1652840.7868890.1200000output.json-0.000006
25425501.2334411.5233441.1652840.8131110.1200000output.json-0.000005
25525601.2334411.5233441.1652840.8375460.1200000output.json-0.000005
\n", + "

256 rows × 10 columns

\n", + "
" + ], "text/plain": [ - "{'ready': 188, 'active': 8, 'finished': 60, 'failed': 0}" + " run_id iteration F L a D d E \\\n", + " 0 0 0 0 0 0 0 0 \n", + "0 1 0 0.766559 1.476656 0.734716 0.762454 0.1 200000 \n", + "1 2 0 0.766559 1.476656 0.734716 0.786889 0.1 200000 \n", + "2 3 0 0.766559 1.476656 0.734716 0.813111 0.1 200000 \n", + "3 4 0 0.766559 1.476656 0.734716 0.837546 0.1 200000 \n", + "4 5 0 0.766559 1.476656 0.865005 0.762454 0.1 200000 \n", + ".. ... ... ... ... ... ... ... ... \n", + "251 252 0 1.233441 1.523344 1.034995 0.837546 0.1 200000 \n", + "252 253 0 1.233441 1.523344 1.165284 0.762454 0.1 200000 \n", + "253 254 0 1.233441 1.523344 1.165284 0.786889 0.1 200000 \n", + "254 255 0 1.233441 1.523344 1.165284 0.813111 0.1 200000 \n", + "255 256 0 1.233441 1.523344 1.165284 0.837546 0.1 200000 \n", + "\n", + " outfile g1 \n", + " 0 0 \n", + "0 output.json -0.000008 \n", + "1 output.json -0.000007 \n", + "2 output.json -0.000006 \n", + "3 output.json -0.000005 \n", + "4 output.json -0.000007 \n", + ".. ... ... \n", + "251 output.json -0.000007 \n", + "252 output.json -0.000007 \n", + "253 output.json -0.000006 \n", + "254 output.json -0.000005 \n", + "255 output.json -0.000005 \n", + "\n", + "[256 rows x 10 columns]" ] }, - "execution_count": 14, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "execution.progress()" + "campaign.get_collation_result()" ] }, { @@ -287,7 +514,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 9, "id": "logical-consent", "metadata": {}, "outputs": [], @@ -305,13 +532,21 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 10, "id": "focal-poetry", "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/analysis/results.py:378: UserWarning: Matplotlib is currently using module://ipykernel.pylab.backend_inline, which is a non-GUI backend, so cannot show the figure.\n", + " fig.show()\n" + ] + }, { "data": { - "image/png": "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JEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkSYBRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQY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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkUAAAI+CAYAAACym37DAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/Il7ecAAAACXBIWXMAAAsTAAALEwEAmpwYAAAp30lEQVR4nO3dd5hU5d248fu7lYWll6WLgKCoCCoqRo1gb1FjwZhoEls0MdFY8E1TXxNbLHk10SQmmhiN3Rix/KxR7ApWREQUkd573WXm+f0xw7pLEwjuCtyf6/LSmTnlOYeVufecZ2cjpYQkSdKWrqC+ByBJkvRVYBRJkiRhFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJqiEifh4Rf13L69+OiKfqckz5/X4tIsZExMKIOKqu97+hIqJzfsyF9T0WSV8s/JwiqbaIGAdUAMuBDPAB8A/glpRSth6HVqciogvwKVCcUlpez2N5FhiSUrphA9YtAe4CdgW2AgaklJ7fuCOUtDnwSpG0ekeklBqTexO9CrgIuLV+h7RF2woY+V+s/xLwHWDqxhnOF4uIorral6SNwyiS1iKlNC+lNAQYBHw3InYAiIjSiLg2IsZHxLSI+FNElK1YLyKOjIh3ImJ+RHwSEQfnn28fEUMiYnZEfBwRp9dY59KIuD8i7oyIBRExIiJ6RMTPImJ6REyIiANrLP98RFwZEW/k9/NwRLSo8fo3ImJkRMzNL7tdjdcuiohJ+f2Mjoj9aozhzvxiL+T/PTd/C6h/RHwvIl6qsZ09I2JYRMzL/3vPlcb364h4Ob+fpyKi1ZrOdUScnj8ns/PnqH3++U+ArsAj+XGUrmbdnSPi7fx+7o+IeyPiN/k/w8qU0v+llF4id+VvjSJiUEQMX+m5n0bEkPx/H5bfz/z8n8elNZbrEhEpIk6NiPHAf2o8V5Rf5vsRMSo/zrER8YMa6+8bERMj4vz8n/eUiPh+jdfLIuK6iPgsf75fWvE1FxF7RMQr+T/rdyNi37Udp6TVM4qkdZBSegOYCOydf+oqoAfQB+gOdAAuBoiI3cjdbrsQaAbsA4zLr3dPfjvtgWOBKyJiYI1dHQHcATQH3gaeJPf/aQfgMuDPKw3tZOAUoB2523035sfQA7gbOBdoDTxOLipKIqIncDbQL3817KAa46tpn/y/m6WUylNKr9Z8MR9gj+X32RK4HngsIlrWWOxE4PtAG6AEuGA1+yF/Dq4Ejs8fy2f5c0VKqRswntzVu/KU0rKV1i0BHgL+DrTIH/fRq9vPOngE6BkR26x0DHfl/3sRuXPeDDgMOCtWneP0dWA7cud1ZdOBw4Em5M7L7yJi5xqvtwWakvvzPhW4KSKa51+7FtgF2JPccQ4GshHRgdyfw2/yz18APBgRrdfnwCUZRdL6mAy0iIgAzgB+mlKanVJaAFwBnJBf7lTgtpTS0ymlbEppUkrpw4joBHwNuCiltDSl9A7wV3Jvsiu8mFJ6Mj+H535yQXNVSqmKXCR0iYhmNZa/I6X0fkppEfAr4PjITeodBDyWH0MVuTfUMnJvqBmgFOgVEcUppXEppU824HwcBoxJKd2RUlqeUrob+JBc2K3wt5TSRymlJcB95CJydb6dP2dv5aPnZ0D/yM1r+iJ7AEXAjSmlqpTSv4A3NuB4SCktBh4GvgWQj6NtgSH5159PKY3I/7m+Ry7Avr7SZi5NKS3KH/PK238spfRJyhkKPMXnoQ1QBVyWP47HgYXkIq2AXPyek/96yqSUXsmfq+8Aj6eUHs+P62lgOHDohpwDaUtmFEnrrgMwm1yoNATezN+umAs8kX8eoBOwushoD6yIqBU+y293hWk1/nsJMDOllKnxGKC8xjITVtpWMdAqv6/PVryQnyA+AeiQUvqY3BWkS4HpEXHPiltV66nWPtZwPDXn8Cxeaexr3FZKaSEwa6VtrW0ck1LtnxqZsKaF18Fd5KOI3FWif+djiYjYPSKei4gZETEPOJPc+a5pjfuOiEMi4rX8LcK55MKl5vqzVprUvuKctQIasPqvq62A41Z8Lea3uxe5K26S1oNRJK2DiOhH7g36JWAmuUDZPqXULP9P05TSijf8CUC31WxmxZWmxjWe6wxM+i+G1mmlbVXlxzeZ3JvlivFHftlJACmlu1JKe+WXScDVq9n2F/1oaq191BjDhhzPyuNtRO6W3LpsawrQIX+MK3Ra08Lr4GmgdUT0IRdHd9V47S5yV406pZSaAn8CYqX1V3ve8nOhHiR31a4ipdSM3G3NlddfnZnAUlb/dTWB3BXDZjX+aZRSumodtiupBqNIWouIaBIRh5O7dXXnilsnwF/IzQdpk1+uQ0SsmENyK/D9iNgvIgryr22bUpoAvAJcGRENIqI3uVttd66653X2nYjoFRENyc05eiB/Zek+4LD8GIqB84FlwCsR0TMiBubfpJeSC7zVfdTAjPzzXdew78eBHhFxYkQURcQgoBfw6AYcx93kzlmf/LiuAF5PKY1bh3VfJXdL8Oz8OI4Edqu5QOQmxjfIPyzJn//Vxkj+duP9wDXk5ug8XePlxuSu9i3Nzx07cd0PkRJyty1nAMsj4hDgwLWvUj2mLHAbcH3kJusXRm7ieym5r58jIuKg/PMN8pO2O67H2CRhFElr8khELCD3XfgvyE0i/n6N1y8CPgZei4j5wDNAT6ielP194HfAPGAon18F+RbQhdyVkYeAS1JKz/wX47yD3ATjqeRur/wkP4bR5Oaa/J7cVYYjyE1UriT3xnxV/vmp5CZB/2zlDedvGV0OvJy/LbPHSq/PIjdp+Hxyt7oGA4enlGau70Hkz8GvyF1JmULuisgJa13p83UrgW+SC8y55I77UXIRuMJocvHXgdzk9SWsepWrpruA/YH7V7qd9UPgsvzXxsXk4nOd5G+b/iS/zhxyQTVkXdcnN4F6BDCM3G3cq4GCfGwfCfycXHBNIDfJ37/fpfXkhzdKm6iIeJ7c1as1fgL1lioiXgf+lFL6W32PRdKmw+8kJG3yIuLrEdE2f/vsu0BvcpPfJWmd+YmrkjYHPcndlmoEjAWOTSlNqd8hSdrUePtMkiQJb59JkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRKwgZ9oPeihE/zEx9W49+h7Vvtbt9e4PMWeR0lSnRtE1Tq/X/1w9FFfyfeqm3v+e73ec9eFV4okSZIwiiRJkgCjSJIkCTCKJEmSAKNIkiQJ2ISj6F8nPljfQ5AkSZuRTTaKJEmSNiajSJIkCaNIkiQJMIokSZIAo0iSJAkwiiRJkoAN/IWwXwXLly3nkdOGVD/u8Y2e9PxGz3ockSRJ2pRtslF0/IOD6nsIkiRpM+LtM0mSJIwiSZIkoJ5un015awrv3PY2KZvYev+ubPfN7Wq9nqnK8MYNrzNn7BxKGpfQ//w9adSmEcsWLOOVa15hzsez6TKgCzufvguQm1/06jWvsHDaQqIgaL9re3qftBMAM0ZO5+3b3mbeZ/PY47z+dNqzU/V+xj33KR888AEAvY7tRZcBW9fRGZAkSV81dR5F2UyWt/7yJl+/ZF/KWpbxzOCnad+vPU07Na1e5tNnxlJcXsKhNx/G+JfG894/3qX/BXtSWFzIDt/agXnj5zF//Lxa2+15ZE/a7FhBpirD0EufZ8pbU2i3czsatm7Ebj/endEPf1hr+WULljHyvpHs/9sDiAievvAp2vfrQEl5SZ2cB0mS9NVS57fPZn88m/J2jSlvW05hcSGd9+rM5Dcm1Vpm0rDJdBnQBYCO/TsybcQ0UkoUNSii9XatKSwurLV8UWkRbXasAKCwuJDmXZuzeNZiABq1aUSzLs2Igqi1zrR3plLRu4LSxqWUlJdQ0buCqW9P+ZKOWpIkfdXVeRQtmbWEhi3Lqh+XtWzIktlLVlpmMQ1bNgSgoLCA4obFVC6oXKftVy6qZPLwyVTkI2lNFs9aQsNWDWuNY/GsJWtZQ5Ikbc42q4nW2UyW165/lW0O3YbytuX1PRxJkrQJqfMoKmtZVuuKzJJZiylrUbbSMg2rb39lM1mqFldR0viL5/oM/+Nwyts1pscRX/whjg1blrF45uJa46h5BUuSJG1Z6jyKWnRvwcIpC1g4bSGZqgzjXxpP+34dai3Tvl97xj03DoCJr06kzY4VRMRqtva5EXeNoGpxFX1P6btO46jo05Zp706jcmEllQsrmfbuNCr6tN2gY5IkSZu+Ov/ps4LCAnY+bWdeuGxo7kfy9+tK085Nef/uETTv1oIOu3Wg635def2G13j8h49RUl7CHuf1r17/0R88wvIly8kuzzLp9Unsc8nXKS4rZtQDH9C4Q2OevuApALof0p2uB3Rj9phZvHz1y7m5RsMmM/Le9zn4hkMobVzKdsf14pnBTwPQ67helDYurevTIUmSviIipbTeKw166IT1X2kLcO/R96z9ctbKy1PseZQk1blBVK3z+9UPRx/1lXyvurnnv9frPXddbFYTrSVJkjaUUSRJkoRRJEmSBBhFkiRJgFEkSZIEGEWSJEmAUSRJkgQYRZIkScAGfqL1+n5IoSRtDN8qXE7nHSFTBQVFsM/JBRz606CgwL+SpLWZPi7x28MzXPv+qm/7EXEZ8EJK6Zk1rT/s9x9S3LCIPqd2/zKHuVbzJy7m8bNe44RHBn5p+6jzX/MhSRuqpAyufif319a86Ynfn5hlyfzEcf9bWM8jkzZdKaWLv+x9ZDOJgsL1++YluzxLQdGG39CKiKKU0vL1WccokrRJatomOP2WAn7RL8Oxl6Yv/KXR0pYum4FbTs/w0SuJEz6Ip4AjU0pLIuLvwKMppQci4lDgemAR8DLQ9awPjwRgzicLePikl1gwZQm9T+5K75O7AfDRkAmMuGMsmaosFb2bs/clO1FQGPxl50fZ/vguTHx1Bntf3Jt2u7SsHsvMUfMYeum7LF+SoWnnhgy4vC+lTUt4+KSXaLldU6a+OYvuh3Wkw26teO4XbwPQ8WttahxLIiKuAfYFSoGbUkp/joh9gV8Dc4BtI6IvcB/QESgEfp1SundN58g5RZI2WRVdg2wG5k2v75FIX31Tx8CBPyrg2pFFAHOBY2q+HhENgD8Dh6SUdgFa13x9ztgFHH5rf465fx+G3zSaTFWWOZ8s4OPHJ3HUXXtz/L8HEIXBmEcmALB8cYY2OzXn+IcH1AoigGcveov+5/di0JABtOjRhGE3ja5+LVuV5dgH96XPKd35z8/fZq9f7sjxDw+otf6HD3wGMC+l1A/oB5weEVvnX94ZOCel1AM4GJicUtoppbQD8MTazpFRJEnSFqDN1tClT/UV1TeBListsi0wNqX0af7x3TVf3GrfCgpLCilrXkpZy1KWzFrGxFdnMGPkXB48bij3HfUcE1+dwfwJiwGIwqDrge1XGceyBVVULqii/W6tAOh5VGemDJ9V/Xr3QzrklpufX65ffrkjO1YvM+Hl6QAnR8Q7wOtAS2Cb/Mtv1DiGEcABEXF1ROydUpq3tnPk7TNJm6xpYxMFhdC0zRcvK23pikprPcwAZeuzfmHx53P3ojDILk+QclGzx/m9Vl2+tGC95xEBFJV98RzBlAD4cUrpyZrP52+fLfp8ufRRROwMHAr8JiKeTSldtqbteqVI0iZp/ozEX8/MctDZ4XwiaeMYDXSNiC75x4O+aIUO/Vsx9qnJLJ61DIClcytZMGnxWtcpbVxMaZNiJuevDn308ATa92u56nJNiilpXMyUN/PLPTKx+rXOe7UBOCsiigEiokdENFp5GxHRHlicUroTuIbcrbU18kqRpE1G5RK4qM/y6h/J3/ukAg47zyCSNob8pOsfAk9ExCJg2Bet06J7E3Y7ZzsePfUVUhYKioK9L+5N4w4N17rewKt2rp5o3aRTQwZe0Xf1y13RNzfROqBTjYnW2x23FUMvefcD4K3IfVc0AzhqNZvYEbgmIrJAFXDW2sYVKX8NSnXvXoo9+ZKkOjeIqtV+NxER5SmlhfnQuAkYc9aHR15ft6NbNzf3/PdG/47I22eSJGmF0/OTl0cCTcn9NNoWwyiSJEkApJR+l1Lqk1LqlVL6dkpp7ROENjMbNKfowRc/8rbPahyzdw8nN0hr8c4TWW4/J0s2AwNPK+DI/6n9fdmoFxK3n5th/Hvwk3sK2OPY2q8vnp+4oFeGXY8KTvlD7idU7vlFhhf+kVg0B25f+PlfaY9dn+U/f81SWASNWwdn3lZA661y/4v+c3CGtx9LZLPQ+4DguzcUOFlbWoPxL07jpctHkLKw3bGd2fmMHrVez1RmePait5gxch4NmhVzwPX9aNKxIUvnVPLkOcOY/v4ctj2qM3tf3LvGOlle/PV7TH5jJlEQ7HbudnQ7qD2Th83k5SvfZ9bo+Rxw3a50Ozj3I/0zR83jhUvfpXLRcqIg2OXMHtBz4x+rE60l1YlsJnHbj7L84ulCWnaEn/fLsMs3go69Po+Rlp3hrL8X8ui12dVu475fZdl2n9rxsssRBRx0Npy7TabW8136whXDCyltGDz1xyz/HJzl3HsLGf1KYvTLid++l4uqS/bK8MHQxPb7GkXSyiKisEmnhhxx2540qijjweOG0mVgW1p0b1K9zKgHxlPapIRvP7U/Yx6byGvXjeTA3/WjsLSA3c7Zltlj5jP7owW1tvvmnz6irGUpJz65PymbWDqvEoDydg0ZeGVf3rnt41rLFzUoZODVO9OsSzmLpi3hgWOHEudFs5TS3I15vN4+k1QnPn4D2nYPKroGRSXBnicUMPzh2hed23QJtuodxGr+Zhr7ZmLeNOh9YO142WaPoHm7VYNm+wEFlDaM6mVmT8ztKwKqlsLySqhalvvlss0qDCJpDXZr2rkRTTo1orCkgO6HdmDcs1NrLTDu2Sn0PKoTAN0Oas+kV2eSUqK4YRHtdmlJYcmqnzv04b8+Y+czcp+1GAVBWfPchyg16diQlj2brnLlttnW5TTrUg5Ao4oyylqUwkqfuL0xGEWS6sTsSYmWnT5/3KJj7rl1kc0m7jg/w3eu3bC/sp67NUufQ3Lr9ugf9BoQnNkuw5ntMvQ+KOiwnVEkrUGHRu0+/4zHRm3LWDRtaa0FFk5fSnl+mYKiAkoaF7F0buUaN7hsfhUAb9zwIfd/83mePGcYi2cuXePyK5v23hwyVVmAT9b9MNaNUSTpK++pmxN9Dy2gZcf1j5cX78wydnjiiAtz6079ODF5FNw8sZA/Tipk5H8So150mqRUV7KZLIumLqVt3xYc9699adunOa/+duQ6rbto+lKeHfwmA67oS0pp9ffZ/wvOKZJUJ1p0CGZN+Dw+Zk/MPbcuxrya+PDFxFM3Z1m2MHfrq0F5hhOvWvuvAxjxTJaHLs9yydBCiktz+xr2UKL7HkGD8tzjPocUMObVxHZ7e7VIWo1Ji6YsqX6waOoSGlU0qLVAeZsGLJyyhPK2ZWSXZ6lcsJwGzUrWuMEGzUooKiuk64HtAOh2cAdGPTj+CwdSubCKx898jd3P7UXbPi028HDWzitFkupEt34wdUxi+qeJ5ZWJV+7Jsss31i1EfvzPQm4aX8QfxhXx7WsL2Pvk+MIg+vTtxF9+kOXCIYU0bVN7MveooYnM8sTyqsQHQxMdtvuvDk3anA2b+9ki5k9cRKYyy8ePT6LLwLa1FugysC2j/z0BgE+enEyHPVqt9ac5I4IuA9oy6Y2ZAEx8dQbNuzVe6yAylVmeOPsNehzZqfon0r4MG/SJ1v5I/uqt74/k+4nW2tK8/XiW28/N/Uj+gFMKOPoXBdx3cYauuwa7fqOAT4Ylrjs6w6I5UNwAmrWFa0fWvqD9/N9zt8NW/Ej+PwdnePmuxJzJ0Lw9DDgtOO7SQn6zf4YJIxLNct+M0qpzcOGQQrKZxK0/zDLqhUQE7HRwcPL1X/wLKKXNyZo+0Xp1Drulf3r5ihGkbGLbYzqzy5k9eePGUbTeoRlbD2zH8mUZnh38FjNHzaNB02IOuH5XmnTK/RqyOwc+ReWi5WSqspQ2LubwW/vTonsTFkxazLMXvcWy+VWUtShhwBV9ady+IdNHzOGJs99g2fwqCksKaNi6ASc8OpCPhkzguZ+/TfPun8fTrA/n900pvbMxz4tRtBEZRZKkTcH6RNEPRx/1lXyv8td8SJIkfUmMIkmSJIwiSZIkwCiSJEkCjCJJkiTAKJIkSQKMIkmSJMAokiRJAowiSZIkwCiSJEkCjCJJkiTAKJIkSQKMIkmSJMAokiRJAowiSZIkwCiSJEkCjCJJkiTAKJIkSQKMIkmSJMAokiRJAowiSZIkwCiSJEkCjCJJkiTAKJIkSQKMIkmSJMAokiRJAowiSZIkwCiSJEkCjCJJkiTAKJIkSQKMIkmSJMAokiRJAowiSZIkwCiSJEkCjCJJkiQAiup7AFuyQVRFfY9BkqS1ubnnv7eY9yqvFEmSJGEUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQB/pqPevXILeek+h6DJEmboiPOuGGj//oRrxRJkiRhFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQBRpEkSRJgFEmSJAFGkSRJEgBF9T2ADXX8vtvRuWuP6seDL7+JNu061uOIJEnSpmyTjaKS0gZce9vD9T0MSZK0mfD2mSRJEpvwlaLKZUu54JQjAWjTriODL7+pnkckSZI2ZZtsFHn7TJIkbUzePpMkScIokiRJAowiSZIkYBOOojuffLu+hyBJkjYjm2wUSZIkbUxGkSRJEvUURW+//gI/+fZBnP2tA3jozltWef2Dd4Zx4alHc/yAXrz6/BPVz8+YOokLTz2aC045knNPPownH757lXWv+p8z+el3D1/l+SH33Max+/Rk/tzZALzx4jOc970juOCUIxl8+jcZ9d7wjXiEkiRpU1Pnn1OUyWT46+8u4+Lr/0aL1hX8zxnHsuteA+nUpXv1Mq0q2vGjn1/JkHtuq7Vus5atueKP91JcUsKSxYs473tH0O9rA2nRqgKA14Y+RYOGjVbZ58xpU3h32Mu0qmhf/dyOu/Sn3177ERGM++RDrr/kXG6884lV1pUkSVuGOr9S9PGo92jbYSsq2neiuLiEr+13GMNeerbWMm3adaRLt20piNrDKy4uobikBIDlVZWkbLb6tSWLF/HofX/jmJPPWmWff//DlZx01oVERPVzZQ0bVT9etmQJQayyniRJ2nLU+ZWi2TOn0apN2+rHLVtXMOaD99Z5/ZnTpnDFRWcwddJ4TjprcPVVontuvYEjBp1CaWmDWsu/8eIztGjVhi7dt11lW6+/8DT/vOU65s+Zzc+u/vMGHpEkSdocbHITrVtVtOP6vz/CH+5+iqFPPMTc2TP5dMwopk0az+77HFBr2WVLl/CvO//MoFPPWe22dt/nAG688wkGX34T99x6Q10MX5IkfUXVeRS1aFXBzOlTqx/PmjGNFq0rNmg7nbpuw6j3hvPRyLf5ZPT7nHX8QH559olMmTCOi39yElMnjWf6lIlccMqRnHX8QGbNmMrg077JnFkzam2rV59+TJs8oXoStiRJ2vLU+e2z7tvuyJSJ45g2eQItWlfw8rOPce7F163TurOmT6W8aTNKSxuwcME8PnzvLQ4/7nv03/dgDjrqRACmT5nIlf9zJpfdeAcAtw15tXr9s44fyNW3PECTZi2YMvEz2nboTEQwdvRIlldV0rhp841/wJIkaZNQ51FUWFTEaedezG8uOI1sNsPAQ4+h09bbcM+tN9Ct5w7022s/Ph71Hr/95dksWjCf4a88x723/Z7/+8djTPzsE26/6SoigpQS3zjhFLbq1nODxvHa0CcZ+uTDFBUVUVLagJ9e+rtaE7ElSdKWJVJK673Sgy9+tP4rbQGO2bvHelXVI7ec43mUJGkDHHHGDRv9SsYmN9FakiTpy2AUSZIkYRRJkiQBRpEkSRJgFEmSJAFGkSRJEmAUSZIkAUaRJEkSYBRJkiQB9fBrPiTpq+7ymx9l5pwFVFZlOGJgHw7eZ4f6HpKkOmAUSdJKfvLd/WncqAHLKpdz/pX3sOfO3WhSXlbfw5L0JTOKJGklj/znHV57ZywAM2cvZPL0uUaRtAUwiiSphhGjJ/LuqAlcc9FxlJYU8/PrHqSqKlPfw5JUB5xoLUk1LFqyjPKGpZSWFDNx6mxGj51a30OSVEeMIkmqYZfttyKTTfzwkju4/V+v0LNr2/oekqQ64u0zSaqhuLiIS39yZH0PQ1I98EqRJEkSRpEkSRJgFEmSJAHOKZK0GXnz/XH89b4XyGQTB+61PccevOsqy7w0/CPufvR1INi6YysuOO1gAP7+4MsMf/9TAAYduht79+sBQEqJOx9+lZff/JiCguCQr+/IEQP7kFLiL/e+wPD3x1FaUsS53zuAbp3b8N7oCdx634vV+5s4dQ4Xnn4we/Tp9uWfAEn/FaNI0mYhk83y57uf57Jzj6Zl83LOv/Jeduu9NZ3bt6xeZvK0udz/xHCuvvA4yhs1YO78xQAMG/Epn0yYzg2/PJGq5Rl+ft2D7LLDVjQsK+XZV0Yxc85Cbv7fkygoiOp13nz/MyZPn8uff30yoz+dyh//+RzX/mwQvXt24oZfnQjAgkVL+cEvb6dvr851f0IkrTdvn0naLIz5dBrt2jSjbeumFBcVsveu2/D6u2NrLfPkS+9z2L69KW/UAIBmTRoCMGHybLbfpgOFhQU0KC2mS8dWvDXyMwD+3wsjGHTYbhQURK11Xn93LAP22JaIYNuu7Vi0ZBmz5y2qtb+X3/yYXXboQmlJ8Zd67JI2DqNI0mZh1tyFtGpeXv24VfNyZs2tHSmTp81l0rS5DP7t/Vxw1b28+f44ALbulIugZZVVzF+4hBGjJzJjzkIAps6Yx0vDx3De5fdw6Y0PM3na3Or9tW7RuHrbLZuVMyu/zgovDv+IffK34SR99RlFkrYYmWyWKdPncsX53+SC0w7mpjv/w8LFy+jbayt23aELg6++n2v++gTbdm1HQeSuDFUtz1BcXMj1vziBA/fanhv/8cw67Wv2vEV8Nmkmfbf31pm0qTCKJG0WWjYrZ2aNKzUz5yykZbNGtZZp1byc3Xp3paiwkLatmtK+TTOmTJ8LwPGH9uOGX53Ir889mpQSHSqaV2+3f9/cJOn+fbsxbuLM6udnzF5Qve1ZcxfSssaVqpeGj2GPPt0oKiz8Uo5X0sZnFEnaLGzTpYLJ0+cydeY8qpZneHH4GHbfqWutZXbfqSsjPpoIwPyFS5g8fS4VrZqQyWaZv3AJAJ9OnMm4STOrJ0fv0acrI0bn1nn/o0m0r2gGwG47bc1zr31ISokPx06hYVkpLZp+HmEvDBvNPrt560zalPjTZ5I2C4WFBfzghH259IaHyWaz7P+17encviX/HPIa3bdqw+47dWXn7bfinQ/G86NL76AgCvjeMXvRpLyMyqrl/OzaBwAoa1DCeaccRGFh7nvGYw7eletvfZIhz7xDg9JifnzSfgDsukMX3hwxjh/88nZKS4r5yXf3rx7LtJnzmTlnITts07HuT4SkDRYppfVe6cEXP1r/lbYAx+zdI9Zn+UduOcfzKEnSBjjijBvW6z13XXj7TJIkCaNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJgEgp1fcYJEmS6p1XiiRJkjCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgCjSJIkCTCKJEmSAKNIkiQJMIokSZIAo0iSJAkwiiRJkgD4/0BjmqZtBRgIAAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] @@ -337,13 +572,35 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "explicit-catering", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'g1': {'F': array([0.13443363]),\n", + " 'L': array([0.01210006]),\n", + " 'a': array([0.69607479]),\n", + " 'D': array([0.14127507])}}" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "results.sobols_first()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "indonesian-palmer", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/tutorials/kubernetes_tutorial.ipynb b/tutorials/kubernetes_tutorial.ipynb index c679d804c..ed2f0137b 100644 --- a/tutorials/kubernetes_tutorial.ipynb +++ b/tutorials/kubernetes_tutorial.ipynb @@ -44,27 +44,10 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "irish-baker", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "FROM ubuntu:latest\r\n", - "\r\n", - "RUN apt-get update && \\\r\n", - " apt-get install -y python3-pip && \\\r\n", - " apt-get install -y git && \\\r\n", - " apt-get install -y tini && \\\r\n", - " pip3 install easyvvuq && \\\r\n", - " git clone https://github.com/UCL-CCS/EasyVVUQ.git\r\n", - "\r\n", - "ENTRYPOINT [\"tini\", \"--\"]\r\n" - ] - } - ], + "outputs": [], "source": [ "!cat kubernetes/Dockerfile" ] @@ -111,31 +94,10 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "narrow-integral", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[1;33mWARNING:\u001b[0m Starting in January 2021, clusters will use the Regular release channel by default when `--cluster-version`, `--release-channel`, `--no-enable-autoupgrade`, and `--no-enable-autorepair` flags are not specified.\n", - "\u001b[1;33mWARNING:\u001b[0m Currently VPC-native is not the default mode during cluster creation. In the future, this will become the default mode and can be disabled using `--no-enable-ip-alias` flag. Use `--[no-]enable-ip-alias` flag to suppress this warning.\n", - "\u001b[1;33mWARNING:\u001b[0m Starting with version 1.18, clusters will have shielded GKE nodes by default.\n", - "\u001b[1;33mWARNING:\u001b[0m Your Pod address range (`--cluster-ipv4-cidr`) can accommodate at most 1008 node(s). \n", - "\u001b[1;33mWARNING:\u001b[0m Starting with version 1.19, newly created clusters and node-pools will have COS_CONTAINERD as the default node image when no image type is specified.\n", - "Creating cluster easyvvuq in us-central1-f... Cluster is being health-checked..\n", - ".⠏ \n", - "Creating cluster easyvvuq in us-central1-f... Cluster is being health-checked (\n", - "master is healthy)...done. \n", - "Created [https://container.googleapis.com/v1/projects/graphite-flare-278712/zones/us-central1-f/clusters/easyvvuq].\n", - "To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-f/easyvvuq?project=graphite-flare-278712\n", - "kubeconfig entry generated for easyvvuq.\n", - "NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS\n", - "easyvvuq us-central1-f 1.17.15-gke.800 35.238.138.25 e2-medium 1.17.15-gke.800 3 RUNNING\n" - ] - } - ], + "outputs": [], "source": [ "!gcloud container clusters create easyvvuq" ] @@ -150,19 +112,20 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "julian-glasgow", "metadata": {}, "outputs": [], "source": [ "import easyvvuq as uq\n", "import chaospy as cp\n", - "import matplotlib.pyplot as plt" + "import matplotlib.pyplot as plt\n", + "from easyvvuq.actions import CreateRunDirectory, Encode, Decode, ExecuteKubernetes, Actions" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "recorded-refrigerator", "metadata": {}, "outputs": [], @@ -179,28 +142,42 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "found-consumer", "metadata": {}, "outputs": [], "source": [ "encoder = uq.encoders.GenericEncoder(template_fname='sir.template', delimiter='$', target_filename='input.json')\n", - "decoder = uq.decoders.SimpleCSV(target_filename='output.csv', output_columns=['I'])" + "decoder = uq.decoders.SimpleCSV(target_filename='output.csv', output_columns=['I'])\n", + "execute = ExecuteKubernetes(\n", + " \"orbitfold/easyvvuq:latest\",\n", + " \"/EasyVVUQ/tutorials/sir /config/input.json && cat output.csv\",\n", + " output_file_name='output.csv')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "going-break", + "metadata": {}, + "outputs": [], + "source": [ + "actions = Actions(CreateRunDirectory('/tmp'), Encode(encoder), execute, Decode(decoder))" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "administrative-north", "metadata": {}, "outputs": [], "source": [ - "campaign = uq.Campaign(name='sir', params=params, encoder=encoder, decoder=decoder)" + "campaign = uq.Campaign(name='sir', params=params, actions=actions)" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "diverse-desktop", "metadata": {}, "outputs": [], @@ -213,7 +190,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "alleged-method", "metadata": {}, "outputs": [], @@ -241,35 +218,22 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "agricultural-radiation", - "metadata": {}, + "metadata": { + "scrolled": true + }, "outputs": [], "source": [ - "execution = campaign.sample_and_apply(\n", - " action=uq.actions.ExecuteKubernetes(\n", - " \"orbitfold/easyvvuq:latest\",\n", - " \"/EasyVVUQ/tutorials/sir /config/input.json && cat output.csv\"), \n", - " batch_size=8).start()" + "execution = campaign.execute(sequential=True)" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "id": "quantitative-catch", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'ready': 0, 'active': 0, 'finished': 36, 'failed': 0}" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "execution.progress()" ] @@ -284,7 +248,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "id": "given-sample", "metadata": {}, "outputs": [], @@ -294,66 +258,20 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "id": "ranking-store", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "result.plot_sobols_first('I', xlabel='t')" ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "id": "awful-register", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "result.plot_moments('I', xlabel='t')" ] diff --git a/tutorials/mcmc_tutorial.ipynb b/tutorials/mcmc_tutorial.ipynb index 0e5ca91ee..725c052fd 100644 --- a/tutorials/mcmc_tutorial.ipynb +++ b/tutorials/mcmc_tutorial.ipynb @@ -27,23 +27,33 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, + "id": "extreme-scout", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": 2, "id": "piano-lodge", "metadata": {}, "outputs": [], "source": [ - "import numpy as np\n", - "def model(a, b, c, d):\n", - " a = int(a)\n", - " b = int(b)\n", - " c = int(c)\n", - " d = int(d)\n", + "def model(params):\n", + " import numpy as np\n", + " a = int(params['a'])\n", + " b = int(params['b'])\n", + " c = int(params['c'])\n", + " d = int(params['d'])\n", " x = np.linspace(0, 1, 50)\n", - " return np.random.poisson(\n", + " return {'Values' : list(np.random.poisson(\n", " a * (0.5 * np.sin(2.0 * np.pi * x) + 1.0) +\\\n", " b * (0.5 * np.sin(4.0 * np.pi * x) + 1.0) +\\\n", " c * (0.5 * np.sin(6.0 * np.pi * x) + 1.0) +\\\n", - " d * (0.5 * np.sin(8.0 * np.pi * x) + 1.0))" + " d * (0.5 * np.sin(8.0 * np.pi * x) + 1.0)))}" ] }, { @@ -56,14 +66,27 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "periodic-vulnerability", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], "source": [ "import matplotlib.pyplot as plt\n", "for _ in range(20):\n", - " plt.plot(model(50, 80, 50, 80), '.')" + " plt.plot(model({'a': 50, 'b': 80, 'c': 50, 'd': 80})['Values'], '.')" ] }, { @@ -76,7 +99,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "level-produce", "metadata": {}, "outputs": [], @@ -113,12 +136,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "dominant-newman", "metadata": {}, "outputs": [], "source": [ "import easyvvuq as uq\n", + "from easyvvuq.actions import ExecutePython, Actions\n", "import scipy.special\n", "import chaospy as cp\n", "import numpy as np\n", @@ -137,7 +161,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "varied-advocacy", "metadata": {}, "outputs": [], @@ -151,12 +175,8 @@ " \"ensemble_id\": {\"type\": \"integer\", \"default\": 0},\n", " \"chain_id\": {\"type\": \"integer\", \"default\": 0}\n", "}\n", - "encoder = uq.encoders.GenericEncoder(template_fname=\"mcmc.template\", \n", - " delimiter=\"$\", target_filename=\"input.json\")\n", - "decoder = uq.decoders.SimpleCSV(\"output.csv\", [\"Values\"])\n", - "campaign = uq.Campaign(\n", - " name=\"mcmc\", encoder=encoder, decoder=decoder, \n", - " params=params, work_dir='.')" + "actions = Actions(ExecutePython(model))\n", + "campaign = uq.Campaign(name=\"mcmc\", actions=actions, params=params, work_dir='.')" ] }, { @@ -169,7 +189,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "integral-demographic", "metadata": {}, "outputs": [], @@ -192,12 +212,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "chemical-student", "metadata": {}, "outputs": [], "source": [ - "def proposal(x, b=5):\n", + "def proposal(x, b=2.5):\n", " return cp.J(cp.Normal(x['a'], b), \n", " cp.Normal(x['b'], b), \n", " cp.Normal(x['c'], b), \n", @@ -214,7 +234,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "binding-salon", "metadata": {}, "outputs": [], @@ -236,7 +256,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "perceived-cosmetic", "metadata": {}, "outputs": [], @@ -259,23 +279,514 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "seventh-halloween", "metadata": {}, "outputs": [], "source": [ - "iterator = campaign.iterate(uq.actions.ExecuteLocalV2('mcmc input.json'), batch_size=8, mark_invalid=True)" + "iterator = campaign.iterate(mark_invalid=True, sequential=True)" ] }, { "cell_type": "code", - "execution_count": null, - "id": "received-parts", - "metadata": {}, - "outputs": [], - "source": [ - "for _ in range(2000):\n", - " next(iterator).start().wait(1)" + "execution_count": 12, + "id": "identified-above", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n", + "/Users/di73kuj2/Programming/EasyVVUQ/easyvvuq/sampling/mcmc.py:123: RuntimeWarning: invalid value encountered in double_scalars\n", + " r = min(1.0, (f_y / self.f_x[chain_id]) * (q_xy / q_yx))\n" + ] + } + ], + "source": [ + "for _ in range(10000):\n", + " next(iterator).collate()" ] }, { @@ -395,7 +906,7 @@ "metadata": {}, "outputs": [], "source": [ - "result.plot_hist('a', skip=200)" + "result.plot_hist('a', skip=1000)" ] }, { @@ -405,7 +916,7 @@ "metadata": {}, "outputs": [], "source": [ - "result.plot_hist('b', skip=200)" + "result.plot_hist('b', skip=1000)" ] }, { @@ -415,7 +926,7 @@ "metadata": {}, "outputs": [], "source": [ - "result.plot_hist('c', skip=200)" + "result.plot_hist('c', skip=1000)" ] }, { @@ -425,16 +936,8 @@ "metadata": {}, "outputs": [], "source": [ - "result.plot_hist('d', skip=200)" + "result.plot_hist('d', skip=1000)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "rubber-sitting", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/tutorials/vector_qoi_tutorial.ipynb b/tutorials/vector_qoi_tutorial.ipynb index cc5b093ad..b16dc0189 100644 --- a/tutorials/vector_qoi_tutorial.ipynb +++ b/tutorials/vector_qoi_tutorial.ipynb @@ -284,6 +284,24 @@ "campaign.set_sampler(uq.sampling.PCESampler(vary=vary, polynomial_order=5))" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "initial-hurricane", + "metadata": {}, + "outputs": [], + "source": [ + "actions = [\n", + " CreateRunDirectory('/tmp'), \n", + " TemplateEncoder(template_fname='sir.template', delimiter='$', target_filename='input.json'), \n", + " ExecuteLocal('sir input.json'),\n", + " CSVDecoder(target_filename='output.csv', output_columns=['I']),\n", + " CleanUp()\n", + "]\n", + "campaign.add_app('sir', actions)\n", + "campaign.execute().collate()" + ] + }, { "cell_type": "code", "execution_count": 12,