From faa92902f9de7b85e68a0d01544c81b0159c87af Mon Sep 17 00:00:00 2001 From: LucR31 <94859181+LucR31@users.noreply.github.com> Date: Wed, 20 Dec 2023 15:41:10 +0000 Subject: [PATCH] Multi day workflow (#19) * Multi day workflow * Update import. --- aiida_flexpart/utils.py | 58 ++- .../workflows/multi_dates_workflow.py | 369 +++++++++++++----- examples/example_workflow_combi.py | 219 +++++++++++ pyproject.toml | 1 + 4 files changed, 550 insertions(+), 97 deletions(-) create mode 100644 examples/example_workflow_combi.py diff --git a/aiida_flexpart/utils.py b/aiida_flexpart/utils.py index 5371920..3c88be7 100644 --- a/aiida_flexpart/utils.py +++ b/aiida_flexpart/utils.py @@ -2,6 +2,7 @@ """Utilties to convert between python and fortran data types and formats.""" import numbers +import datetime import importlib import numpy import jinja2 @@ -176,8 +177,63 @@ def convert_input_to_namelist_entry(key, val, mapping=None): def fill_in_template_file(folder, fname, data): """Create an input file based on the standard templates.""" - with folder.open(fname, 'w') as infile: + + if 'ifs' in fname: + fname_ = fname[:-4] + else: + fname_ = fname + + with folder.open(fname_, 'w') as infile: template = jinja2.Template( importlib.resources.read_text('aiida_flexpart.templates', fname + '.j2')) infile.write(template.render(data=data)) + + +def reformat_locations(dict_, model): + """reformat locations""" + for key in dict_.keys(): + if 'longitude' in dict_[key]: + dict_[key]['longitude_of_lower_left_corner'] = dict_[key][ + 'longitude'] + dict_[key]['longitude_of_upper_right_corner'] = dict_[key][ + 'longitude'] + dict_[key]['latitude_of_lower_left_corner'] = dict_[key][ + 'latitude'] + dict_[key]['latitude_of_upper_right_corner'] = dict_[key][ + 'latitude'] + + if model in dict_[key]['level']: + dict_[key]['lower_z_level'] = dict_[key]['level'][model] + dict_[key]['upper_z_level'] = dict_[key]['level'][model] + else: + dict_[key]['lower_z_level'] = dict_[key]['level']['default'] + dict_[key]['upper_z_level'] = dict_[key]['level']['default'] + + dict_[key]['level_type'] = dict_[key]['level_type'][model] + + dict_[key].pop('longitude') + dict_[key].pop('latitude') + dict_[key].pop('level') + return dict_ + + +def get_simulation_period(date, age_class_time, release_duration, + simulation_direction): + """Dealing with simulation times.""" + #initial values + simulation_beginning_date = datetime.datetime.strptime( + date, '%Y-%m-%d %H:%M:%S') + age_class_time = datetime.timedelta(seconds=age_class_time) + release_duration = datetime.timedelta(seconds=release_duration + 3600) + + if simulation_direction > 0: #forward + simulation_ending_date = simulation_beginning_date + release_duration + age_class_time + else: #backward + simulation_ending_date = release_duration + simulation_beginning_date + simulation_beginning_date -= age_class_time + + return datetime.datetime.strftime(simulation_ending_date, + '%Y%m%d%H'), datetime.datetime.strftime( + simulation_beginning_date, + '%Y%m%d%H') diff --git a/aiida_flexpart/workflows/multi_dates_workflow.py b/aiida_flexpart/workflows/multi_dates_workflow.py index ac0644d..a2d3c03 100644 --- a/aiida_flexpart/workflows/multi_dates_workflow.py +++ b/aiida_flexpart/workflows/multi_dates_workflow.py @@ -2,29 +2,16 @@ """Flexpart multi-dates WorkChain.""" from aiida import engine, plugins, orm from aiida_shell import launch_shell_job -from aiida.engine import calcfunction, while_, if_ -import datetime - -FlexpartCalculation = plugins.CalculationFactory('flexpart.cosmo') - -def get_simulation_period(date, - age_class_time, - release_duration, - simulation_direction - ): - """Dealing with simulation times.""" - #initial values - simulation_beginning_date = datetime.datetime.strptime(date,'%Y-%m-%d %H:%M:%S') - age_class_time = datetime.timedelta(seconds=age_class_time) - release_duration = datetime.timedelta(seconds=release_duration+3600) - - if simulation_direction>0: #forward - simulation_ending_date=simulation_beginning_date+release_duration+age_class_time - else: #backward - simulation_ending_date=release_duration+simulation_beginning_date - simulation_beginning_date-=age_class_time - - return datetime.datetime.strftime(simulation_ending_date,'%Y%m%d%H'), datetime.datetime.strftime(simulation_beginning_date,'%Y%m%d%H') +from aiida_flexpart.utils import get_simulation_period + +#plugins +FlexpartCosmoCalculation = plugins.CalculationFactory('flexpart.cosmo') +FlexpartIfsCalculation = plugins.CalculationFactory('flexpart.ifs') +FlexpartPostCalculation = plugins.CalculationFactory('flexpart.post') + +#possible models +cosmo_models = ['cosmo7', 'cosmo1', 'kenda1'] +ECMWF_models = ['IFS_GL_05', 'IFS_GL_1', 'IFS_EU_02', 'IFS_EU_01'] class FlexpartMultipleDatesWorkflow(engine.WorkChain): @@ -33,30 +20,64 @@ class FlexpartMultipleDatesWorkflow(engine.WorkChain): def define(cls, spec): """Specify inputs and outputs.""" super().define(spec) - # Basic Inputs + + #codes spec.input('fcosmo_code', valid_type=orm.AbstractCode) spec.input('check_meteo_cosmo_code', valid_type=orm.AbstractCode) - spec.input('simulation_dates', valid_type=orm.List, + spec.input('fifs_code', valid_type=orm.AbstractCode) + spec.input('check_meteo_ifs_code', valid_type=orm.AbstractCode) + spec.input('post_processing_code', valid_type=orm.AbstractCode) + + # Basic Inputs + spec.input('simulation_dates', + valid_type=orm.List, help='A list of the starting dates of the simulations') - spec.input('model', valid_type=orm.Str, required=True) - + spec.input('model', valid_type=orm.List, required=True) + spec.input('model_offline', valid_type=orm.List, required=True) + spec.input('offline_integration_time', valid_type=orm.Int) + spec.input('integration_time', + valid_type=orm.Int, + help='Integration time in hours') + spec.input( + 'parent_calc_folder', + valid_type=orm.RemoteData, + required=False, + help= + 'Working directory of a previously ran calculation to restart from.' + ) + #model settings spec.input('input_phy', valid_type=orm.Dict) spec.input('command', valid_type=orm.Dict) spec.input('release_settings', valid_type=orm.Dict) - spec.input('locations', valid_type=orm.Dict, + spec.input('locations', + valid_type=orm.Dict, help='Dictionary of locations properties.') - + #meteo related inputs - spec.input('meteo_inputs', valid_type=orm.Dict, + spec.input('meteo_inputs', + valid_type=orm.Dict, + required=False, help='Meteo models input params.') - spec.input('meteo_path', valid_type=orm.RemoteData, - required=True, help='Path to the folder containing the meteorological input data.') + spec.input('meteo_inputs_offline', + valid_type=orm.Dict, + required=False, + help='Meteo models input params.') + spec.input( + 'meteo_path', + valid_type=orm.List, + required=False, + help='Path to the folder containing the meteorological input data.' + ) + spec.input( + 'meteo_path_offline', + valid_type=orm.List, + required=False, + help='Path to the folder containing the meteorological input data.' + ) spec.input('gribdir', valid_type=orm.Str, required=True) #others - spec.input('integration_time', valid_type=orm.Int, - help='Integration time in hours') spec.input('outgrid', valid_type=orm.Dict) spec.input('outgrid_nest', valid_type=orm.Dict, required=False) spec.input('species', valid_type=orm.RemoteData, required=True) @@ -65,10 +86,20 @@ def define(cls, spec): required=False, dynamic=True, help='#TODO') + spec.input_namespace('land_use_ifs', + valid_type=orm.RemoteData, + required=False, + dynamic=True) - spec.expose_inputs(FlexpartCalculation, + spec.expose_inputs(FlexpartCosmoCalculation, + include=['metadata.options'], + namespace='flexpartcosmo') + spec.expose_inputs(FlexpartIfsCalculation, include=['metadata.options'], - namespace='flexpart') + namespace='flexpartifs') + spec.expose_inputs(FlexpartPostCalculation, + include=['metadata.options'], + namespace='flexpartpost') # Outputs #spec.output('output_file', valid_type=orm.SinglefileData) @@ -76,96 +107,242 @@ def define(cls, spec): # What the workflow will do, step-by-step spec.outline( cls.setup, - while_(cls.condition)( - if_(cls.prepare_meteo_folder)( - cls.run_simulation - ) + engine.while_(cls.condition)( + engine.if_(cls.run_cosmo)(engine.if_( + cls.prepare_meteo_folder_cosmo)(cls.run_cosmo_simulation)), + engine.if_(cls.run_ifs)(engine.if_( + cls.prepare_meteo_folder_ifs)(cls.run_ifs_simulation)), + cls.post_processing, ), cls.results, ) def condition(self): - return True if self.ctx.index < len(self.ctx.simulation_dates) else False + """multi dates loop""" + return self.ctx.index < len(self.ctx.simulation_dates) + + def run_cosmo(self): + """run cosmo simulation""" + if all(mod in cosmo_models + for mod in self.inputs.model) and self.inputs.model: + return True + return False + + def run_ifs(self): + """run ifs simulation""" + if (all(mod in ECMWF_models for mod in self.inputs.model) + or all(mod in ECMWF_models + for mod in self.inputs.model_offline) + and self.inputs.model and self.inputs.model_offline): + return True + return False def setup(self): """Prepare a simulation.""" - self.report(f'starting setup') + self.report('starting setup') self.ctx.index = 0 self.ctx.simulation_dates = self.inputs.simulation_dates self.ctx.integration_time = self.inputs.integration_time - + self.ctx.offline_integration_time = self.inputs.offline_integration_time + #model settings self.ctx.release_settings = self.inputs.release_settings self.ctx.command = self.inputs.command self.ctx.input_phy = self.inputs.input_phy self.ctx.locations = self.inputs.locations - + #others self.ctx.outgrid = self.inputs.outgrid self.ctx.outgrid_nest = self.inputs.outgrid_nest self.ctx.species = self.inputs.species self.ctx.land_use = self.inputs.land_use - def prepare_meteo_folder(self): - e_date, s_date = get_simulation_period(self.ctx.simulation_dates[self.ctx.index], - self.inputs.integration_time.value * 3600, - self.ctx.command.get_dict()["release_duration"], - self.ctx.command.get_dict()["simulation_direction"]) - - self.report(f'prepare meteo from {s_date} to {e_date}') - - results, node = launch_shell_job( - self.inputs.check_meteo_cosmo_code, - arguments=' -s {sdate} -e {edate} -g {gribdir} -m {model} -a', - nodes={ - 'sdate': orm.Str(s_date), - 'edate': orm.Str(e_date), - 'gribdir': self.inputs.gribdir, - 'model': self.inputs.model - }) - - return node.is_finished_ok - - def run_simulation(self): - """Run calculations for equation of state.""" - - self.report('starting flexpart cosmo') - - builder = FlexpartCalculation.get_builder() - builder.code = self.inputs.fcosmo_code - - #update command file - new_dict = self.ctx.command.get_dict() - new_dict['simulation_date'] = self.ctx.simulation_dates[self.ctx.index] + def prepare_meteo_folder_ifs(self): + """prepare meteo folder""" + age_class_ = self.inputs.integration_time.value * 3600 + if self.ctx.offline_integration_time > 0: + age_class_ = self.inputs.offline_integration_time.value * 3600 + e_date, s_date = get_simulation_period( + self.ctx.simulation_dates[self.ctx.index], age_class_, + self.ctx.command.get_dict()['release_duration'], + self.ctx.command.get_dict()['simulation_direction']) + + self.report(f'preparing meteo from {s_date} to {e_date}') + + if all(mod in ECMWF_models + for mod in self.inputs.model) and self.inputs.model: + model_list = self.inputs.model + else: + model_list = self.inputs.model_offline + + node_list = [] + for mod in model_list: + self.report(f'transfering {mod} meteo') + node = launch_shell_job( + self.inputs.check_meteo_ifs_code, + arguments=' -s {sdate} -e {edate} -g {gribdir} -m {model} -a', + nodes={ + 'sdate': orm.Str(s_date), + 'edate': orm.Str(e_date), + 'gribdir': self.inputs.gribdir, + 'model': orm.Str(mod) + }) + node_list.append(node) + + if all(node.is_finished_ok for node in node_list): + self.report('ALL meteo OK') + return True + + self.report('FAILED to transfer meteo') + self.ctx.index += 1 + return False + + def prepare_meteo_folder_cosmo(self): + """prepare meteo folder""" + e_date, s_date = get_simulation_period( + self.ctx.simulation_dates[self.ctx.index], + self.inputs.integration_time.value * 3600, + self.ctx.command.get_dict()['release_duration'], + self.ctx.command.get_dict()['simulation_direction']) + + self.report(f'preparing meteo from {s_date} to {e_date}') + + node_list = [] + for mod in self.inputs.model: + self.report(f'transfering {mod} meteo') + node = launch_shell_job( + self.inputs.check_meteo_cosmo_code, + arguments=' -s {sdate} -e {edate} -g {gribdir} -m {model} -a', + nodes={ + 'sdate': orm.Str(s_date), + 'edate': orm.Str(e_date), + 'gribdir': self.inputs.gribdir, + 'model': orm.Str(mod) + }) + node_list.append(node) + + if all(node.is_finished_ok for node in node_list): + self.report('ALL meteo OK') + return True + + self.report('FAILED to transfer meteo') + self.ctx.index += 1 + return False + + def post_processing(self): + """post processing""" + self.report('starting post-processsing') + builder = FlexpartPostCalculation.get_builder() + builder.code = self.inputs.post_processing_code + builder.input_dir = self.ctx.calculations[-1].outputs.remote_folder + + if self.ctx.offline_integration_time > 0: + self.report( + f'main: {self.ctx.calculations[-2].outputs.remote_folder}') + self.report( + f'offline: {self.ctx.calculations[-1].outputs.remote_folder}') + builder.input_dir = self.ctx.calculations[-2].outputs.remote_folder + builder.input_offline_dir = self.ctx.calculations[ + -1].outputs.remote_folder + + builder.metadata.options = self.inputs.flexpartpost.metadata.options + + running = self.submit(builder) + self.to_context(calculations=engine.append_(running)) + + def run_cosmo_simulation(self): + """Run calculations for equation of state.""" + + self.report( + f'starting flexpart cosmo {self.ctx.simulation_dates[self.ctx.index]}' + ) + + builder = FlexpartCosmoCalculation.get_builder() + builder.code = self.inputs.fcosmo_code + + #update command file + new_dict = self.ctx.command.get_dict() + new_dict['simulation_date'] = self.ctx.simulation_dates[self.ctx.index] + new_dict['age_class'] = self.inputs.integration_time * 3600 + new_dict.update(self.inputs.meteo_inputs) + + #model settings + builder.model_settings = { + 'release_settings': self.ctx.release_settings, + 'locations': self.ctx.locations, + 'command': orm.Dict(dict=new_dict), + 'input_phy': self.ctx.input_phy, + } + + builder.outgrid = self.ctx.outgrid + builder.outgrid_nest = self.ctx.outgrid_nest + builder.species = self.ctx.species + builder.land_use = self.ctx.land_use + builder.meteo_path = self.inputs.meteo_path + + # Walltime, memory, and resources. + builder.metadata.description = 'Test workflow to submit a flexpart calculation' + builder.metadata.options = self.inputs.flexpartcosmo.metadata.options + + # Ask the workflow to continue when the results are ready and store them in the context + running = self.submit(builder) + self.to_context(calculations=engine.append_(running)) + if self.ctx.offline_integration_time == 0: + self.ctx.index += 1 + + def run_ifs_simulation(self): + """Run calculations for equation of state.""" + # Set up calculation. + self.report( + f'running flexpart ifs for {self.ctx.simulation_dates[self.ctx.index]}' + ) + builder = FlexpartIfsCalculation.get_builder() + builder.code = self.inputs.fifs_code + + #changes in the command file + new_dict = self.ctx.command.get_dict() + new_dict['simulation_date'] = self.ctx.simulation_dates[self.ctx.index] + + if self.ctx.offline_integration_time > 0: + new_dict['age_class'] = self.ctx.offline_integration_time * 3600 + new_dict['dumped_particle_data'] = True + + self.ctx.parent_calc_folder = self.ctx.calculations[ + -1].outputs.remote_folder + builder.parent_calc_folder = self.ctx.parent_calc_folder + self.report(f'starting from: {self.ctx.parent_calc_folder}') + + builder.meteo_path = self.inputs.meteo_path_offline + new_dict.update(self.inputs.meteo_inputs_offline) + + else: new_dict['age_class'] = self.inputs.integration_time * 3600 + builder.meteo_path = self.inputs.meteo_path new_dict.update(self.inputs.meteo_inputs) - #model settings - builder.model_settings = { - 'release_settings': self.ctx.release_settings, - 'locations': self.ctx.locations, - 'command': orm.Dict(dict=new_dict), - 'input_phy': self.ctx.input_phy, - } - - builder.outgrid = self.ctx.outgrid - builder.outgrid_nest = self.ctx.outgrid_nest - builder.species = self.ctx.species - builder.land_use = self.ctx.land_use - builder.meteo_path = self.inputs.meteo_path + #model settings + builder.model_settings = { + 'release_settings': self.ctx.release_settings, + 'locations': self.ctx.locations, + 'command': orm.Dict(dict=new_dict), + } - # Walltime, memory, and resources. - builder.metadata.description = 'Test workflow to submit a flexpart calculation' - builder.metadata.options = self.inputs.flexpart.metadata.options - + builder.outgrid = self.ctx.outgrid + builder.outgrid_nest = self.ctx.outgrid_nest + builder.species = self.ctx.species + builder.land_use = self.inputs.land_use_ifs - # Ask the workflow to continue when the results are ready and store them in the context - running = self.submit(builder) - self.to_context(calculations=engine.append_(running)) + # Walltime, memory, and resources. + builder.metadata.description = 'Test workflow to submit a flexpart calculation' + builder.metadata.options = self.inputs.flexpartifs.metadata.options - self.ctx.index += 1 + # Ask the workflow to continue when the results are ready and store them in the context + running = self.submit(builder) + self.to_context(calculations=engine.append_(running)) + + self.ctx.index += 1 def results(self): """Process results.""" diff --git a/examples/example_workflow_combi.py b/examples/example_workflow_combi.py new file mode 100644 index 0000000..ea9333d --- /dev/null +++ b/examples/example_workflow_combi.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Run a multi dates workflow.""" + +import pathlib +import datetime +import click +import yaml +from aiida import orm, plugins, engine, cmdline, common +from aiida_flexpart.utils import reformat_locations + + +def read_yaml_data(data_filename: str, names=None) -> dict: + """Read in a YAML data file as a dictionary""" + data_path = pathlib.Path(data_filename) + with data_path.open('r', encoding='utf-8') as fp: + yaml_data = yaml.safe_load(fp) + + return {key: value + for key, value in yaml_data.items() + if key in names} if names else yaml_data + + +def make_locations_list(list_locations): + """makes location list""" + list_locations_ = read_yaml_data('inputs/location_groups.yaml', + names=list_locations) + list_ = [] + if list_locations_: + for _, j in list_locations_.items(): + list_ += j + return sorted(set(list_locations + list_)) + + +def simulation_dates_parser(date_list: list) -> list: + """ + Parse a range of dates and returns a list of date strings. + + Examples: + 2021-01-02--2021-01-10 -> [2021-01-02 00:00:00, 2021-01-02 00:00:00, ..., 2021-01-10 00:00:00] + 2021-01-02, 2021-01-10 -> [2021-01-02 00:00:00, 2021-01-10 00:00:00] + 2021-01-02 -> [2021-01-02 00:00:00,] + """ + dates = [] + for date_string in date_list: + if ',' in date_string: + dates += [ + date.strip() + ' 00:00:00' for date in date_string.split(',') + ] + elif '--' in date_string: + date_start, date_end = list( + map(lambda date: datetime.datetime.strptime(date, '%Y-%m-%d'), + date_string.split('--'))) + dates += [ + date.strftime('%Y-%m-%d 00:00:00') for date in [ + date_start + datetime.timedelta(days=x) + for x in range(0, (date_end - date_start).days + 1) + ] + ] + else: + dates += [date_string.strip() + ' 00:00:00'] + + return orm.List(list=dates) + + +def test_run(flexpart_code): + """Run workflow.""" + + simulation_dates = simulation_dates_parser(['2020-10-01']) + model = ['cosmo7'] + model_offline = [] + username = 'lfernand' + outgrid_main = 'Europe' + outgrid_nest = 'Switzerland' + integration_time = 24 + integration_time_offline = 0 + + users_address = f'/users/{username}/resources/flexpart/' + scratch_address = f'/scratch/snx3000/{username}/FLEXPART_input/' + + #list of locations and/or groups of locations + list_locations = ['KIT_200magl'] + + # Links to the remote files/folders. + glc = orm.RemoteData(remote_path=users_address + 'GLC2000', + computer=flexpart_code.computer) + glc_ifs = orm.RemoteData(remote_path=users_address + 'IGBP_int1.dat', + computer=flexpart_code.computer) + species = orm.RemoteData(remote_path=users_address + 'SPECIES', + computer=flexpart_code.computer) + surfdata = orm.RemoteData(remote_path=users_address + 'surfdata.t', + computer=flexpart_code.computer) + surfdepo = orm.RemoteData(remote_path=users_address + 'surfdepo.t', + computer=flexpart_code.computer) + + # parent_folder = orm.load_node(pk previous tsk) + # parent_folder = orm.RemoteData( + # remote_path = '/scratch/snx3000/lfernand/aiida/76/8d/cb2c-2fc6-46c4-b609-1d33fce0f60c', + # computer=flexpart_code.computer) + parent_folder = None + + #builder starts + workflow = plugins.WorkflowFactory('flexpart.multi_dates') + builder = workflow.get_builder() + builder.fcosmo_code = flexpart_code + builder.fifs_code = orm.load_code('flexpart_ifs@daint') + builder.check_meteo_ifs_code = orm.load_code( + 'check-ifs-data@daint-direct-106') + builder.check_meteo_cosmo_code = orm.load_code( + 'check-cosmo-data@daint-direct-106') + builder.post_processing_code = orm.load_code('post-processing@daint') + + #basic settings + builder.simulation_dates = simulation_dates + builder.integration_time = orm.Int(integration_time) + builder.offline_integration_time = orm.Int(integration_time_offline) + + #meteo realted settings + builder.model = orm.List(model) + builder.model_offline = orm.List(model_offline) + + meteo_path = orm.List([scratch_address + mod for mod in model]) + builder.meteo_path = meteo_path + builder.meteo_inputs = orm.Dict( + dict=read_yaml_data('inputs/meteo_inputs.yaml', names=[ + model[-1], + ])[model[-1]]) + + if model_offline: + meteo_path_offline = orm.List( + [scratch_address + mod for mod in model_offline]) + builder.meteo_path_offline = meteo_path_offline + builder.meteo_inputs_offline = orm.Dict(dict=read_yaml_data( + 'inputs/meteo_inputs.yaml', names=[ + model_offline[-1], + ])[model_offline[-1]]) + + builder.gribdir = orm.Str(scratch_address) + + #model settings + builder.command = orm.Dict(dict=read_yaml_data( + 'inputs/command.yaml')) #simulation date will be overwritten + builder.input_phy = orm.Dict(dict=read_yaml_data('inputs/input_phy.yaml')) + + dict_ = read_yaml_data('inputs/locations.yaml', + names=make_locations_list(list_locations)) + reformated_dict_locations = reformat_locations(dict_, model[-1]) + builder.locations = orm.Dict(dict=reformated_dict_locations) + + builder.release_settings = orm.Dict( + dict=read_yaml_data('inputs/release.yaml')) + + #other + builder.outgrid = orm.Dict( + dict=read_yaml_data('inputs/outgrid.yaml', names=[ + outgrid_main, + ])[outgrid_main]) + builder.outgrid_nest = orm.Dict( + dict=read_yaml_data('inputs/outgrid.yaml', names=[ + outgrid_nest, + ])[outgrid_nest]) + builder.species = species + builder.land_use = { + 'glc': glc, + 'surfdata': surfdata, + 'surfdepo': surfdepo, + } + builder.land_use_ifs = { + 'glc': glc_ifs, + 'surfdata': surfdata, + 'surfdepo': surfdepo, + } + builder.parent_calc_folder = parent_folder + + builder.flexpartcosmo.metadata.options.stash = { + 'source_list': + ['aiida.out', 'header*', 'partposit_inst', 'grid_time_*.nc'], + 'target_base': f'/store/empa/em05/{username}/aiida_stash', + 'stash_mode': common.StashMode.COPY.value, + } + builder.flexpartifs.metadata.options.stash = { + 'source_list': + ['aiida.out', 'header*', 'partposit_inst*', 'grid_time_*.nc'], + 'target_base': + f'/store/empa/em05/{username}/aiida_stash', + 'stash_mode': + common.StashMode.COPY.value, + } + builder.flexpartpost.metadata.options.stash = { + 'source_list': + ['aiida.out', 'boundary_sensitivity_*.nc', 'grid_time_*.nc'], + 'target_base': f'/store/empa/em05/{username}/aiida_stash', + 'stash_mode': common.StashMode.COPY.value, + } + + #change wall time for cosmo and ifs in seconds + builder.flexpartcosmo.metadata.options.max_wallclock_seconds = 1800 + #builder.flexpartifs.metadata.options.max_wallclock_seconds = 2700 + + engine.run(builder) + + +@click.command() +@cmdline.utils.decorators.with_dbenv() +@cmdline.params.options.CODE() +def cli(code): + """Run example. + + Example usage: $ ./example_01.py --code diff@localhost + + Alternative (creates diff@localhost-test code): $ ./example_01.py + + Help: $ ./example_01.py --help + """ + test_run(code) + + +if __name__ == '__main__': + cli() # pylint: disable=no-value-for-parameter diff --git a/pyproject.toml b/pyproject.toml index 3111917..9450b41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ disable = [ "invalid-name", "duplicate-code", "too-many-locals", + "too-many-statements", ] [tool.pytest.ini_options]