From ba58e3619ab0ca4b991e3fda4ca002f8f761bea9 Mon Sep 17 00:00:00 2001
From: lkugler <lukas.kugler@gmail.com>
Date: Tue, 21 Feb 2023 17:53:21 +0100
Subject: [PATCH] new config import style

---
 analysis_only.py                       |  16 +-
 config/cfg.py                          |  27 +-
 config/clusters.py                     | 155 ----------
 config/jet.py                          |  79 +++++
 config/srvx1.py                        |  76 +++++
 config/vsc.py                          |  77 +++++
 dartwrf/assim_synth_obs.py             |  38 ++-
 dartwrf/create_obs_upfront.py          |   9 +-
 dartwrf/create_obsseq.py               |   2 +-
 dartwrf/create_wbubble_wrfinput.py     |   2 +-
 dartwrf/evaluate_posterior.py          |   2 +-
 dartwrf/evaluate_prior.py              |   2 +-
 dartwrf/link_dart_rttov.py             |   2 +-
 dartwrf/obs/calculate_obs_locations.py |   2 +-
 dartwrf/obsseq.py                      |   2 +-
 dartwrf/obsseq_2dim.py                 |   2 +-
 dartwrf/obsseq_to_netcdf.py            |   2 +-
 dartwrf/prep_IC_prior.py               |   2 +-
 dartwrf/prepare_namelist.py            |   2 +-
 dartwrf/prepare_wrfrundir.py           |   2 +-
 dartwrf/run_obs_diag.py                |   2 +-
 dartwrf/update_IC.py                   |   2 +-
 dartwrf/utils.py                       | 104 +++----
 dartwrf/workflows.py                   | 404 ++++++++++++++-----------
 dartwrf/wrfinput_add_geo.py            |   2 +-
 dartwrf/wrfout_add_geo.py              |   2 +-
 26 files changed, 562 insertions(+), 455 deletions(-)
 delete mode 100755 config/clusters.py
 create mode 100755 config/jet.py
 create mode 100755 config/srvx1.py
 create mode 100755 config/vsc.py

diff --git a/analysis_only.py b/analysis_only.py
index 976c9b0..c1ae8b9 100755
--- a/analysis_only.py
+++ b/analysis_only.py
@@ -6,27 +6,17 @@ import os, sys, shutil
 import datetime as dt
 
 from dartwrf import utils
-from config.cfg import exp
-from config.clusters import cluster
+from dartwrf.workflows import WorkFlows
 
 
-###############################
-
 prior_path_exp = '/mnt/jetfs/scratch/lkugler/data/sim_archive/exp_v1.19_P3_wbub7_noDA'
 prior_init_time = dt.datetime(2008,7,30,12)
 prior_valid_time = dt.datetime(2008,7,30,12,30)
 assim_time = prior_valid_time
 
+w = WorkFlows(exp_config='cfg.py', server_config='srvx1.py')
 
-cluster.setup()
-
-os.system(
-    cluster.python+' '+cluster.scripts_rundir+'/assim_synth_obs.py '
-                +assim_time.strftime('%Y-%m-%d_%H:%M ')
-                +prior_init_time.strftime('%Y-%m-%d_%H:%M ')
-                +prior_valid_time.strftime('%Y-%m-%d_%H:%M ')
-                +prior_path_exp
-    )
+w.assimilate(assim_time, prior_init_time, prior_valid_time, prior_path_exp)
 
 
 
diff --git a/config/cfg.py b/config/cfg.py
index f70d034..46ea5e7 100755
--- a/config/cfg.py
+++ b/config/cfg.py
@@ -1,13 +1,13 @@
 from dartwrf import utils
 
-exp = utils.ExperimentConfiguration()
-exp.expname = "exp_v1.22_P2_rr_VIS_obs10_loc20_inf5"
+exp = utils.Experiment()
+exp.expname = "test_newcode"
 exp.model_dx = 2000
-exp.n_ens = 40
+exp.n_ens = 10
 
 exp.filter_kind = 1
-exp.prior_inflation = 5
-exp.post_inflation = 0
+exp.prior_inflation = 0
+exp.post_inflation = 4
 exp.sec = True
 exp.reject_smallFGD = False
 exp.cov_loc_vert_km_horiz_km = (3, 20)
@@ -20,13 +20,10 @@ exp.use_existing_obsseq = False  # False or pathname (use precomputed obs_seq.ou
 #exp.use_existing_obsseq = '/gpfs/data/fs71386/lkugler/sim_archive/exp_v1.21_P3_wbub7_REFL2D_obs10_loc20_oe5/obs_seq_out/2008-07-30_%H:%M_obs_seq.out'
 #exp.use_existing_obsseq = '/gpfs/data/fs71386/lkugler/sim_archive/exp_v1.21_P2_rr_VIS_obs20_loc4/obs_seq_out/2008-07-30_%H:%M_obs_seq.out'
 
-#exp.nature_wrfout = '/home/fs71386/lkugler/data/sim_archive/exp_v1.19_P5+su_nat2/2008-07-30_07:00/1/wrfout_d01_%Y-%m-%d_%H:%M:%S'
-#exp.nature_wrfout = '/jetfs/home/lkugler/data/sim_archive/exp_v1.19_P3_wbub7_nat/2008-07-30_12:00/1/wrfout_d01_%Y-%m-%d_%H:%M:%S'
-#exp.nature_wrfout = '/home/fs71386/lkugler/data/sim_archive/exp_v1.19_Pwbub5_nat/2008-07-30_12:00/1/wrfout_d01_%Y-%m-%d_%H:%M:%S'
-exp.nature_wrfout = '/jetfs/home/lkugler/data/sim_archive/exp_v1.18_P1_nature/2008-07-30_06:00/1/wrfout_d01_%Y-%m-%d_%H:%M:%S'
-#exp.nature_wrfout = '/home/fs71386/lkugler/data/sim_archive/exp_v1.19_P4_nat/2008-07-30_07:00/1/wrfout_d01_%Y-%m-%d_%H:%M:%S'
+#exp.nature = '/mnt/jetfs/scratch/lkugler/data/sim_archive/exp_v1.19_P3_wbub7_nat/2008-07-30_12:00/1'
+exp.nature = '/mnt/jetfs/scratch/lkugler/data/sim_archive/exp_v1.18_P1_nature/2008-07-30_06:00/1'
 
-exp.input_profile = '/jetfs/home/lkugler/data/initial_profiles/wrf/ens/2022-03-31/raso.fc.<iens>.wrfprof'
+exp.input_profile = '/mnt/jetfs/home/lkugler/data/initial_profiles/wrf/ens/2022-03-31/raso.fc.<iens>.wrfprof'
 #exp.input_profile = '/gpfs/data/fs71386/lkugler/initial_profiles/wrf/ens/2022-03-31/raso.nat.<iens>.wrfprof'
 #exp.input_profile = '/gpfs/data/fs71386/lkugler/initial_profiles/wrf/ens/2022-05-18/raso.fc.<iens>.wrfprof'
 
