Package: parspace
This module is a simple tool to automatically explore a parameter space.You can install parspace withpip:$ python3 -m pip install -U --user parspaceUsage as a decoratorThis package provides a class that can be used to aumatically run a function
over all possible combinations of a given parameter space.Say that you have a problem controlled by two parameters: an aspect ratioaspand a densitydensity.  The following example shows how to useParSpaceto automatically explore every combination of the requested values
for these two parameters.  If you have a function (here calledlaunch_simu)
that performs the desired task for a given value of these two parameters, you
merely have to useParSpaceas a decorator on that function.fromparspaceimportParSpace@ParSpace(asp=[0.5,1,2],density=[1,10])deflaunch_simu(asp,density):print(f"aspect ratio{asp}and density{density}")launch_simu()This code will print the following on screen:aspect ratio 0.5 and density 1
aspect ratio 0.5 and density 10
aspect ratio 1 and density 1
aspect ratio 1 and density 10
aspect ratio 2 and density 1
aspect ratio 2 and density 10In real use cases,launch_simucould e.g. perform the desired simulation or
submit a job on a supercomputer.  The order of arguments oflaunch_simudoes not matter, only their names.  In other words, if its signature is insteaddef launch_simu(density, asp), you would obtain the same result. On the
other hand, defining it asdef launch_simu(aspect, density)would result in
aTypeErrorwhen calling the function as theaspargument fed toParSpacedoes not match any argument oflaunch_simu.If you have a large number of parameters, you might prefer defininglaunch_simuas taking a dictionary of keyword arguments.  Parameter names
and values are then the keys and values of that dictionary.fromparspaceimportParSpace@ParSpace(asp=[0.5,1,2],density=[1,10])deflaunch_simu(**pars):asp=pars['asp']density=pars['density']print(f"aspect ratio{asp}and density{density}")launch_simu()Iterating through returned valuesIf you care about the returned values of the wrapped function rather than on
its side effects, you can iterate over the decorated object as shown below.fromparspaceimportParSpace@ParSpace(asp=[0.5,1,2],density=[1,10])defcalc_mass(asp,density):returnasp*densityforpars,massincalc_mass:asp=pars['asp']density=pars['density']print(f"Mass for aspect{asp}at density{density}:{mass}")Note that iterating through a bareParSpaceinstance yields the parameters
dictionary.fromparspaceimportParSpacespace=ParSpace(asp=[0.5,1,2],density=[1,10])forparsinspace:print("aspect ratio{asp}and density{density}".format(**pars))Exploring the same space on several functionsProvided that the iterables used to build theParSpaceinstance can be
iterated through any number of times (mind that generators can only be iterated
throughonce), you can use that instance on several functions as follow.fromparspaceimportParSpacespace=ParSpace(asp=[0.5,1,2],density=[1,10])@spacedeflaunch_simu(asp,density):print(f"aspect ratio{asp}and density{density}")launch_simu()forpars,massinspace(lambdaasp,density:asp*density):asp=pars['asp']density=pars['density']print(f"Mass for aspect{asp}at density{density}:{mass}")Realistic example of a script submitting jobsThis script shows a simplistic but realistic example of whatParSpaceenables you to do.  This script is written for a particular system and is
therefore unlikely to work for you as-is but adapting it to your use case
should be a fairly simple task.  The functionsubmit_jobsdefines what
should be done for one specific job and its decorated version automatically
explore the desired parameter space.#!/usr/bin/env python3"""Submit jobs on a PBS enabled cluster.

This script is for demonstration purpose only and offers no guarantee, please
adapt it to your use case.
"""fromfunctoolsimportlru_cachefrompathlibimportPathimportjsonimportsubprocessimporttextwrapfromparspaceimportParSpaceQSUB='/usr/local/bin/qsub'BATCH=textwrap.dedent("""\#!/bin/bash
    #PBS -N jobname
    #PBS -l nodes=1:ppn=8
    #PBS -q queuename
    #PBS -j oe
    #PBS -V
    cd{work_dir}mpirun -np 8 /path/to/executable > out.log 2> err.log
    sync
    exit
    """)ROOT=Path().resolve(strict=True)# If you need to compute an entry parameter that depends only on a subset of# all the parameters you explore, you might want to cache its result if the# computation is expensive.  This isn't necessary in this simplistic case and# is only for illustrative purposes.@lru_cache(maxsize=None)defn_horiz(aspect_ratio):"""Compute grid size for a given aspect ratio."""return64*aspect_ratio@ParSpace(logra=range(4,7),aspect_ratio=[2,4,8])defsubmit_jobs(**pars):"""Create run directory, parameter file, and submit a job."""case_name='ra_1e{logra}__asp_{aspect_ratio}'.format(**pars)case_dir=ROOT/case_name# create run directory, in this case a subdirectory "output"# is also created.(case_dir/'output').mkdir(parents=True,exist_ok=True)# generate par file, this assumes a JSON parameter fileasp=pars['aspect_ratio']par_content=dict(rayleigh=10**pars['logra'],aspect_ratio=pars['aspect_ratio'],ny=n_horiz(asp))par_file=case_dir/'par.json'withpar_file.open('w')aspstream:json.dump(par_content,pstream)batch_content=BATCH.format(work_dir=case_dir)batch_file=case_dir/'batch'batch_file.write_text(batch_content)job_sub=subprocess.run((QSUB,str(batch_file)),capture_output=True,check=True,text=True)job_id=job_sub.stdout.splitlines()[-1].split('.')[0]print(f"Case{case_name}treated by{job_id}")if__name__=="__main__":submit_jobs()