@@ -71,11 +68,11 @@ radar = dict(plotname='Radar reflectivity', plotunits='[dBz]',
 
 t = dict(plotname='Temperature', plotunits='[K]',
          kind='RADIOSONDE_TEMPERATURE', 
-         n_obs=22500, obs_locations='square_array_evenly_on_grid',
-         # n_obs=1, obs_locations=[(45., 0.)],
+         #n_obs=22500, obs_locations='square_array_evenly_on_grid',
+         n_obs=1, obs_locations=[(45., 0.)],
          error_generate=0.2, error_assimilate=0.2,
          heights=[1000,], #range(1000, 17001, 2000),
-         cov_loc_radius_km=1.5)
+         cov_loc_radius_km=50)
 
 q = dict(plotname='Specific humidity', plotunits='[kg/kg]',
          kind='RADIOSONDE_SPECIFIC_HUMIDITY', n_obs=1,
@@ -93,7 +90,7 @@ psfc = dict(plotname='SYNOP Pressure', plotunits='[Pa]',
             error_generate=50., error_assimilate=100.,
             cov_loc_radius_km=32)
 
-exp.observations = [vis]
+exp.observations = [t]
 exp.update_vars = ['U', 'V', 'W', 'THM', 'PH', 'MU', 'QVAPOR', 'QCLOUD', 'QICE', 'PSFC']
 #exp.update_vars = ['U', 'V', 'W', 'T', 'PH', 'MU', 'QVAPOR', 'PSFC']
 
diff --git a/config/clusters.py b/config/clusters.py
deleted file mode 100755
index f87748a..0000000
--- a/config/clusters.py
+++ /dev/null
@@ -1,155 +0,0 @@
-import os, sys
-import datetime as dt
-from dartwrf import utils
-from config.cfg import exp
-
-"""Configuration name docs
-
-When coding, use attributes of a dictionary like this: 
-$ from cfg import exp, cluster
-$ path = cluster.archivedir
-
-
-attribute name    |     description
-------------------------------------------------------
-name                    any string (currently unused)
-
-python                  path of python version to use
-python_enstools         path of python version to use for verification script (not provided)
-ncks                    path to 'ncks' program; type 'which ncks' to find the path,
-                            if it doesn't exist, try to load the module first ('module load nco')
-ideal                   path to WRF's ideal.exe
-wrfexe                  path to WRF's wrf.exe
-
-wrf_rundir_base         path for temporary files for WRF
-dart_rundir_base        path for temporary files for DART
-archive_base            path for long-time output storage
-
-srcdir                  path to where WRF has been compiled, including the 'run' folder of WRF, e.g. /home/WRF-4.3/run
-dart_srcdir             path to DART compile directory, e.g. /home/DART-9.11.9/models/wrf/work
-rttov_srcdir            path to RTTOV compile directory, e.g. /home/RTTOV13/rtcoef_rttov13/
-scriptsdir              path where DART-WRF scripts reside, e.g. /home/DART-WRF/scripts
-
-namelist                path to a namelist template; strings like <hist_interval>, will be overwritten in scripts/prepare_namelist.py
-run_WRF                 path to script which runs WRF on a node of the cluster
-obs_impact_filename     path to obs_impact_filename (see DART guide; module assim_tools_mod and program obs_impact_tool)
-geo_em                  path to NetCDF file of WRF domain (see WRF guide)
-
-slurm_cfg               python dictionary, containing options of SLURM
-                            defined in SLURM docs (https://slurm.schedmd.com/sbatch.html)
-                            this configuration can be overwritten later on, for example:
-                            'dict(cluster.slurm_cfg, **cfg_update)' where
-                            'cfg_update = {"nodes": "2"}'
-"""
-
-
-vsc = utils.ClusterConfig(exp)
-vsc.name = 'vsc' 
-vsc.max_nproc = 20
-vsc.size_jobarray = 10  # 10 jobs with each 4 WRF processes per node
-vsc.use_slurm = True
-
-# binaries
-vsc.python = '/home/fs71386/lkugler/miniconda3/envs/DART/bin/python'
-vsc.python_enstools = '/home/fs71386/lkugler/miniconda3/envs/enstools/bin/python'
-vsc.ncks = '/home/fs71386/lkugler/miniconda3/envs/DART/bin/ncks'
-vsc.ideal = '/home/fs71386/lkugler/compile/bin/ideal-v4.2.2_v1.22.exe'
-vsc.wrfexe = '/home/fs71386/lkugler/compile/bin/wrf-v4.3_v1.22.exe'
-vsc.container = '/home/fs71386/lkugler/run_container.sh python.gcc9.5.0.vsc4.sif'
-
-# paths for data output
-vsc.wrf_rundir_base = '/gpfs/data/fs71386/lkugler/run_WRF/'  # path for temporary files
-vsc.dart_rundir_base = '/gpfs/data/fs71386/lkugler/run_DART/'  # path for temporary files
-vsc.archive_base = '/gpfs/data/fs71386/lkugler/sim_archive/'
-
-# paths used as input
-vsc.srcdir = '/gpfs/data/fs71386/lkugler/compile/WRF/WRF-4.3/run'
-vsc.dart_srcdir = '/gpfs/data/fs71386/lkugler/compile/DART/DART/models/wrf/work'
-vsc.rttov_srcdir = '/gpfs/data/fs71386/lkugler/compile/RTTOV13/rtcoef_rttov13/'
-vsc.scriptsdir = '/home/fs71386/lkugler/DART-WRF/dartwrf/'
-
-# templates/run scripts
-vsc.namelist = vsc.scriptsdir+'/../templates/namelist.input'
-vsc.run_WRF = '/home/fs71386/lkugler/DART-WRF/dartwrf/run_ens.vsc.sh'
-
-vsc.slurm_cfg = {"account": "p71386", "partition": "skylake_0384", "qos": "p71386_0384",
-                 "nodes": "1", "ntasks": "1", "ntasks-per-node": "48", "ntasks-per-core": "1",
-                 "mail-type": "FAIL", "mail-user": "lukas.kugler@univie.ac.at"}
-
-jet = utils.ClusterConfig(exp)
-jet.name = 'jet'
-jet.max_nproc = 12
-jet.use_slurm = True
-jet.size_jobarray = 40
-
-# binaries
-jet.python = '/jetfs/home/lkugler/miniconda3/envs/DART/bin/python'
-jet.python_verif = '/jetfs/home/lkugler/miniconda3/envs/enstools/bin/python'
-jet.ncks = '/jetfs/spack/opt/spack/linux-rhel8-skylake_avx512/intel-20.0.2/nco-4.9.3-dhlqiyog7howjmaleyfhm6lkt7ra37xf/bin/ncks'
-jet.ideal = '/jetfs/home/lkugler/bin/ideal-v4.3_v1.22.exe'
-jet.wrfexe = '/jetfs/home/lkugler/bin/wrf-v4.3_v1.22.exe'
-jet.container = ''
-
-# paths for data output
-jet.wrf_rundir_base = '/jetfs/home/lkugler/data/run_WRF/'  # path for temporary files
-jet.dart_rundir_base = '/jetfs/home/lkugler/data/run_DART/'  # path for temporary files
-jet.archive_base = '/jetfs/home/lkugler/data/sim_archive/'
-
-# paths used as input
-jet.srcdir = '/jetfs/home/lkugler/data/compile/WRF-4.3/run'
-jet.dart_srcdir = '/jetfs/home/lkugler/data/compile/DART/DART-10.5.3/models/wrf/work'
-jet.rttov_srcdir = '/jetfs/home/lkugler/data/compile/RTTOV13/rtcoef_rttov13/'
-jet.scriptsdir = '/jetfs/home/lkugler/DART-WRF/dartwrf/'
-
-# other inputs
-jet.geo_em = '/jetfs/home/lkugler/data/geo_em.d01.nc'
-jet.obs_impact_filename = jet.scriptsdir+'/../templates/impactfactor_T.txt'
-jet.namelist = jet.scriptsdir+'/../templates/namelist.input'
-jet.run_WRF = '/jetfs/home/lkugler/DART-WRF/dartwrf/run_ens.jet.sh'
-
-jet.slurm_cfg = {"account": "lkugler", "partition": "compute", #"nodelist": "jet07",
-                 "ntasks": "1", "ntasks-per-core": "1", "mem": "50G",
-                 "mail-type": "FAIL", "mail-user": "lukas.kugler@univie.ac.at"}
-
-
-srvx1 = utils.ClusterConfig(exp)
-srvx1.name = 'srvx1'
-srvx1.max_nproc = 6
-srvx1.use_slurm = False
-
-# binaries
-srvx1.python = '/users/staff/lkugler/miniconda3/bin/python'
-srvx1.python_verif = '/users/staff/lkugler/miniconda3/bin/python'
-srvx1.ncks = '/home/swd/spack/opt/spack/linux-rhel8-skylake_avx512/gcc-8.5.0/nco-5.0.1-ntu44aoxlvwtr2tsrobfr4lht7cpvccf/bin/ncks'
-srvx1.ideal = '' #/jetfs/home/lkugler/bin/ideal-v4.3_v1.22.exe'
-srvx1.wrfexe = '' #/jetfs/home/lkugler/bin/wrf-v4.3_v1.22.exe'
-srvx1.container = ''
-
-# paths for data output
-srvx1.wrf_rundir_base = '/mnt/jetfs/home/lkugler/data/run_WRF/'  # path for temporary files
-srvx1.dart_rundir_base = '/users/staff/lkugler/AdvDA23/run_DART/'  # path for temporary files
-srvx1.archive_base = '/mnt/jetfs/scratch/lkugler/data/sim_archive/'
-
-# paths used as input
-srvx1.srcdir = '/users/staff/lkugler/AdvDA23/DART/WRF-4.3/run'
-srvx1.dart_srcdir = '/users/staff/lkugler/AdvDA23/DART/models/wrf/work'
-srvx1.rttov_srcdir = '/users/staff/lkugler/AdvDA23/RTTOV13/rtcoef_rttov13/'
-srvx1.scriptsdir = '/users/staff/lkugler/AdvDA23/DART-WRF/dartwrf/'
-srvx1.geo_em = '/mnt/jetfs/scratch/lkugler/data/geo_em.d01.nc'
-
-# templates/run scripts
-srvx1.namelist = srvx1.scriptsdir+'/../templates/namelist.input'
-srvx1.run_WRF = srvx1.scriptsdir+'/run_ens.jet.sh'
-
-srvx1.slurm_cfg = {"account": "lkugler", "partition": "compute",
-                 "ntasks": "1", "ntasks-per-core": "1", "mem": "50G",
-                 "mail-type": "FAIL", "mail-user": "lukas.kugler@univie.ac.at"}
-
-#################################
-# select cluster configuration
-if 'jet' in os.uname().nodename:
-    cluster = jet
-elif 'srvx1' in os.uname().nodename:
-    cluster = srvx1
-else:
-    cluster = vsc
diff --git a/config/jet.py b/config/jet.py
new file mode 100755
index 0000000..96cffd3
--- /dev/null
+++ b/config/jet.py
@@ -0,0 +1,79 @@
+import os, sys
+import datetime as dt
+from dartwrf import utils
+from config.cfg import exp
+
+"""Configuration name docs
+
+When coding, use attributes of a dictionary like this: 
+$ from cfg import exp, cluster
+$ path = cluster.archivedir
+
+
+attribute name    |     description
+------------------------------------------------------
+name                    any string (currently unused)
+
+python                  path of python version to use
+python_enstools         path of python version to use for verification script (not provided)
+ncks                    path to 'ncks' program; type 'which ncks' to find the path,
+                            if it doesn't exist, try to load the module first ('module load nco')
+ideal                   path to WRF's ideal.exe
+wrfexe                  path to WRF's wrf.exe
+
+wrf_rundir_base         path for temporary files for WRF
+dart_rundir_base        path for temporary files for DART
+archive_base            path for long-time output storage
+
+srcdir                  path to where WRF has been compiled, including the 'run' folder of WRF, e.g. /home/WRF-4.3/run
+dart_srcdir             path to DART compile directory, e.g. /home/DART-9.11.9/models/wrf/work
+rttov_srcdir            path to RTTOV compile directory, e.g. /home/RTTOV13/rtcoef_rttov13/
+scriptsdir              path where DART-WRF scripts reside, e.g. /home/DART-WRF/scripts
+
+namelist                path to a namelist template; strings like <hist_interval>, will be overwritten in scripts/prepare_namelist.py
+run_WRF                 path to script which runs WRF on a node of the cluster
+obs_impact_filename     path to obs_impact_filename (see DART guide; module assim_tools_mod and program obs_impact_tool)
+geo_em                  path to NetCDF file of WRF domain (see WRF guide)
+
+slurm_cfg               python dictionary, containing options of SLURM
+                            defined in SLURM docs (https://slurm.schedmd.com/sbatch.html)
+                            this configuration can be overwritten later on, for example:
+                            'dict(cluster.slurm_cfg, **cfg_update)' where
+                            'cfg_update = {"nodes": "2"}'
+"""
+
+
+cluster = utils.ClusterConfig(exp)
+cluster.name = 'jet'
+cluster.max_nproc = 12
+cluster.use_slurm = True
+cluster.size_jobarray = 40
+
+# binaries
+cluster.python = '/jetfs/home/lkugler/miniconda3/envs/DART/bin/python'
+cluster.python_verif = '/jetfs/home/lkugler/miniconda3/envs/enstools/bin/python'
+cluster.ncks = '/jetfs/spack/opt/spack/linux-rhel8-skylake_avx512/intel-20.0.2/nco-4.9.3-dhlqiyog7howjmaleyfhm6lkt7ra37xf/bin/ncks'
+cluster.ideal = '/jetfs/home/lkugler/bin/ideal-v4.3_v1.22.exe'
+cluster.wrfexe = '/jetfs/home/lkugler/bin/wrf-v4.3_v1.22.exe'
+cluster.container = ''
+
+# paths for data output
+cluster.wrf_rundir_base = '/jetfs/home/lkugler/data/run_WRF/'  # path for temporary files
+cluster.dart_rundir_base = '/jetfs/home/lkugler/data/run_DART/'  # path for temporary files
+cluster.archive_base = '/jetfs/home/lkugler/data/sim_archive/'
+
+# paths used as input
+cluster.srcdir = '/jetfs/home/lkugler/data/compile/WRF-4.3/run'
+cluster.dart_srcdir = '/jetfs/home/lkugler/data/compile/DART/DART-10.5.3/models/wrf/work'
+cluster.rttov_srcdir = '/jetfs/home/lkugler/data/compile/RTTOV13/rtcoef_rttov13/'
+cluster.scriptsdir = '/jetfs/home/lkugler/DART-WRF/dartwrf/'
+
+# other inputs
+cluster.geo_em = '/jetfs/home/lkugler/data/geo_em.d01.nc'
+cluster.obs_impact_filename = cluster.scriptsdir+'/../templates/impactfactor_T.txt'
+cluster.namelist = cluster.scriptsdir+'/../templates/namelist.input'
+cluster.run_WRF = '/jetfs/home/lkugler/DART-WRF/dartwrf/run_ens.jet.sh'
+
+cluster.slurm_cfg = {"account": "lkugler", "partition": "compute", #"nodelist": "jet07",
+                 "ntasks": "1", "ntasks-per-core": "1", "mem": "50G",
+                 "mail-type": "FAIL", "mail-user": "lukas.kugler@univie.ac.at"}
diff --git a/config/srvx1.py b/config/srvx1.py
new file mode 100755
index 0000000..9a60872
--- /dev/null
+++ b/config/srvx1.py
@@ -0,0 +1,76 @@
+import os, sys
+import datetime as dt
+from dartwrf import utils
+from config.cfg import exp
+
+"""Configuration name docs
+
+When coding, use attributes of a dictionary like this: 
+$ from cfg import exp, cluster
+$ path = cluster.archivedir
+
+
+attribute name    |     description
+------------------------------------------------------
+name                    any string (currently unused)
+
+python                  path of python version to use
+python_enstools         path of python version to use for verification script (not provided)
+ncks                    path to 'ncks' program; type 'which ncks' to find the path,
+                            if it doesn't exist, try to load the module first ('module load nco')
+ideal                   path to WRF's ideal.exe
+wrfexe                  path to WRF's wrf.exe
+
+wrf_rundir_base         path for temporary files for WRF
+dart_rundir_base        path for temporary files for DART
+archive_base            path for long-time output storage
+
+srcdir                  path to where WRF has been compiled, including the 'run' folder of WRF, e.g. /home/WRF-4.3/run
+dart_srcdir             path to DART compile directory, e.g. /home/DART-9.11.9/models/wrf/work
+rttov_srcdir            path to RTTOV compile directory, e.g. /home/RTTOV13/rtcoef_rttov13/
+scriptsdir              path where DART-WRF scripts reside, e.g. /home/DART-WRF/scripts
+
+namelist                path to a namelist template; strings like <hist_interval>, will be overwritten in scripts/prepare_namelist.py
+run_WRF                 path to script which runs WRF on a node of the cluster
+obs_impact_filename     path to obs_impact_filename (see DART guide; module assim_tools_mod and program obs_impact_tool)
+geo_em                  path to NetCDF file of WRF domain (see WRF guide)
+
+slurm_cfg               python dictionary, containing options of SLURM
+                            defined in SLURM docs (https://slurm.schedmd.com/sbatch.html)
+                            this configuration can be overwritten later on, for example:
+                            'dict(cluster.slurm_cfg, **cfg_update)' where
+                            'cfg_update = {"nodes": "2"}'
+"""
+
+cluster = utils.ClusterConfig(exp)
+cluster.name = 'srvx1'
+cluster.max_nproc = 6
+cluster.use_slurm = False
+
+# binaries
+cluster.python = '/users/staff/lkugler/miniconda3/bin/python'
+cluster.python_verif = '/users/staff/lkugler/miniconda3/bin/python'
+cluster.ncks = '/home/swd/spack/opt/spack/linux-rhel8-skylake_avx512/gcc-8.5.0/nco-5.0.1-ntu44aoxlvwtr2tsrobfr4lht7cpvccf/bin/ncks'
+cluster.ideal = '' #/jetfs/home/lkugler/bin/ideal-v4.3_v1.22.exe'
+cluster.wrfexe = '' #/jetfs/home/lkugler/bin/wrf-v4.3_v1.22.exe'
+cluster.container = ''
+
+# paths for data output
+cluster.wrf_rundir_base = '/users/staff/lkugler/AdvDA23/run_WRF/'  # path for temporary files
+cluster.dart_rundir_base = '/users/staff/lkugler/AdvDA23/run_DART/'  # path for temporary files
+cluster.archive_base = '/mnt/jetfs/scratch/lkugler/data/sim_archive/'
+
+# paths used as input
+cluster.srcdir = '/users/staff/lkugler/AdvDA23/DART/WRF-4.3/run'
+cluster.dart_srcdir = '/users/staff/lkugler/AdvDA23/DART/models/wrf/work'
+cluster.rttov_srcdir = '/users/staff/lkugler/AdvDA23/RTTOV13/rtcoef_rttov13/'
+cluster.scriptsdir = '/users/staff/lkugler/AdvDA23/DART-WRF/dartwrf/'
+cluster.geo_em = '/mnt/jetfs/scratch/lkugler/data/geo_em.d01.nc'
+
+# templates/run scripts
+cluster.namelist = cluster.scriptsdir+'/../templates/namelist.input'
+cluster.run_WRF = cluster.scriptsdir+'/run_ens.jet.sh'
+
+cluster.slurm_cfg = {"account": "lkugler", "partition": "compute",
+                 "ntasks": "1", "ntasks-per-core": "1", "mem": "50G",
+                 "mail-type": "FAIL", "mail-user": "lukas.kugler@univie.ac.at"}
diff --git a/config/vsc.py b/config/vsc.py
new file mode 100755
index 0000000..7f58398
--- /dev/null
+++ b/config/vsc.py
@@ -0,0 +1,77 @@
+import os, sys
+import datetime as dt
+from dartwrf import utils
+from config.cfg import exp
+
+"""Configuration name docs
+
+When coding, use attributes of a dictionary like this: 
+$ from cfg import exp, cluster
+$ path = cluster.archivedir
+
+
+attribute name    |     description
+------------------------------------------------------
+name                    any string (currently unused)
+
+python                  path of python version to use
+python_enstools         path of python version to use for verification script (not provided)
+ncks                    path to 'ncks' program; type 'which ncks' to find the path,
+                            if it doesn't exist, try to load the module first ('module load nco')
+ideal                   path to WRF's ideal.exe
+wrfexe                  path to WRF's wrf.exe
+
+wrf_rundir_base         path for temporary files for WRF
+dart_rundir_base        path for temporary files for DART
+archive_base            path for long-time output storage
+
+srcdir                  path to where WRF has been compiled, including the 'run' folder of WRF, e.g. /home/WRF-4.3/run
+dart_srcdir             path to DART compile directory, e.g. /home/DART-9.11.9/models/wrf/work
+rttov_srcdir            path to RTTOV compile directory, e.g. /home/RTTOV13/rtcoef_rttov13/
+scriptsdir              path where DART-WRF scripts reside, e.g. /home/DART-WRF/scripts
+
+namelist                path to a namelist template; strings like <hist_interval>, will be overwritten in scripts/prepare_namelist.py
+run_WRF                 path to script which runs WRF on a node of the cluster
+obs_impact_filename     path to obs_impact_filename (see DART guide; module assim_tools_mod and program obs_impact_tool)
+geo_em                  path to NetCDF file of WRF domain (see WRF guide)
+
+slurm_cfg               python dictionary, containing options of SLURM
+                            defined in SLURM docs (https://slurm.schedmd.com/sbatch.html)
+                            this configuration can be overwritten later on, for example:
+                            'dict(cluster.slurm_cfg, **cfg_update)' where
+                            'cfg_update = {"nodes": "2"}'
+"""
+
+
+cluster = utils.ClusterConfig(exp)
+cluster.name = 'VSC' 
+cluster.max_nproc = 20
+cluster.size_jobarray = 10  # 10 jobs with each 4 WRF processes per node
+cluster.use_slurm = True
+
+# binaries
+cluster.python = '/home/fs71386/lkugler/miniconda3/envs/DART/bin/python'
+cluster.python_enstools = '/home/fs71386/lkugler/miniconda3/envs/enstools/bin/python'
+cluster.ncks = '/home/fs71386/lkugler/miniconda3/envs/DART/bin/ncks'
+cluster.ideal = '/home/fs71386/lkugler/compile/bin/ideal-v4.2.2_v1.22.exe'
+cluster.wrfexe = '/home/fs71386/lkugler/compile/bin/wrf-v4.3_v1.22.exe'
+cluster.container = '/home/fs71386/lkugler/run_container.sh python.gcc9.5.0.vsc4.sif'
+
+# paths for data output
+cluster.wrf_rundir_base = '/gpfs/data/fs71386/lkugler/run_WRF/'  # path for temporary files
+cluster.dart_rundir_base = '/gpfs/data/fs71386/lkugler/run_DART/'  # path for temporary files
+cluster.archive_base = '/gpfs/data/fs71386/lkugler/sim_archive/'
+
+# paths used as input
+cluster.srcdir = '/gpfs/data/fs71386/lkugler/compile/WRF/WRF-4.3/run'
+cluster.dart_srcdir = '/gpfs/data/fs71386/lkugler/compile/DART/DART/models/wrf/work'
+cluster.rttov_srcdir = '/gpfs/data/fs71386/lkugler/compile/RTTOV13/rtcoef_rttov13/'
+cluster.scriptsdir = '/home/fs71386/lkugler/DART-WRF/dartwrf/'
+
+# templates/run scripts
+cluster.namelist = cluster.scriptsdir+'/../templates/namelist.input'
+cluster.run_WRF = '/home/fs71386/lkugler/DART-WRF/dartwrf/run_ens.vsc.sh'
+
+cluster.slurm_cfg = {"account": "p71386", "partition": "skylake_0384", "qos": "p71386_0384",
+                 "nodes": "1", "ntasks": "1", "ntasks-per-node": "48", "ntasks-per-core": "1",
+                 "mail-type": "FAIL", "mail-user": "lukas.kugler@univie.ac.at"}
diff --git a/dartwrf/assim_synth_obs.py b/dartwrf/assim_synth_obs.py
index 53306f8..2b57fad 100755
--- a/dartwrf/assim_synth_obs.py
+++ b/dartwrf/assim_synth_obs.py
@@ -3,15 +3,16 @@ import time as time_module
 import datetime as dt
 import numpy as np
 
-from config.cfg import exp
-from config.clusters import cluster
 from dartwrf.utils import symlink, copy, sed_inplace, append_file, mkdir, try_remove, print, shell
 from dartwrf.obs import error_models as err
 import dartwrf.create_obsseq as osq
 from dartwrf import wrfout_add_geo
 from dartwrf import obsseq
 
+from config.cfg import exp
+from config.cluster import cluster
 earth_radius_km = 6370
+wrfout_format = 'wrfout_d01_%Y-%m-%d_%H:%M:%S'
 
 
 def set_DART_nml(just_prior_values=False):
@@ -71,14 +72,21 @@ def set_DART_nml(just_prior_values=False):
 
 
 def link_nature_to_dart_truth(time):
+    """Set a symlink from the WRFout file to be used as nature to the run_DART folder
+    
+    Args:
+        time (dt.datetime): Time at which observations will be made
+    """
+
     # get wrfout_d01 from nature run
-    shutil.copy(time.strftime(exp.nature_wrfout), cluster.dartrundir + "/wrfout_d01")
+    shutil.copy(time.strftime(exp.nature+'/'+wrfout_format), 
+                cluster.dartrundir + "/wrfout_d01")
+
     # DART may need a wrfinput file as well, which serves as a template for dimension sizes
-    symlink(cluster.dartrundir + "/wrfout_d01", cluster.dartrundir + "/wrfinput_d01")
-    print(
-        "linked", time.strftime(exp.nature_wrfout),
-        "to", cluster.dartrundir + "/wrfout_d01",
-    )
+    symlink(cluster.dartrundir + "/wrfout_d01", 
+            cluster.dartrundir + "/wrfinput_d01")
+    print("linked", time.strftime(exp.nature+'/'+wrfout_format),
+          "to", cluster.dartrundir + "/wrfout_d01")
 
 
 def prepare_nature_dart(time):
@@ -103,7 +111,7 @@ def prepare_prior_ensemble(assim_time, prior_init_time, prior_valid_time, prior_
             prior_path_exp
             + prior_init_time.strftime("/%Y-%m-%d_%H:%M/")
             + str(iens)
-            + prior_valid_time.strftime("/wrfout_d01_%Y-%m-%d_%H:%M:%S")
+            + prior_valid_time.strftime("/"+wrfout_format)
         )
         dart_ensdir = cluster.dartrundir + "/prior_ens" + str(iens)
         wrfout_dart = dart_ensdir + "/wrfout_d01"
@@ -484,27 +492,25 @@ def main(time, prior_init_time, prior_valid_time, prior_path_exp):
     print("prepare prior ensemble")
     prepare_prior_ensemble(time, prior_init_time, prior_valid_time, prior_path_exp)
     
-    ################################################
     print(" 1) get observations with specified obs-error")
     oso = get_obsseq_out(time)
 
-    ################################################
-    print('3.1) evaluate prior')  # evaluate prior observations for all locations
+    print(" 2.1) evaluate prior for all observations (incl rejected)")
     osf_prior = evaluate(time, output_format="%Y-%m-%d_%H:%M_obs_seq.final-eval_prior_allobs")
 
-    print(" 3.2) assign observation-errors for assimilation ")
+    print(" 2.2) assign observation-errors for assimilation ")
     set_obserr_assimilate_in_obsseqout(oso, osf_prior, outfile=cluster.dartrundir + "/obs_seq.out")
 
     if getattr(exp, "reject_smallFGD", False):
-        print(" 3.3) reject observations? ")
+        print(" 2.3) reject observations? ")
         qc_obs(time, oso, osf_prior)
 
-    print(" 3.4) assimilate (run filter) ")
+    print(" 3) run filter ")
     set_DART_nml()
     filter(nproc=nproc)
     archive_filteroutput(time)
 
-    # evaluate posterior observations for all locations
+    print(" 4) evaluate posterior observations for all observations (incl rejected)")
     write_list_of_inputfiles_posterior(time)
     if getattr(exp, "reject_smallFGD", False):
         copy(cluster.archivedir+'/obs_seq_out/'+time.strftime('%Y-%m-%d_%H:%M_obs_seq.out-beforeQC'), 
diff --git a/dartwrf/create_obs_upfront.py b/dartwrf/create_obs_upfront.py
index 3e107cf..3ccd5a2 100755
--- a/dartwrf/create_obs_upfront.py
+++ b/dartwrf/create_obs_upfront.py
@@ -5,7 +5,7 @@ import datetime as dt
 import numpy as np
 
 from config.cfg import exp
-from config.clusters import cluster
+from config.cluster import cluster
 from dartwrf.utils import copy, print
 import dartwrf.create_obsseq as osq
 from dartwrf import obsseq
@@ -24,9 +24,12 @@ if __name__ == "__main__":
     for tstr in args:
         times.append(dt.datetime.strptime(tstr, tformat))
 
-    dir_for_obsseqout = exp.nature_wrfout + '/../../../obs_seq_out/'+exp.use_existing_obsseq
-    os.makedirs(dir_for_obsseqout, exist_ok=True)  # create directory to run DART in
+    # strange path?
+    # dir_for_obsseqout = exp.nature_wrfout + '/../../../obs_seq_out/'+exp.use_existing_obsseq  
+    raise NotImplementedError('where to save obsseq to?')  
+    dir_for_obsseqout = ''  # TODO: solve this when necessary
     print('will save obsseq to', dir_for_obsseqout)
+    os.makedirs(dir_for_obsseqout, exist_ok=True) 
 
     os.chdir(cluster.dartrundir)
 
diff --git a/dartwrf/create_obsseq.py b/dartwrf/create_obsseq.py
index 6caaf7e..1789a9e 100755
--- a/dartwrf/create_obsseq.py
+++ b/dartwrf/create_obsseq.py
@@ -8,7 +8,7 @@ import datetime as dt
 from pysolar.solar import get_altitude, get_azimuth
 
 from config.cfg import exp
-from config.clusters import cluster
+from config.cluster import cluster
 from dartwrf.obs import calculate_obs_locations as col
 
 def obskind_read():
diff --git a/dartwrf/create_wbubble_wrfinput.py b/dartwrf/create_wbubble_wrfinput.py
index 5aed424..f586102 100644
--- a/dartwrf/create_wbubble_wrfinput.py
+++ b/dartwrf/create_wbubble_wrfinput.py
@@ -4,7 +4,7 @@ import datetime as dt
 import numpy as np
 
 from config.cfg import exp
-from config.clusters import cluster
+from config.cluster import cluster
 import netCDF4 as nc
 
 dx_km = 2
diff --git a/dartwrf/evaluate_posterior.py b/dartwrf/evaluate_posterior.py
index dcc40b5..59c5c02 100755
--- a/dartwrf/evaluate_posterior.py
+++ b/dartwrf/evaluate_posterior.py
@@ -4,7 +4,7 @@ import datetime as dt
 import numpy as np
 
 from config.cfg import exp
-from config.clusters import cluster
+from config.cluster import cluster
 from dartwrf import assim_synth_obs as aso
 
 
diff --git a/dartwrf/evaluate_prior.py b/dartwrf/evaluate_prior.py
index f7d63c7..21751ac 100755
--- a/dartwrf/evaluate_prior.py
+++ b/dartwrf/evaluate_prior.py
@@ -4,7 +4,7 @@ import datetime as dt
 import numpy as np
 
 from config.cfg import exp
-from config.clusters import cluster
+from config.cluster import cluster
 from dartwrf.utils import symlink, copy, sed_inplace, append_file, mkdir, try_remove, print, shell
 from dartwrf import assim_synth_obs as aso
 
diff --git a/dartwrf/link_dart_rttov.py b/dartwrf/link_dart_rttov.py
index 3eddcef..6c73355 100644
--- a/dartwrf/link_dart_rttov.py
+++ b/dartwrf/link_dart_rttov.py
@@ -1,6 +1,6 @@
 import os
 from config.cfg import exp
-from config.clusters import cluster
+from config.cluster import cluster
 from utils import symlink, copy_scp_srvx8, copy, sed_inplace
 
 joinp = os.path.join
diff --git a/dartwrf/obs/calculate_obs_locations.py b/dartwrf/obs/calculate_obs_locations.py
index 1245cb5..43e09c9 100755
--- a/dartwrf/obs/calculate_obs_locations.py
+++ b/dartwrf/obs/calculate_obs_locations.py
@@ -8,7 +8,7 @@ import datetime as dt
 import xarray as xr
 
 from config.cfg import exp
-from config.clusters import cluster
+from config.cluster import cluster
 
 #####################
 # Global variables
diff --git a/dartwrf/obsseq.py b/dartwrf/obsseq.py
index 0e36216..a5b0264 100755
--- a/dartwrf/obsseq.py
+++ b/dartwrf/obsseq.py
@@ -8,7 +8,7 @@ import numpy as np
 import pandas as pd
 
 from config.cfg import exp
-from config.clusters import cluster
+from config.cluster import cluster
 from dartwrf.utils import symlink, copy, sed_inplace, append_file, mkdir, try_remove
 
 
diff --git a/dartwrf/obsseq_2dim.py b/dartwrf/obsseq_2dim.py
index f4c675a..966d709 100755
--- a/dartwrf/obsseq_2dim.py
+++ b/dartwrf/obsseq_2dim.py
@@ -9,7 +9,7 @@ import datetime as dt
 import numpy as np
 
 from config.cfg import exp
-from config.clusters import cluster
+from config.cluster import cluster
 from dartwrf import assim_synth_obs as aso
 from dartwrf import obsseq
 
diff --git a/dartwrf/obsseq_to_netcdf.py b/dartwrf/obsseq_to_netcdf.py
index fe3cc4a..4076595 100644
--- a/dartwrf/obsseq_to_netcdf.py
+++ b/dartwrf/obsseq_to_netcdf.py
@@ -1,7 +1,7 @@
 import os, sys, glob, warnings
 
 from config.cfg import exp
-from config.clusters import cluster
+from config.cluster import cluster
 import run_obs_diag as rod
 
 def listdir_dirs(path):
diff --git a/dartwrf/prep_IC_prior.py b/dartwrf/prep_IC_prior.py
index 15e57e9..c832ffd 100755
--- a/dartwrf/prep_IC_prior.py
+++ b/dartwrf/prep_IC_prior.py
@@ -3,7 +3,7 @@ import datetime as dt
 import numpy as np
 
 from config.cfg import exp
-from config.clusters import cluster
+from config.cluster import cluster
 from utils import copy, clean_wrfdir, try_remove
 
 """
diff --git a/dartwrf/prepare_namelist.py b/dartwrf/prepare_namelist.py
index ac2ff1a..8b5a05e 100755
--- a/dartwrf/prepare_namelist.py
+++ b/dartwrf/prepare_namelist.py
@@ -13,7 +13,7 @@ import datetime as dt
 from docopt import docopt
 
 from config.cfg import exp
-from config.clusters import cluster
+from config.cluster import cluster
 from utils import sed_inplace, copy, symlink, mkdir
 
 def run(iens, begin, end, hist_interval=5, radt=5, archive=True,
diff --git a/dartwrf/prepare_wrfrundir.py b/dartwrf/prepare_wrfrundir.py
index 3cfe2f9..87c2245 100755
--- a/dartwrf/prepare_wrfrundir.py
+++ b/dartwrf/prepare_wrfrundir.py
@@ -2,7 +2,7 @@ import os, sys, shutil
 import datetime as dt
 
 from config.cfg import exp
-from config.clusters import cluster
+from config.cluster import cluster
 from utils import symlink, copy, link_contents
 import prepare_namelist
 
diff --git a/dartwrf/run_obs_diag.py b/dartwrf/run_obs_diag.py
index d292292..9598a8e 100644
--- a/dartwrf/run_obs_diag.py
+++ b/dartwrf/run_obs_diag.py
@@ -1,7 +1,7 @@
 import os, sys, shutil, glob
 
 from config.cfg import exp
-from config.clusters import cluster
+from config.cluster import cluster
 from utils import symlink, copy, sed_inplace, append_file, shell
 
 rundir_program = '/home/fs71386/lkugler/data/run_DART/'
diff --git a/dartwrf/update_IC.py b/dartwrf/update_IC.py
index 3247030..60cec76 100755
--- a/dartwrf/update_IC.py
+++ b/dartwrf/update_IC.py
@@ -3,7 +3,7 @@ import datetime as dt
 import netCDF4 as nc
 
 from config.cfg import exp
-from config.clusters import cluster
+from config.cluster import cluster
 
 def update_initials_in_WRF_rundir(time):
     """Updates wrfrst-files in `/run_WRF/` directory 
diff --git a/dartwrf/utils.py b/dartwrf/utils.py
index 4da09e3..b45b79f 100755
--- a/dartwrf/utils.py
+++ b/dartwrf/utils.py
@@ -2,35 +2,18 @@ import os, sys, shutil, glob, warnings
 import builtins as __builtin__
 import subprocess
 import datetime as dt
+import re, tempfile
+import importlib
 
-class ExperimentConfiguration(object):
-    """Collection of variables to use in code later on"""
+class Experiment(object):
+    """Collection of variables regarding the experiment configuration"""
     def __init__(self):
         pass
 
-class Shellslurm():
-    """Like Slurmpy class, but runs locally"""
-    def __init__(self, *args, **kwargs):
-        pass
-    def run(self, *args, **kwargs):
-        print(args[0])
-        os.system(args[0])
-
 class ClusterConfig(object):
-    """Collection of variables to use in code later on"""
+    """Collection of variables regarding the cluster configuration"""
     def __init__(self, exp):
         self.exp = exp
-        self.set_up = False
-
-    def setup(self):
-        # Set paths and backup scripts
-        self.log_dir = self.archivedir+'/logs/'
-        self.slurm_scripts_dir = self.archivedir+'/slurm-scripts/'
-        print('logging to', self.log_dir)
-        print('scripts, which are submitted to SLURM:', self.slurm_scripts_dir)
-
-        self.backup_scripts()
-        self.set_up = True
 
     @property
     def archivedir(self):
@@ -43,49 +26,51 @@ class ClusterConfig(object):
 
     @property
     def scripts_rundir(self):
+        """Path to the directory where the DART-WRF scripts are executed
+        
+        Example:
+            `/user/data/sim_archive/DART-WRF/`
+        """
         return self.archivedir+'/DART-WRF/'
 
     @property
     def dartrundir(self):
+        """Path to the directory where DART programs will run
+        Includes the experiment name
+        """
         return self.dart_rundir_base+'/'+self.exp.expname+'/'
 
     def wrf_rundir(self, iens):
+        """Path to the directory where an ensemble member will run WRF
+        Includes the experiment name and the ensemble member index
+        """
         return self.wrf_rundir_base+'/'+self.exp.expname+'/'+str(iens)
 
-    def create_job(self, *args, cfg_update=dict(), **kwargs):
-        """Shortcut to slurmpy's class; keep certain default kwargs
-        and only update some with kwarg `cfg_update`
-        see https://github.com/brentp/slurmpy
+    def run_job(self, cmd, jobname='', cfg_update=dict(), depends_on=None):
+        """Run scripts in a shell
 
-        depending on cluster config : run either locally or via SLURM 
-        """
-        if not self.set_up:
-            self.setup()
+        If not using SLURM: calls scripts through shell
+        if using SLURM: uses slurmpy to submit jobs, keep certain default kwargs and only update some with kwarg `overwrite_these_configurations`
 
+        Args:
+            cmd (str): Bash command(s) to run
+            jobname (str, optional): Name of SLURM job
+            cfg_update (dict): The config keywords will be overwritten with values
+            depends_on (int or None): SLURM job id of dependency, job will start after this id finished.
+
+        Returns 
+            None
+        """
         if self.use_slurm:
             from slurmpy import Slurm
-            return Slurm(*args, slurm_kwargs=dict(self.slurm_cfg, **cfg_update), 
-                        log_dir=self.log_dir, 
-                        scripts_dir=self.slurm_scripts_dir, 
-                        **kwargs)
+            Slurm(jobname, slurm_kwargs=dict(self.slurm_cfg, **cfg_update), 
+                  log_dir=self.log_dir, 
+                  scripts_dir=self.slurm_scripts_dir, 
+                  **kwargs
+                  ).run(cmd, depends_on=depends_on)
         else:
-            return Shellslurm(*args)
-
-    def backup_scripts(self):
-        """Copies scripts and configuration to cluster.archivedir folder"""
-        os.makedirs(self.archivedir, exist_ok=True)
-    
-        try:
-            shutil.copytree(self.scriptsdir, self.scripts_rundir)
-            print('scripts have been copied to', self.archivedir)
-        except FileExistsError:
-            pass
-        except:
-            raise
-        # try:
-        #     copy(os.path.basename(__file__), self.scripts_rundir+'/')
-        # except Exception as e:
-        #     warnings.warn(str(e))
+            print(cmd)
+            os.system(cmd)
 
 def shell(args):
     print(args)
@@ -145,10 +130,19 @@ def copy_scp_srvx8(src, dst):
     os.system('scp '+src+' a1254888@srvx8.img.univie.ac.at:'+dst)
 
 def sed_inplace(filename, pattern, repl):
-    import re, tempfile
-    '''
-    Perform the pure-Python equivalent of in-place `sed` substitution: e.g.,
-    `sed -i -e 's/'${pattern}'/'${repl}' "${filename}"`.
+    '''Perform the pure-Python equivalent of in-place `sed` substitution
+    Like `sed -i -e 's/'${pattern}'/'${repl}' "${filename}"`.
+
+    Args:
+        filename (str):  path to file
+        pattern (str): string that will be replaced
+        repl (str): what shall be written instead of pattern?
+
+    Example:
+        sed_inplace('namelist.input', '<dx>', str(int(exp.model_dx)))
+
+    Returns:
+        None
     '''
     # For efficiency, precompile the passed regular expression.
     pattern_compiled = re.compile(pattern)
diff --git a/dartwrf/workflows.py b/dartwrf/workflows.py
index bc60a01..acc8a8e 100644
--- a/dartwrf/workflows.py
+++ b/dartwrf/workflows.py
@@ -1,192 +1,232 @@
 #!/usr/bin/python3
 """
-Run a cycled OSSE with WRF and DART.
+These functions mostly call python scripts via the shell,
+e.g. assimilate() calls dartwrf/assim_synth_obs.py through the shell.
+
+This would not be necessary, but some users might want to use queueing systems (e.g. SLURM) which must call scripts.
 """
 import os, sys, shutil, glob, warnings
 import datetime as dt
+import importlib
 
 from dartwrf.utils import script_to_str
 from config.cfg import exp
-from config.clusters import cluster
 
-def prepare_WRFrundir(init_time):
-    """Create WRF/run directories and wrfinput files
-    """
-    cmd = cluster.python+' '+cluster.scripts_rundir+'/prepare_wrfrundir.py '+init_time.strftime('%Y-%m-%d_%H:%M')
-    print(cmd)
-    os.system(cmd)
-
-def run_ideal(depends_on=None):
-    """Run ideal for every ensemble member"""
-    cmd = """# run ideal.exe in parallel, then add geodata
-export SLURM_STEP_GRES=none
-for ((n=1; n<="""+str(exp.n_ens)+"""; n++))
-do
-    rundir="""+cluster.wrf_rundir_base+'/'+exp.expname+"""/$n
-    echo $rundir
-    cd $rundir
-    mpirun -np 1 ./ideal.exe &
-done
-wait
-for ((n=1; n<="""+str(exp.n_ens)+"""; n++))
-do
-    rundir="""+cluster.wrf_rundir_base+'/'+exp.expname+"""/$n
-    mv $rundir/rsl.out.0000 $rundir/rsl.out.input
-done
-"""
-    s = cluster.create_job("ideal", cfg_update={"ntasks": str(exp.n_ens),
-                                      "time": "10", "mem": "100G"})
-    id = s.run(cmd, depends_on=[depends_on])
-    return id
-
-def wrfinput_insert_wbubble(perturb=True, depends_on=None):
-    """Given that directories with wrfinput files exist,
-    update these wrfinput files with warm bubbles
+class WorkFlows(object):
+    def __init__(self, exp_config='cfg.py', server_config='server.py'):
+        """Set up the experiment folder in `archivedir`.
+
+        Args:
+            exp (str): Path to exp config file
+            config (str): Path to the cluster config file
+        """
+        # At first, load config from present folder (later from scripts_rundir)
+        # exp = __import__('config/'+exp_config)
+        self.cluster = importlib.import_module('config.'+server_config.strip('.py')).cluster
+
+        # Set paths and backup scripts
+        self.cluster.log_dir = self.cluster.archivedir+'/logs/'
+        print('logging to', self.cluster.log_dir)
+
+        if self.cluster.use_slurm:
+            self.cluster.slurm_scripts_dir = self.cluster.archivedir+'/slurm-scripts/'
+            print('scripts, which are submitted to SLURM:', self.cluster.slurm_scripts_dir)
+
+        # Copy scripts to self.cluster.archivedir folder
+        os.makedirs(self.cluster.archivedir, exist_ok=True)
+        try:
+            shutil.copytree(self.cluster.scriptsdir, self.cluster.scripts_rundir)
+            print('scripts have been copied to', self.cluster.archivedir)
+        except FileExistsError as e:
+            warnings.warn(str(e))
+        except:
+            raise
+
+        # Copy config files
+        shutil.copy('config/'+exp_config, self.cluster.scripts_rundir+'/cfg.py')
+        shutil.copy('config/'+server_config, self.cluster.scripts_rundir+'/cluster.py')  # whatever server, the config name is always the same!
+        shutil.copy('config/'+server_config, 'config/cluster.py')  # whatever server, the config name is always the same!
+
+
+    def prepare_WRFrundir(self, init_time):
+        """Create WRF/run directories and wrfinput files
+        """
+        cmd = self.cluster.python+' '+self.cluster.scripts_rundir+'/prepare_wrfrundir.py '+init_time.strftime('%Y-%m-%d_%H:%M')
+        print(cmd)
+        os.system(cmd)
+
+    def run_ideal(self, depends_on=None):
+        """Run ideal for every ensemble member"""
+        cmd = """# run ideal.exe in parallel, then add geodata
+    export SLURM_STEP_GRES=none
+    for ((n=1; n<="""+str(exp.n_ens)+"""; n++))
+    do
+        rundir="""+self.cluster.wrf_rundir_base+'/'+exp.expname+"""/$n
+        echo $rundir
+        cd $rundir
+        mpirun -np 1 ./ideal.exe &
+    done
+    wait
+    for ((n=1; n<="""+str(exp.n_ens)+"""; n++))
+    do
+        rundir="""+self.cluster.wrf_rundir_base+'/'+exp.expname+"""/$n
+        mv $rundir/rsl.out.0000 $rundir/rsl.out.input
+    done
     """
-    s = cluster.create_job("ins_wbubble", cfg_update={"time": "5"})
-    pstr = ' '
-    if perturb:
-        pstr = ' perturb'
-    id = s.run(cluster.python+' '+cluster.scripts_rundir+'/create_wbubble_wrfinput.py'+pstr,
-               depends_on=[depends_on])
-    return id
-
-def run_ENS(begin, end, depends_on=None, first_minute=True, 
-            input_is_restart=True, restart_path=False, output_restart_interval=720):
-    """Run forecast for 1 minute, save output. 
-    Then run whole timespan with 5 minutes interval.
-
-    if input_is_restart:  # start WRF in restart mode
-    """
-    id = depends_on
-    restart_flag = '.false.' if not input_is_restart else '.true.'
-
-    # if False:  # doesnt work with restarts at the moment# first_minute:
-    #     # first minute forecast (needed for validating an assimilation)
-    #     hist_interval = 1
-    #     radt = 1  # calc CFRAC also in first minute
-    #     begin_plus1 = begin+dt.timedelta(minutes=1)
-    #     s = cluster.create_job("preWRF1", cfg_update=dict(time="2"))
-    #     args = [cluster.python, cluster.scripts_rundir+'/prepare_namelist.py',
-    #             begin.strftime('%Y-%m-%d_%H:%M'),
-    #             begin_plus1.strftime('%Y-%m-%d_%H:%M'),
-    #             str(hist_interval),
-    #             '--radt='+str(radt),
-    #             '--restart='+restart_flag,]
-    #     id = s.run(' '.join(args), depends_on=[id])
-
-    #     s = cluster.create_job("runWRF1", cfg_update={"nodes": "1", "array": "1-"+str(exp.n_nodes),
-    #                 "time": "2", "mem-per-cpu": "2G"})
-    #     cmd = script_to_str(cluster.run_WRF).replace('<expname>', exp.expname)
-    #     id = s.run(cmd, depends_on=[id])
-
-    #     # apply forward operator (DART filter without assimilation)
-    #     s = cluster.create_job("fwOP-1m", cfg_update=dict(time="10", ntasks=48))
-    #     id = s.run(cluster.python+' '+cluster.scripts_rundir+'/apply_obs_op_dart.py '
-    #                + begin.strftime('%Y-%m-%d_%H:%M')+' '
-    #                + begin_plus1.strftime('%Y-%m-%d_%H:%M'),
-    #                depends_on=[id])
-
-    # whole forecast timespan
-    hist_interval = 5
-    radt = 5
-    args = [cluster.python,
-                cluster.scripts_rundir+'/prepare_namelist.py',
-                begin.strftime('%Y-%m-%d_%H:%M'),
-                end.strftime('%Y-%m-%d_%H:%M'),
-                str(hist_interval),
-                '--radt='+str(radt),
-                '--restart='+restart_flag,]
-    if output_restart_interval:
-        args.append('--restart_interval='+str(int(float(output_restart_interval))))
-
-    s = cluster.create_job("preWRF", cfg_update=dict(time="2"))
-    id = s.run(' '.join(args), depends_on=[id])
-
-    time_in_simulation_hours = (end-begin).total_seconds()/3600
-    runtime_wallclock_mins_expected = int(8+time_in_simulation_hours*9.5)  # usually below 9 min/hour
-    s = cluster.create_job("WRF", cfg_update={"array": "1-"+str(cluster.size_jobarray), "ntasks": "10", "nodes": "1",
-                "time": str(runtime_wallclock_mins_expected), "mem": "140G"})
-    cmd = script_to_str(cluster.run_WRF).replace('<exp.expname>', exp.expname
-                                       ).replace('<cluster.wrf_rundir_base>', cluster.wrf_rundir_base)
-    id = s.run(cmd, depends_on=[id])
-    return id
-
-
-def assimilate(assim_time, prior_init_time, prior_valid_time, prior_path_exp, 
-               depends_on=None):
-    """Creates observations from a nature run and assimilates them.
-
-    Args:
-        assim_time (dt.datetime):       timestamp of prior wrfout files
-        prior_init_time (dt.datetime):  timestamp to find the directory where the prior wrfout files are
-        prior_path_exp (str):           use this directory to get prior state (i.e. cluster.archivedir)
-    """
-    if not os.path.exists(prior_path_exp):
-        raise IOError('prior_path_exp does not exist: '+prior_path_exp)
-
-    id = cluster.create_job("Assim", cfg_update={"ntasks": "12", "time": "60",
-                             "mem": "200G", "ntasks-per-node": "12", "ntasks-per-core": "2"}
-            ).run(cluster.python+' '+cluster.scripts_rundir+'/assim_synth_obs.py '
-               +assim_time.strftime('%Y-%m-%d_%H:%M ')
-               +prior_init_time.strftime('%Y-%m-%d_%H:%M ')
-               +prior_valid_time.strftime('%Y-%m-%d_%H:%M ')
-               +prior_path_exp, depends_on=[depends_on])
-    return id
-
-
-def prepare_IC_from_prior(prior_path_exp, prior_init_time, prior_valid_time, new_start_time=None, depends_on=None):
-
-    if new_start_time != None:
-        tnew = new_start_time.strftime(' %Y-%m-%d_%H:%M')
-    else:
-        tnew = ''
-
-    id = cluster.create_job("IC-prior", cfg_update=dict(time="8")
-            ).run(cluster.python+' '+cluster.scripts_rundir+'/prep_IC_prior.py '
-                +prior_path_exp 
-                +prior_init_time.strftime(' %Y-%m-%d_%H:%M')
-                +prior_valid_time.strftime(' %Y-%m-%d_%H:%M')
-                +tnew, depends_on=[depends_on])
-    return id
-
-
-def update_IC_from_DA(assim_time, depends_on=None):
-    id = cluster.create_job("IC-update", cfg_update=dict(time="8")
-            ).run(cluster.python+' '+cluster.scripts_rundir+'/update_IC.py '
-                +assim_time.strftime('%Y-%m-%d_%H:%M'), depends_on=[depends_on])
-    return id
-
-
-def create_satimages(init_time, depends_on=None):
-    s = cluster.create_job("RTTOV", cfg_update={"ntasks": "12", "time": "80", "mem": "200G"})
-    id = s.run(cluster.python_verif+' ~/RTTOV-WRF/run_init.py '+cluster.archivedir
-               +init_time.strftime('/%Y-%m-%d_%H:%M/'),
-          depends_on=[depends_on])
-    return id
-
-
-def gen_obsseq(depends_on=None):
-    s = cluster.create_job("obsseq_netcdf", cfg_update={"time": "10", "mail-type": "FAIL,END"})
-    id = s.run(cluster.python+' '+cluster.scripts_rundir+'/obsseq_to_netcdf.py',
-               depends_on=[depends_on])
-    return id
-
-
-def verify_sat(depends_on=None):
-    s = cluster.create_job("verif-SAT-"+exp.expname, cfg_update={"time": "60", "mail-type": "FAIL,END", "ntasks": "20", 
-            "ntasks-per-node": "20", "ntasks-per-core": "1", "mem": "100G",})
-    cmd = cluster.python_verif+' /jetfs/home/lkugler/osse_analysis/plot_from_raw/analyze_fc.py '+exp.expname+' has_node sat verif1d FSS BS'
-    s.run(cmd, depends_on=[depends_on])
-
-def verify_wrf(depends_on=None):
-    s = cluster.create_job("verif-WRF-"+exp.expname, cfg_update={"time": "120", "mail-type": "FAIL,END", "ntasks": "20", 
-                 "ntasks-per-node": "20", "ntasks-per-core": "1", "mem": "250G"})
-    cmd = cluster.python_verif+' /jetfs/home/lkugler/osse_analysis/plot_from_raw/analyze_fc.py '+exp.expname+' has_node wrf verif1d verif3d FSS BS'
-    s.run(cmd, depends_on=[depends_on])
-
-def verify_fast(depends_on=None):
-    s = cluster.create_job("verif-fast-"+exp.expname, cfg_update={"time": "10", "mail-type": "FAIL", "ntasks": "1",
-        "ntasks-per-node": "1", "ntasks-per-core": "1"})
-    cmd = cluster.python_verif+' /jetfs/home/lkugler/osse_analysis/plot_fast/plot_single_exp.py '+exp.expname
-    s.run(cmd, depends_on=[depends_on])
+        id = self.cluster.run_job(cmd, "ideal", cfg_update={"ntasks": str(exp.n_ens),
+                            "time": "10", "mem": "100G"}, depends_on=[depends_on])
+        return id
+
+    def wrfinput_insert_wbubble(self, perturb=True, depends_on=None):
+        """Given that directories with wrfinput files exist,
+        update these wrfinput files with warm bubbles
+        """
+
+        pstr = ' '
+        if perturb:
+            pstr = ' perturb'
+        cmd = self.cluster.python+' '+self.cluster.scripts_rundir+'/create_wbubble_wrfinput.py'+pstr
+
+        id = self.cluster.run_job(cmd, "ins_wbubble", cfg_update={"time": "5"}, depends_on=[depends_on])
+        return id
+
+    def run_ENS(self, begin, end, depends_on=None, first_minute=True, 
+                input_is_restart=True, restart_path=False, output_restart_interval=720):
+        """Run forecast for 1 minute, save output. 
+        Then run whole timespan with 5 minutes interval.
+
+        if input_is_restart:  # start WRF in restart mode
+        """
+        id = depends_on
+        restart_flag = '.false.' if not input_is_restart else '.true.'
+
+        # if False:  # doesnt work with restarts at the moment# first_minute:
+        #     # first minute forecast (needed for validating an assimilation)
+        #     hist_interval = 1
+        #     radt = 1  # calc CFRAC also in first minute
+        #     begin_plus1 = begin+dt.timedelta(minutes=1)
+        #     s = self.cluster.run_job("preWRF1", cfg_update=dict(time="2"))
+        #     args = [self.cluster.python, self.cluster.scripts_rundir+'/prepare_namelist.py',
+        #             begin.strftime('%Y-%m-%d_%H:%M'),
+        #             begin_plus1.strftime('%Y-%m-%d_%H:%M'),
+        #             str(hist_interval),
+        #             '--radt='+str(radt),
+        #             '--restart='+restart_flag,]
+        #     id = s.run(' '.join(args), depends_on=[id])
+
+        #     s = self.cluster.run_job("runWRF1", cfg_update={"nodes": "1", "array": "1-"+str(exp.n_nodes),
+        #                 "time": "2", "mem-per-cpu": "2G"})
+        #     cmd = script_to_str(self.cluster.run_WRF).replace('<expname>', exp.expname)
+        #     id = s.run(cmd, depends_on=[id])
+
+        #     # apply forward operator (DART filter without assimilation)
+        #     s = self.cluster.run_job("fwOP-1m", cfg_update=dict(time="10", ntasks=48))
+        #     id = s.run(self.cluster.python+' '+self.cluster.scripts_rundir+'/apply_obs_op_dart.py '
+        #                + begin.strftime('%Y-%m-%d_%H:%M')+' '
+        #                + begin_plus1.strftime('%Y-%m-%d_%H:%M'),
+        #                depends_on=[id])
+
+        # whole forecast timespan
+        hist_interval = 5
+        radt = 5
+        args = [self.cluster.python,
+                    self.cluster.scripts_rundir+'/prepare_namelist.py',
+                    begin.strftime('%Y-%m-%d_%H:%M'),
+                    end.strftime('%Y-%m-%d_%H:%M'),
+                    str(hist_interval),
+                    '--radt='+str(radt),
+                    '--restart='+restart_flag,]
+        if output_restart_interval:
+            args.append('--restart_interval='+str(int(float(output_restart_interval))))
+
+        id = self.cluster.run_job(' '.join(args), "preWRF", cfg_update=dict(time="2"), depends_on=[id])
+
+        cmd = script_to_str(self.cluster.run_WRF).replace('<exp.expname>', exp.expname
+                                        ).replace('<cluster.wrf_rundir_base>', self.cluster.wrf_rundir_base)
+
+        time_in_simulation_hours = (end-begin).total_seconds()/3600
+        runtime_wallclock_mins_expected = int(8+time_in_simulation_hours*9.5)  # usually below 9 min/hour
+
+        id = self.cluster.run_job(cmd, "WRF", cfg_update={"array": "1-"+str(self.cluster.size_jobarray), "ntasks": "10", "nodes": "1",
+                            "time": str(runtime_wallclock_mins_expected), "mem": "140G"}, depends_on=[id])
+        return id
+
+
+    def assimilate(self, assim_time, prior_init_time, prior_valid_time, prior_path_exp, 
+                depends_on=None):
+        """Creates observations from a nature run and assimilates them.
+
+        Args:
+            assim_time (dt.datetime):       timestamp of prior wrfout files
+            prior_init_time (dt.datetime):  timestamp to find the directory where the prior wrfout files are
+            prior_path_exp (str):           use this directory to get prior state (i.e. self.cluster.archivedir)
+        """
+        if not os.path.exists(prior_path_exp):
+            raise IOError('prior_path_exp does not exist: '+prior_path_exp)
+
+        cmd = (self.cluster.python+' '+self.cluster.scripts_rundir+'/assim_synth_obs.py '
+                +assim_time.strftime('%Y-%m-%d_%H:%M ')
+                +prior_init_time.strftime('%Y-%m-%d_%H:%M ')
+                +prior_valid_time.strftime('%Y-%m-%d_%H:%M ')
+                +prior_path_exp)
+
+        id = self.cluster.run_job(cmd, "Assim", cfg_update={"ntasks": "12", "time": "60",
+                                "mem": "200G", "ntasks-per-node": "12", "ntasks-per-core": "2"}, depends_on=[depends_on])
+        return id
+
+
+    def prepare_IC_from_prior(self, prior_path_exp, prior_init_time, prior_valid_time, new_start_time=None, depends_on=None):
+
+        if new_start_time != None:
+            tnew = new_start_time.strftime(' %Y-%m-%d_%H:%M')
+        else:
+            tnew = ''
+
+        cmd = (self.cluster.python+' '+self.cluster.scripts_rundir+'/prep_IC_prior.py '
+                    +prior_path_exp 
+                    +prior_init_time.strftime(' %Y-%m-%d_%H:%M')
+                    +prior_valid_time.strftime(' %Y-%m-%d_%H:%M')
+                    +tnew)
+        id = self.cluster.run_job(cmd, "IC-prior", cfg_update=dict(time="8"), depends_on=[depends_on])
+        return id
+
+
+    def update_IC_from_DA(self, assim_time, depends_on=None):
+        cmd = self.cluster.python+' '+self.cluster.scripts_rundir+'/update_IC.py '+assim_time.strftime('%Y-%m-%d_%H:%M')
+        id = self.cluster.run_job(cmd, "IC-update", cfg_update=dict(time="8"), depends_on=[depends_on])
+        return id
+
+
+    def create_satimages(self, init_time, depends_on=None):
+        cmd = self.cluster.python_verif+' ~/RTTOV-WRF/run_init.py '+self.cluster.archivedir+init_time.strftime('/%Y-%m-%d_%H:%M/')
+        id = self.cluster.run_job(cmd, "RTTOV", cfg_update={"ntasks": "12", "time": "80", "mem": "200G"}, depends_on=[depends_on])
+        return id
+
+
+    def gen_obsseq(self, depends_on=None):
+        cmd = self.cluster.python+' '+self.cluster.scripts_rundir+'/obsseq_to_netcdf.py'
+        id = self.cluster.run_job("obsseq_netcdf", cfg_update={"time": "10", "mail-type": "FAIL,END"}, 
+                depends_on=[depends_on])
+        return id
+
+
+    def verify_sat(self, depends_on=None):
+        cmd = self.cluster.python_verif+' /jetfs/home/lkugler/osse_analysis/plot_from_raw/analyze_fc.py '+exp.expname+' has_node sat verif1d FSS BS'
+
+        self.cluster.run_job(cmd, "verif-SAT-"+exp.expname, 
+                        cfg_update={"time": "60", "mail-type": "FAIL,END", "ntasks": "20", 
+                                    "ntasks-per-node": "20", "ntasks-per-core": "1", "mem": "100G",}, depends_on=[depends_on])
+
+    def verify_wrf(self, depends_on=None):
+        cmd = self.cluster.python_verif+' /jetfs/home/lkugler/osse_analysis/plot_from_raw/analyze_fc.py '+exp.expname+' has_node wrf verif1d verif3d FSS BS'
+        
+        self.cluster.run_job(cmd, "verif-WRF-"+exp.expname, 
+                        cfg_update={"time": "120", "mail-type": "FAIL,END", "ntasks": "20", 
+                                    "ntasks-per-node": "20", "ntasks-per-core": "1", "mem": "250G"}, depends_on=[depends_on])
+
+    def verify_fast(self, depends_on=None):
+        cmd = self.cluster.python_verif+' /jetfs/home/lkugler/osse_analysis/plot_fast/plot_single_exp.py '+exp.expname
+
+        self.cluster.run_job(cmd, "verif-fast-"+exp.expname, 
+                        cfg_update={"time": "10", "mail-type": "FAIL", "ntasks": "1",
+                                    "ntasks-per-node": "1", "ntasks-per-core": "1"}, depends_on=[depends_on])
diff --git a/dartwrf/wrfinput_add_geo.py b/dartwrf/wrfinput_add_geo.py
index fe4e155..1a2c266 100755
--- a/dartwrf/wrfinput_add_geo.py
+++ b/dartwrf/wrfinput_add_geo.py
@@ -12,7 +12,7 @@ import os, sys
 import netCDF4 as nc
 
 from config.cfg import exp
-from config.clusters import cluster
+from config.cluster import cluster
 
 def run(geo_data_file, wrfinput_file):
     geo_ds = nc.Dataset(geo_data_file, 'r')
diff --git a/dartwrf/wrfout_add_geo.py b/dartwrf/wrfout_add_geo.py
index 99e1d85..a13737c 100755
--- a/dartwrf/wrfout_add_geo.py
+++ b/dartwrf/wrfout_add_geo.py
@@ -2,7 +2,7 @@ import os, sys
 import netCDF4 as nc
 
 from config.cfg import exp
-from config.clusters import cluster
+from config.cluster import cluster
 
 fields_old = ["XLAT_M",   "XLONG_M",      "CLAT",
                 "XLONG_U",  "XLONG_V",     "XLAT_U",    "XLAT_V"]
-- 
GitLab