diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 10d7e34ce..cff5a4c69 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -25,4 +25,5 @@ repos:
rev: v0.1.4 # Ruff version.
hooks:
- id: ruff # Run the linter for Python.
+ args: ["--ignore=E402"] # Ignore E402 error "Module level import not at top of file"
- id: ruff-format # Run the formatter for Python.
diff --git a/docs/source/conf.py b/docs/source/conf.py
index afbb5a7be..75b147deb 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: conf.py
Project: source
@@ -16,7 +16,7 @@
-----
File Description:
configuration for CLEO documentation made using Sphinx
-'''
+"""
# Configuration file for the Sphinx documentation builder.
@@ -32,17 +32,18 @@
import pathlib
import sys
+
# sys.path.insert(0, os.path.abspath('../..'))
sys.path.insert(0, pathlib.Path(__file__).parents[2].resolve().as_posix())
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
-project = 'CLEO'
-license = 'BSD 3-Clause'
-copyright = '(2023) MPI-M, Clara Bayley'
-author = 'Clara Bayley & Other Developers'
-release = '0.1.0'
+project = "CLEO"
+license = "BSD 3-Clause"
+copyright = "(2023) MPI-M, Clara Bayley"
+author = "Clara Bayley & Other Developers"
+release = "0.1.0"
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
@@ -51,21 +52,21 @@
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
- 'sphinx.ext.autodoc',
- 'sphinx.ext.duration',
- 'sphinx_copybutton',
- 'sphinx.ext.intersphinx',
- 'sphinxcontrib.bibtex',
- 'breathe',
+ "sphinx.ext.autodoc",
+ "sphinx.ext.duration",
+ "sphinx_copybutton",
+ "sphinx.ext.intersphinx",
+ "sphinxcontrib.bibtex",
+ "breathe",
"sphinx.ext.viewcode",
]
# configuration of citations using bibtex file(s)
-bibtex_bibfiles = ['./references.bib']
-bibtex_reference_style = 'label'
+bibtex_bibfiles = ["./references.bib"]
+bibtex_reference_style = "label"
# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
@@ -74,15 +75,15 @@
# Integrate doxygen with sphinx via breathe
breathe_projects = {
- "libs" : "../build/doxygen/xml/",
- "runcleo" : "../build/doxygen/xml/",
- "superdrops" : "../build/doxygen/xml/",
- "zarr" : "../build/doxygen/xml/",
- "observers" : "../build/doxygen/xml/",
- "initialise" : "../build/doxygen/xml/",
+ "libs": "../build/doxygen/xml/",
+ "runcleo": "../build/doxygen/xml/",
+ "superdrops": "../build/doxygen/xml/",
+ "zarr": "../build/doxygen/xml/",
+ "observers": "../build/doxygen/xml/",
+ "initialise": "../build/doxygen/xml/",
}
-breathe_default_project = 'proj'
+breathe_default_project = "proj"
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
@@ -90,14 +91,14 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
-html_theme = 'furo'
+html_theme = "furo"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+html_static_path = ["_static"]
# To include the date of the last visit in Sphinx documentation, use the last
# updated feature of Sphinx. This feature automatically adds the last modification
# date of the source file to the rendered HTML output.
-html_last_updated_fmt = '%d %B %Y'
+html_last_updated_fmt = "%d %B %Y"
diff --git a/docs/source/usage/good_coding.rst b/docs/source/usage/good_coding.rst
index 1c6528bc1..e72463892 100644
--- a/docs/source/usage/good_coding.rst
+++ b/docs/source/usage/good_coding.rst
@@ -61,7 +61,9 @@ by automated tools such as `Cocogitto-bot `_` for formatting and linting. Ruff checks
+are something like the combination of several Python linters (Flake8, isort, pydocstyle etc.) and
+the black formatter. We obey the default settings of ruff except we ignore E402 errors.
For C++ we obey the Google C++ Style Guide with:
| IndentWidth: 2
diff --git a/examples/adiabaticparcel/as2017.py b/examples/adiabaticparcel/as2017.py
index 711f072b2..30600125d 100644
--- a/examples/adiabaticparcel/as2017.py
+++ b/examples/adiabaticparcel/as2017.py
@@ -1,6 +1,7 @@
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
-'''
----- CLEO -----
File: as2017.py
Project: adiabaticparcel
@@ -8,22 +9,19 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Thursday 18th April 2024
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
https://opensource.org/licenses/BSD-3-Clause
-----
-Copyright (c) 2023 MPI-M, Clara Bayley
------
File Description:
-Script generate input files, runs CLEO adia0D executable to
-create data and then creates plots for adiabatic parcel example
-similar to Figure 5 of "On the CCN (de)activation nonlinearities"
-S. Arabas and S. Shima 2017 to show
+Script generate input files, runs CLEO adia0D executable to create data and
+then creates plots for adiabatic parcel example similar to Figure 5 of "On
+the CCN (de)activation nonlinearities" S. Arabas and S. Shima 2017 to show
example of adaibatic parcel expansion and contraction.
Note: SD(M) = superdroplet (model)
-'''
+"""
import os
import sys
@@ -36,13 +34,14 @@
configfile = sys.argv[3]
sys.path.append(path2CLEO) # for imports from pySD package
-sys.path.append(path2CLEO+"/examples/exampleplotting/") # for imports from example plotting package
+sys.path.append(
+ path2CLEO + "/examples/exampleplotting/"
+) # for imports from example plotting package
-from plotssrc import pltsds, as2017fig
+from plotssrc import as2017fig
from pySD import editconfigfile
-from pySD.sdmout_src import sdtracing
-from pySD.sdmout_src import *
-from pySD.initsuperdropsbinary_src import *
+from pySD.sdmout_src import pyzarr, pysetuptxt, pygbxsdat, sdtracing
+from pySD.initsuperdropsbinary_src import rgens, dryrgens, probdists, attrsgen
from pySD.initsuperdropsbinary_src import create_initsuperdrops as csupers
from pySD.initsuperdropsbinary_src import read_initsuperdrops as rsupers
from pySD.gbxboundariesbinary_src import read_gbxboundaries as rgrid
@@ -50,11 +49,11 @@
############### INPUTS ##################
# path and filenames for creating SD initial conditions and for running model
-constsfile = path2CLEO+"/libs/cleoconstants.hpp"
-binpath = path2build+"/bin/"
-sharepath = path2build+"/share/"
-initSDsfile = sharepath+"as2017_dimlessSDsinit.dat"
-gridfile = sharepath+"as2017_dimlessGBxboundaries.dat"
+constsfile = path2CLEO + "/libs/cleoconstants.hpp"
+binpath = path2build + "/bin/"
+sharepath = path2build + "/share/"
+initSDsfile = sharepath + "as2017_dimlessSDsinit.dat"
+gridfile = sharepath + "as2017_dimlessGBxboundaries.dat"
# booleans for [making, saving] initialisation figures
isfigures = [True, True]
@@ -103,42 +102,41 @@
paramslist = [params1, params2, params3]
lwdths = [2, 1, 0.5]
+
def displacement(time, w_avg, thalf):
- '''displacement z given velocity, w, is sinusoidal
+ """displacement z given velocity, w, is sinusoidal
profile: w = w_avg * pi/2 * np.sin(np.pi * t/thalf)
- where wmax = pi/2*w_avg and tauhalf = thalf/pi.'''
+ where wmax = pi/2*w_avg and tauhalf = thalf/pi."""
zmax = w_avg / 2 * thalf
z = zmax * (1 - np.cos(np.pi * time / thalf))
return z
+
############### RUN EXAMPLE ##################
### delete any existing datasets
-for run_num in range(len(monors)*len(paramslist)):
- dataset = binpath+"as2017_sol"+str(run_num)+".zarr"
- os.system("rm -rf "+dataset)
+for run_num in range(len(monors) * len(paramslist)):
+ dataset = binpath + "as2017_sol" + str(run_num) + ".zarr"
+ os.system("rm -rf " + dataset)
### ensure build, share and bin directories exist
if path2CLEO == path2build:
- raise ValueError("build directory cannot be CLEO")
+ raise ValueError("build directory cannot be CLEO")
else:
- Path(path2build).mkdir(exist_ok=True)
- Path(sharepath).mkdir(exist_ok=True)
- Path(binpath).mkdir(exist_ok=True)
+ Path(path2build).mkdir(exist_ok=True)
+ Path(sharepath).mkdir(exist_ok=True)
+ Path(binpath).mkdir(exist_ok=True)
### create file (and plot) for gridbox boundaries
-os.system("rm "+gridfile)
-cgrid.write_gridboxboundaries_binary(gridfile, zgrid, xgrid,
- ygrid, constsfile)
+os.system("rm " + gridfile)
+cgrid.write_gridboxboundaries_binary(gridfile, zgrid, xgrid, ygrid, constsfile)
rgrid.print_domain_info(constsfile, gridfile)
if isfigures[0]:
- rgrid.plot_gridboxboundaries(constsfile, gridfile,
- binpath, isfigures[1])
+ rgrid.plot_gridboxboundaries(constsfile, gridfile, binpath, isfigures[1])
plt.close()
runnum = 0
for i in range(len(monors)):
-
### create file (and plots) for initial SDs conditions
monor, numconc = monors[i], numconcs[i]
# all SDs have the same dryradius = monor [m]
@@ -147,51 +145,50 @@ def displacement(time, w_avg, thalf):
# monodisperse droplet radii probability distribution
xiprobdist = probdists.DiracDelta(monor)
- initattrsgen = attrsgen.AttrsGenerator(radiigen, dryradiigen, xiprobdist,
- coord3gen, coord1gen, coord2gen)
- os.system("rm "+initSDsfile)
- csupers.write_initsuperdrops_binary(initSDsfile, initattrsgen,
- configfile, constsfile,
- gridfile, nsupers, numconc)
+ initattrsgen = attrsgen.AttrsGenerator(
+ radiigen, dryradiigen, xiprobdist, coord3gen, coord1gen, coord2gen
+ )
+ os.system("rm " + initSDsfile)
+ csupers.write_initsuperdrops_binary(
+ initSDsfile, initattrsgen, configfile, constsfile, gridfile, nsupers, numconc
+ )
rsupers.print_initSDs_infos(initSDsfile, configfile, constsfile, gridfile)
if isfigures[0]:
- rsupers.plot_initGBxs_distribs(configfile, constsfile, initSDsfile,
- gridfile, binpath, isfigures[1], "all")
+ rsupers.plot_initGBxs_distribs(
+ configfile, constsfile, initSDsfile, gridfile, binpath, isfigures[1], "all"
+ )
plt.close()
fig, axs = plt.subplots(nrows=3, ncols=1, figsize=(5, 16))
for params, lwdth in zip(paramslist, lwdths):
-
### edit relevant setup file parameters
- params["zarrbasedir"] = binpath+"as2017_sol"+str(runnum)+".zarr"
- params["setup_filename"] = binpath+"as2017_setup.txt"
+ params["zarrbasedir"] = binpath + "as2017_sol" + str(runnum) + ".zarr"
+ params["setup_filename"] = binpath + "as2017_setup.txt"
editconfigfile.edit_config_params(configfile, params)
### delete any existing dataset
- os.system("rm -rf "+params["zarrbasedir"])
- os.system("rm "+params["setup_filename"])
+ os.system("rm -rf " + params["zarrbasedir"])
+ os.system("rm " + params["setup_filename"])
### run model
os.chdir(path2build)
- executable = path2build+"/examples/adiabaticparcel/src/adia0D"
- print('Executable: '+executable)
- print('Config file: '+configfile)
+ executable = path2build + "/examples/adiabaticparcel/src/adia0D"
+ print("Executable: " + executable)
+ print("Config file: " + configfile)
os.system(executable + " " + configfile)
### load results
- setupfile = binpath+"as2017_setup.txt"
- dataset = binpath+"as2017_sol"+str(runnum)+".zarr"
+ setupfile = binpath + "as2017_setup.txt"
+ dataset = binpath + "as2017_sol" + str(runnum) + ".zarr"
### read in constants and intial setup from setup .txt file
config = pysetuptxt.get_config(setupfile, nattrs=3, isprint=True)
consts = pysetuptxt.get_consts(setupfile, isprint=True)
- gbxs = pygbxsdat.get_gridboxes(gridfile, consts["COORD0"],
- isprint=True)
+ gbxs = pygbxsdat.get_gridboxes(gridfile, consts["COORD0"], isprint=True)
# read in output Xarray data
- thermo = pyzarr.get_thermodata(dataset, config["ntime"],
- gbxs["ndims"], consts)
+ thermo = pyzarr.get_thermodata(dataset, config["ntime"], gbxs["ndims"], consts)
supersat = thermo.supersaturation()
time = pyzarr.get_time(dataset).secs
sddata = pyzarr.get_supers(dataset, consts)
@@ -199,26 +196,33 @@ def displacement(time, w_avg, thalf):
attrs = ["radius", "xi", "msol"]
sd0 = sdtracing.attributes_for1superdroplet(sddata, 0, attrs)
- numconc = np.sum(sddata["xi"][0])/gbxs["domainvol"]/1e6 # [/cm^3]
+ numconc = np.sum(sddata["xi"][0]) / gbxs["domainvol"] / 1e6 # [/cm^3]
### plot results
- wlab = " = {:.1f}".format(config["W_avg"]*100)+"cm s$^{-1}$"
- axs = as2017fig.condensation_validation_subplots(axs, time, sd0["radius"],
- supersat[:, 0, 0, 0],
- zprof,
- lwdth=lwdth,
- lab=wlab)
+ wlab = " = {:.1f}".format(config["W_avg"] * 100) + "cm s$^{-1}$"
+ axs = as2017fig.condensation_validation_subplots(
+ axs, time, sd0["radius"], supersat[:, 0, 0, 0], zprof, lwdth=lwdth, lab=wlab
+ )
runnum += 1
### save figure
- as2017fig.plot_kohlercurve_with_criticalpoints(axs[1], sd0["radius"],
- sd0["msol"][0],
- thermo.temp[0, 0, 0, 0],
- sddata.IONIC, sddata.MR_SOL)
-
- textlab = "N = "+str(numconc)+"cm$^{-3}$\n" +\
- "r$_{dry}$ = "+"{:.2g}\u03BCm\n".format(sd0["radius"][0])
+ as2017fig.plot_kohlercurve_with_criticalpoints(
+ axs[1],
+ sd0["radius"],
+ sd0["msol"][0],
+ thermo.temp[0, 0, 0, 0],
+ sddata.IONIC,
+ sddata.MR_SOL,
+ )
+
+ textlab = (
+ "N = "
+ + str(numconc)
+ + "cm$^{-3}$\n"
+ + "r$_{dry}$ = "
+ + "{:.2g}\u03BCm\n".format(sd0["radius"][0])
+ )
axs[0].legend(loc="lower right", fontsize=10)
axs[1].legend(loc="upper left")
axs[0].text(0.03, 0.85, textlab, transform=axs[0].transAxes)
@@ -233,8 +237,9 @@ def displacement(time, w_avg, thalf):
fig.tight_layout()
- savename = "as2017fig_"+str(i)+".png"
- fig.savefig(binpath+savename, dpi=400,
- bbox_inches="tight", facecolor='w', format="png")
- print("Figure .png saved as: "+binpath+savename)
+ savename = "as2017fig_" + str(i) + ".png"
+ fig.savefig(
+ binpath + savename, dpi=400, bbox_inches="tight", facecolor="w", format="png"
+ )
+ print("Figure .png saved as: " + binpath + savename)
plt.show()
diff --git a/examples/adiabaticparcel/cuspbifurc.py b/examples/adiabaticparcel/cuspbifurc.py
index 7fb9783fa..c03a37bab 100644
--- a/examples/adiabaticparcel/cuspbifurc.py
+++ b/examples/adiabaticparcel/cuspbifurc.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: cuspbifurc.py
Project: adiabaticparcel
@@ -21,7 +21,7 @@
S. Arabas and S. Shima 2017 to show example of cusp birfucation for
0D adiabatic parcel expansion and contraction.
Note: SD(M) = superdroplet (model)
-'''
+"""
import os
import sys
@@ -34,30 +34,30 @@
configfile = sys.argv[3]
sys.path.append(path2CLEO) # for imports from pySD package
-sys.path.append(path2CLEO+"/examples/exampleplotting/") # for imports from example plotting package
+sys.path.append(
+ path2CLEO + "/examples/exampleplotting/"
+) # for imports from example plotting package
from plotssrc import pltsds, as2017fig
-from pySD.sdmout_src import sdtracing
-from pySD.sdmout_src import *
-from pySD.initsuperdropsbinary_src import *
+from pySD.sdmout_src import pyzarr, pysetuptxt, pygbxsdat, sdtracing
+from pySD.initsuperdropsbinary_src import rgens, dryrgens, probdists, attrsgen
from pySD.initsuperdropsbinary_src import create_initsuperdrops as csupers
from pySD.initsuperdropsbinary_src import read_initsuperdrops as rsupers
from pySD.gbxboundariesbinary_src import read_gbxboundaries as rgrid
from pySD.gbxboundariesbinary_src import create_gbxboundaries as cgrid
-
############### INPUTS ##################
# path and filenames for creating SD initial conditions and for running model
-constsfile = path2CLEO+"/libs/cleoconstants.hpp"
-binpath = path2build+"/bin/"
-sharepath = path2build+"/share/"
-initSDsfile = sharepath+"cuspbifurc_dimlessSDsinit.dat"
-gridfile = sharepath+"cuspbifurc_dimlessGBxboundaries.dat"
+constsfile = path2CLEO + "/libs/cleoconstants.hpp"
+binpath = path2build + "/bin/"
+sharepath = path2build + "/share/"
+initSDsfile = sharepath + "cuspbifurc_dimlessSDsinit.dat"
+gridfile = sharepath + "cuspbifurc_dimlessGBxboundaries.dat"
# path and file names for plotting results
-setupfile = binpath+"cuspbifurc_setup.txt"
-dataset = binpath+"cuspbifurc_sol.zarr"
+setupfile = binpath + "cuspbifurc_setup.txt"
+dataset = binpath + "cuspbifurc_sol.zarr"
# booleans for [making, saving] initialisation figures
isfigures = [True, True]
@@ -89,53 +89,54 @@
def displacement(time, w_avg, thalf):
- '''displacement z given velocity, w, is sinusoidal
+ """displacement z given velocity, w, is sinusoidal
profile: w = w_avg * pi/2 * np.sin(np.pi * t/thalf)
- where wmax = pi/2*w_avg and tauhalf = thalf/pi.'''
+ where wmax = pi/2*w_avg and tauhalf = thalf/pi."""
zmax = w_avg / 2 * thalf
z = zmax * (1 - np.cos(np.pi * time / thalf))
return z
+
############### RUN EXAMPLE ##################
### ensure build, share and bin directories exist
if path2CLEO == path2build:
- raise ValueError("build directory cannot be CLEO")
+ raise ValueError("build directory cannot be CLEO")
else:
- Path(path2build).mkdir(exist_ok=True)
- Path(sharepath).mkdir(exist_ok=True)
- Path(binpath).mkdir(exist_ok=True)
+ Path(path2build).mkdir(exist_ok=True)
+ Path(sharepath).mkdir(exist_ok=True)
+ Path(binpath).mkdir(exist_ok=True)
### delete any exisitng initial conditions
-os.system("rm "+gridfile)
-os.system("rm "+initSDsfile)
+os.system("rm " + gridfile)
+os.system("rm " + initSDsfile)
### create files (and plots) for gridbox boundaries and initial SD conditions
-cgrid.write_gridboxboundaries_binary(gridfile, zgrid, xgrid,
- ygrid, constsfile)
+cgrid.write_gridboxboundaries_binary(gridfile, zgrid, xgrid, ygrid, constsfile)
rgrid.print_domain_info(constsfile, gridfile)
-initattrsgen = attrsgen.AttrsGenerator(radiigen, dryradiigen, xiprobdist,
- coord3gen, coord1gen, coord2gen)
-csupers.write_initsuperdrops_binary(initSDsfile, initattrsgen,
- configfile, constsfile,
- gridfile, nsupers, numconc)
+initattrsgen = attrsgen.AttrsGenerator(
+ radiigen, dryradiigen, xiprobdist, coord3gen, coord1gen, coord2gen
+)
+csupers.write_initsuperdrops_binary(
+ initSDsfile, initattrsgen, configfile, constsfile, gridfile, nsupers, numconc
+)
rsupers.print_initSDs_infos(initSDsfile, configfile, constsfile, gridfile)
if isfigures[0]:
- rgrid.plot_gridboxboundaries(constsfile, gridfile,
- binpath, isfigures[1])
- rsupers.plot_initGBxs_distribs(configfile, constsfile, initSDsfile,
- gridfile, binpath, isfigures[1], "all")
+ rgrid.plot_gridboxboundaries(constsfile, gridfile, binpath, isfigures[1])
+ rsupers.plot_initGBxs_distribs(
+ configfile, constsfile, initSDsfile, gridfile, binpath, isfigures[1], "all"
+ )
plt.close()
### run model
os.chdir(path2build)
-os.system('pwd')
-os.system('rm -rf '+dataset) # delete any existing dataset
-executable = path2build+"/examples/adiabaticparcel/src/adia0D"
-print('Executable: '+executable)
-print('Config file: '+configfile)
+os.system("pwd")
+os.system("rm -rf " + dataset) # delete any existing dataset
+executable = path2build + "/examples/adiabaticparcel/src/adia0D"
+print("Executable: " + executable)
+print("Config file: " + configfile)
os.system(executable + " " + configfile)
### load results
@@ -154,20 +155,27 @@ def displacement(time, w_avg, thalf):
### plot results
# sample drops to plot from whole range of SD ids
sample = [0, int(config["maxnsupers"])]
-radii = sdtracing.attribute_for_superdroplets_sample(sddata, "radius",
- minid=sample[0],
- maxid=sample[1])
+radii = sdtracing.attribute_for_superdroplets_sample(
+ sddata, "radius", minid=sample[0], maxid=sample[1]
+)
savename = binpath + "/cuspbifurc_SDgrowth.png"
pltsds.individ_radiusgrowths_figure(time, radii, savename=savename)
attrs = ["radius", "xi", "msol"]
sd0 = sdtracing.attributes_for1superdroplet(sddata, 0, attrs)
-numconc = np.sum(sddata["xi"][0])/gbxs["domainvol"]/1e6 # [/cm^3]
-
-savename2 = binpath+"/cuspbifurc_validation.png"
-as2017fig.arabas_shima_2017_fig(time, zprof, sd0["radius"], sd0["msol"],
- thermo.temp[:, 0, 0, 0],
- supersat[:, 0, 0, 0],
- sddata.IONIC, sddata.MR_SOL,
- config["W_avg"], numconc,
- savename2)
+numconc = np.sum(sddata["xi"][0]) / gbxs["domainvol"] / 1e6 # [/cm^3]
+
+savename2 = binpath + "/cuspbifurc_validation.png"
+as2017fig.arabas_shima_2017_fig(
+ time,
+ zprof,
+ sd0["radius"],
+ sd0["msol"],
+ thermo.temp[:, 0, 0, 0],
+ supersat[:, 0, 0, 0],
+ sddata.IONIC,
+ sddata.MR_SOL,
+ config["W_avg"],
+ numconc,
+ savename2,
+)
diff --git a/examples/boxmodelcollisions/shima2009.py b/examples/boxmodelcollisions/shima2009.py
index 162b720ae..7137b7161 100644
--- a/examples/boxmodelcollisions/shima2009.py
+++ b/examples/boxmodelcollisions/shima2009.py
@@ -1,4 +1,7 @@
-'''
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
----- CLEO -----
File: shima2009.py
Project: boxmodelcollisions
@@ -6,19 +9,17 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Friday 3rd May 2024
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
https://opensource.org/licenses/BSD-3-Clause
-----
-Copyright (c) 2023 MPI-M, Clara Bayley
------
File Description:
Script generates input files, runs CLEO 0-D box model executables for
collisions with selected collision kernels (e.g. Golovin's or Long's) to create data.
Then plots results similar to Shima et al. 2009 Fig. 2
-'''
+"""
import os
import sys
@@ -32,11 +33,13 @@
kernels = sys.argv[4:]
sys.path.append(path2CLEO) # for imports from pySD package
-sys.path.append(path2CLEO+"/examples/exampleplotting/") # for imports from example plotting package
+sys.path.append(
+ path2CLEO + "/examples/exampleplotting/"
+) # for imports from example plotting package
from plotssrc import shima2009fig
-from pySD.sdmout_src import *
-from pySD.initsuperdropsbinary_src import *
+from pySD.sdmout_src import pyzarr, pysetuptxt, pygbxsdat
+from pySD.initsuperdropsbinary_src import rgens, probdists, attrsgen
from pySD.initsuperdropsbinary_src import create_initsuperdrops as csupers
from pySD.initsuperdropsbinary_src import read_initsuperdrops as rsupers
from pySD.gbxboundariesbinary_src import read_gbxboundaries as rgrid
@@ -47,19 +50,19 @@
### ---------------------------------------------------------------- ###
### --- essential paths and filenames --- ###
# path and filenames for creating initial SD conditions
-constsfile = path2CLEO+"/libs/cleoconstants.hpp"
-binpath = path2build+"/bin/"
-sharepath = path2build+"/share/"
-initSDsfile = sharepath+"shima2009_dimlessSDsinit.dat"
-gridfile = sharepath+"shima2009_dimlessGBxboundaries.dat"
+constsfile = path2CLEO + "/libs/cleoconstants.hpp"
+binpath = path2build + "/bin/"
+sharepath = path2build + "/share/"
+initSDsfile = sharepath + "shima2009_dimlessSDsinit.dat"
+gridfile = sharepath + "shima2009_dimlessGBxboundaries.dat"
# path and file names for plotting results
-setupfile = binpath+"shima2009_setup.txt"
-dataset = binpath+"shima2009_sol.zarr"
+setupfile = binpath + "shima2009_setup.txt"
+dataset = binpath + "shima2009_sol.zarr"
# booleans for [making, saving] initialisation figures
isfigures = [True, True]
-savefigpath = path2build+"/bin/" # directory for saving figures
+savefigpath = path2build + "/bin/" # directory for saving figures
### --- settings for 0-D Model gridbox boundaries --- ###
zgrid = np.asarray([0, 100])
@@ -74,15 +77,15 @@
# settings for distirbution from exponential in droplet volume
# peak of volume exponential distribution [m]
volexpr0 = 30.531e-6
-numconc = 2**(23) # total no. conc of real droplets [m^-3]
-rspan = [1e-8, 9e-5] # max and min range of radii to sample [m]
+numconc = 2 ** (23) # total no. conc of real droplets [m^-3]
+rspan = [1e-8, 9e-5] # max and min range of radii to sample [m]
samplevol = rgrid.calc_domainvol(zgrid, xgrid, ygrid)
xiprobdist = probdists.VolExponential(volexpr0, rspan)
radiigen = rgens.SampleLog10RadiiGen(rspan) # radii are sampled from rspan [m]
-dryradiigen = rgens.MonoAttrGen(1e-16) # all SDs have negligible solute [m]
+dryradiigen = rgens.MonoAttrGen(1e-16) # all SDs have negligible solute [m]
-coord3gen = None # do not generate superdroplet coords
+coord3gen = None # do not generate superdroplet coords
coord1gen = None
coord2gen = None
@@ -94,55 +97,57 @@
### ---------------------------------------------------------------- ###
### --- ensure build, share and bin directories exist --- ###
if path2CLEO == path2build:
- raise ValueError("build directory cannot be CLEO")
+ raise ValueError("build directory cannot be CLEO")
else:
- Path(path2build).mkdir(exist_ok=True)
- Path(sharepath).mkdir(exist_ok=True)
- Path(binpath).mkdir(exist_ok=True)
- if isfigures[1]:
- Path(savefigpath).mkdir(exist_ok=True)
+ Path(path2build).mkdir(exist_ok=True)
+ Path(sharepath).mkdir(exist_ok=True)
+ Path(binpath).mkdir(exist_ok=True)
+ if isfigures[1]:
+ Path(savefigpath).mkdir(exist_ok=True)
### --- delete any existing initial conditions --- ###
-os.system("rm "+gridfile)
-os.system("rm "+initSDsfile)
+os.system("rm " + gridfile)
+os.system("rm " + initSDsfile)
### ----- write gridbox boundaries binary ----- ###
-cgrid.write_gridboxboundaries_binary(gridfile, zgrid, xgrid,
- ygrid, constsfile)
+cgrid.write_gridboxboundaries_binary(gridfile, zgrid, xgrid, ygrid, constsfile)
rgrid.print_domain_info(constsfile, gridfile)
### ----- write initial superdroplets binary ----- ###
-initattrsgen = attrsgen.AttrsGenerator(radiigen, dryradiigen, xiprobdist,
- coord3gen, coord1gen, coord2gen)
-csupers.write_initsuperdrops_binary(initSDsfile, initattrsgen,
- configfile, constsfile,
- gridfile, nsupers, numconc)
+initattrsgen = attrsgen.AttrsGenerator(
+ radiigen, dryradiigen, xiprobdist, coord3gen, coord1gen, coord2gen
+)
+csupers.write_initsuperdrops_binary(
+ initSDsfile, initattrsgen, configfile, constsfile, gridfile, nsupers, numconc
+)
rsupers.print_initSDs_infos(initSDsfile, configfile, constsfile, gridfile)
### ----- show (and save) plots of binary file data ----- ###
if isfigures[0]:
- rgrid.plot_gridboxboundaries(constsfile, gridfile,
- savefigpath, isfigures[1])
- rsupers.plot_initGBxs_distribs(configfile, constsfile, initSDsfile,
- gridfile, savefigpath, isfigures[1], "all")
+ rgrid.plot_gridboxboundaries(constsfile, gridfile, savefigpath, isfigures[1])
+ rsupers.plot_initGBxs_distribs(
+ configfile, constsfile, initSDsfile, gridfile, savefigpath, isfigures[1], "all"
+ )
plt.close()
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
+
def run_exectuable(path2build, dataset, executable, configfile):
- ''' delete existing dataset, the run exectuable with given config file'''
- os.chdir(path2build)
- os.system('pwd')
- os.system('rm -rf '+dataset) # delete any existing dataset
- print('Executable: '+executable)
- print('Config file: '+configfile)
- os.system(executable + ' ' + configfile)
+ """delete existing dataset, the run exectuable with given config file"""
+ os.chdir(path2build)
+ os.system("pwd")
+ os.system("rm -rf " + dataset) # delete any existing dataset
+ print("Executable: " + executable)
+ print("Config file: " + configfile)
+ os.system(executable + " " + configfile)
+
if "golovin" in kernels:
### ------------------------------------------------------------ ###
### -------------------- RUN CLEO EXECUTABLE ------------------- ###
### ------------------------------------------------------------ ###
- executable = path2build+'/examples/boxmodelcollisions/golovin/src/golcolls'
+ executable = path2build + "/examples/boxmodelcollisions/golovin/src/golcolls"
run_exectuable(path2build, dataset, executable, configfile)
### ------------------------------------------------------------ ###
### ------------------------------------------------------------ ###
@@ -161,14 +166,22 @@ def run_exectuable(path2build, dataset, executable, configfile):
# 4. plot results
tplt = [0, 1200, 2400, 3600]
# 0.2 factor for guassian smoothing
- smoothsig = 0.62*(config["maxnsupers"]**(-1/5))
+ smoothsig = 0.62 * (config["maxnsupers"] ** (-1 / 5))
plotwitherr = True
savename = savefigpath + "golovin_validation.png"
- fig, ax = shima2009fig.plot_validation_figure(plotwitherr, time,
- sddata, tplt, gbxs["domainvol"],
- numconc, volexpr0, smoothsig,
- savename=savename, withgol=True)
+ fig, ax = shima2009fig.plot_validation_figure(
+ plotwitherr,
+ time,
+ sddata,
+ tplt,
+ gbxs["domainvol"],
+ numconc,
+ volexpr0,
+ smoothsig,
+ savename=savename,
+ withgol=True,
+ )
### ------------------------------------------------------------ ###
### ------------------------------------------------------------ ###
@@ -176,7 +189,7 @@ def run_exectuable(path2build, dataset, executable, configfile):
### ------------------------------------------------------------ ###
### -------------------- RUN CLEO EXECUTABLE ------------------- ###
### ------------------------------------------------------------ ###
- executable = path2build+'/examples/boxmodelcollisions/long/src/longcolls'
+ executable = path2build + "/examples/boxmodelcollisions/long/src/longcolls"
run_exectuable(path2build, dataset, executable, configfile)
### ------------------------------------------------------------ ###
### ------------------------------------------------------------ ###
@@ -195,14 +208,21 @@ def run_exectuable(path2build, dataset, executable, configfile):
# 4. plot results
tplt = [0, 600, 1200, 1800]
# 0.2 factor for guassian smoothing
- smoothsig = 0.62*(config["maxnsupers"]**(-1/5))
+ smoothsig = 0.62 * (config["maxnsupers"] ** (-1 / 5))
plotwitherr = False
savename = savefigpath + "long_validation.png"
- fig, ax = shima2009fig.plot_validation_figure(plotwitherr, time,
- sddata, tplt, gbxs["domainvol"],
- numconc, volexpr0, smoothsig,
- savename=savename)
+ fig, ax = shima2009fig.plot_validation_figure(
+ plotwitherr,
+ time,
+ sddata,
+ tplt,
+ gbxs["domainvol"],
+ numconc,
+ volexpr0,
+ smoothsig,
+ savename=savename,
+ )
### ------------------------------------------------------------ ###
### ------------------------------------------------------------ ###
@@ -210,7 +230,7 @@ def run_exectuable(path2build, dataset, executable, configfile):
### ------------------------------------------------------------ ###
### -------------------- RUN CLEO EXECUTABLE ------------------- ###
### ------------------------------------------------------------ ###
- executable = path2build+'/examples/boxmodelcollisions/lowlist/src/lowlistcolls'
+ executable = path2build + "/examples/boxmodelcollisions/lowlist/src/lowlistcolls"
run_exectuable(path2build, dataset, executable, configfile)
### ------------------------------------------------------------ ###
### ------------------------------------------------------------ ###
@@ -229,13 +249,20 @@ def run_exectuable(path2build, dataset, executable, configfile):
# 4. plot results
tplt = [0, 600, 1200, 1800, 2400, 3600]
# 0.2 factor for guassian smoothing
- smoothsig = 0.62*(config["maxnsupers"]**(-1/5))
+ smoothsig = 0.62 * (config["maxnsupers"] ** (-1 / 5))
plotwitherr = False
savename = savefigpath + "lowlist_validation.png"
- fig, ax = shima2009fig.plot_validation_figure(plotwitherr, time,
- sddata, tplt, gbxs["domainvol"],
- numconc, volexpr0, smoothsig,
- savename=savename)
+ fig, ax = shima2009fig.plot_validation_figure(
+ plotwitherr,
+ time,
+ sddata,
+ tplt,
+ gbxs["domainvol"],
+ numconc,
+ volexpr0,
+ smoothsig,
+ savename=savename,
+ )
### ------------------------------------------------------------ ###
### ------------------------------------------------------------ ###
diff --git a/examples/constthermo2d/constthermo2d.py b/examples/constthermo2d/constthermo2d.py
index 9ad9cfd77..b8ac0f102 100644
--- a/examples/constthermo2d/constthermo2d.py
+++ b/examples/constthermo2d/constthermo2d.py
@@ -1,4 +1,7 @@
-'''
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
----- CLEO -----
File: constthermo2d.py
Project: constthermo2d
@@ -6,24 +9,21 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Friday 3rd May 2024
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
https://opensource.org/licenses/BSD-3-Clause
-----
-Copyright (c) 2023 MPI-M, Clara Bayley
------
File Description:
Script generatees input files, runs CLEO executable "const2D" to create
data and then plots precipitation example given 2-D flow field and
constant thermodynamics read from a file.
-'''
+"""
import os
import sys
import numpy as np
-import matplotlib.pyplot as plt
from pathlib import Path
from matplotlib.colors import LogNorm, Normalize
@@ -32,13 +32,15 @@
configfile = sys.argv[3]
sys.path.append(path2CLEO) # for imports from pySD package
-sys.path.append(path2CLEO+"/examples/exampleplotting/") # for imports from example plotting package
+sys.path.append(
+ path2CLEO + "/examples/exampleplotting/"
+) # for imports from example plotting package
from plotssrc import pltsds, pltmoms, animations
-from pySD.sdmout_src import *
+from pySD.sdmout_src import pyzarr, pysetuptxt, pygbxsdat
from pySD.gbxboundariesbinary_src import read_gbxboundaries as rgrid
from pySD.gbxboundariesbinary_src import create_gbxboundaries as cgrid
-from pySD.initsuperdropsbinary_src import *
+from pySD.initsuperdropsbinary_src import crdgens, rgens, dryrgens, probdists, attrsgen
from pySD.initsuperdropsbinary_src import create_initsuperdrops as csupers
from pySD.initsuperdropsbinary_src import read_initsuperdrops as rsupers
from pySD.thermobinary_src import thermogen
@@ -50,54 +52,54 @@
### ---------------------------------------------------------------- ###
### --- essential paths and filenames --- ###
# path and filenames for creating initial SD conditions
-constsfile = path2CLEO+"/libs/cleoconstants.hpp"
-binpath = path2build+"/bin/"
-sharepath = path2build+"/share/"
-gridfile = sharepath+"const2d_dimlessGBxboundaries.dat"
-initSDsfile = sharepath+"const2d_dimlessSDsinit.dat"
-thermofile = sharepath+"/const2d_dimlessthermo.dat"
+constsfile = path2CLEO + "/libs/cleoconstants.hpp"
+binpath = path2build + "/bin/"
+sharepath = path2build + "/share/"
+gridfile = sharepath + "const2d_dimlessGBxboundaries.dat"
+initSDsfile = sharepath + "const2d_dimlessSDsinit.dat"
+thermofile = sharepath + "/const2d_dimlessthermo.dat"
# path and file names for plotting results
-setupfile = binpath+"const2d_setup.txt"
-dataset = binpath+"const2d_sol.zarr"
+setupfile = binpath + "const2d_setup.txt"
+dataset = binpath + "const2d_sol.zarr"
### --- plotting initialisation figures --- ###
-isfigures = [True, True] # booleans for [making, saving] initialisation figures
-savefigpath = path2build+"/bin/" # directory for saving figures
-SDgbxs2plt = [0] # gbxindex of SDs to plot (nb. "all" can be very slow)
+isfigures = [True, True] # booleans for [making, saving] initialisation figures
+savefigpath = path2build + "/bin/" # directory for saving figures
+SDgbxs2plt = [0] # gbxindex of SDs to plot (nb. "all" can be very slow)
### --- settings for 2-D gridbox boundaries --- ###
-zgrid = [0, 1500, 75] # evenly spaced zhalf coords [zmin, zmax, zdelta] [m]
-xgrid = [0, 1500, 75] # evenly spaced xhalf coords [m]
+zgrid = [0, 1500, 75] # evenly spaced zhalf coords [zmin, zmax, zdelta] [m]
+xgrid = [0, 1500, 75] # evenly spaced xhalf coords [m]
ygrid = np.array([0, 20]) # array of yhalf coords [m]
### --- settings for initial superdroplets --- ###
# settings for initial superdroplet coordinates
-zlim = 500 # max z coord of superdroplets
-npergbx = 8 # number of superdroplets per gridbox
+zlim = 500 # max z coord of superdroplets
+npergbx = 8 # number of superdroplets per gridbox
# [min, max] range of initial superdroplet radii (and implicitly solute masses)
-rspan = [3e-9, 3e-6] # [m]
+rspan = [3e-9, 3e-6] # [m]
# settings for initial superdroplet multiplicies
# (from bimodal Lognormal distribution)
-geomeans = [0.02e-6, 0.15e-6]
-geosigs = [1.4, 1.6]
-scalefacs = [6e6, 4e6]
+geomeans = [0.02e-6, 0.15e-6]
+geosigs = [1.4, 1.6]
+scalefacs = [6e6, 4e6]
numconc = np.sum(scalefacs)
### --- settings for 2D Thermodynamics --- ###
-PRESS0 = 101315 # [Pa]
-THETA = 288.15 # [K]
-qcond = 0.0 # [Kg/Kg]
-WMAX = 0.6 # [m/s]
-VVEL = None # [m/s]
-Zlength = 1500 # [m]
-Xlength = 1500 # [m]
+PRESS0 = 101315 # [Pa]
+THETA = 288.15 # [K]
+qcond = 0.0 # [Kg/Kg]
+WMAX = 0.6 # [m/s]
+VVEL = None # [m/s]
+Zlength = 1500 # [m]
+Xlength = 1500 # [m]
qvapmethod = "sratio"
-Zbase = 750 # [m]
-sratios = [0.99, 1.0025] # s_ratio [below, above] Zbase
-moistlayer=False
+Zbase = 750 # [m]
+sratios = [0.99, 1.0025] # s_ratio [below, above] Zbase
+moistlayer = False
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
@@ -107,56 +109,75 @@
### ---------------------------------------------------------------- ###
### --- ensure build, share and bin directories exist --- ###
if path2CLEO == path2build:
- raise ValueError("build directory cannot be CLEO")
+ raise ValueError("build directory cannot be CLEO")
else:
- Path(path2build).mkdir(exist_ok=True)
- Path(sharepath).mkdir(exist_ok=True)
- Path(binpath).mkdir(exist_ok=True)
+ Path(path2build).mkdir(exist_ok=True)
+ Path(sharepath).mkdir(exist_ok=True)
+ Path(binpath).mkdir(exist_ok=True)
### --- delete any existing initial conditions --- ###
-os.system("rm "+gridfile)
-os.system("rm "+initSDsfile)
-os.system("rm "+thermofile[:-4]+"*")
+os.system("rm " + gridfile)
+os.system("rm " + initSDsfile)
+os.system("rm " + thermofile[:-4] + "*")
### ----- write gridbox boundaries binary ----- ###
cgrid.write_gridboxboundaries_binary(gridfile, zgrid, xgrid, ygrid, constsfile)
rgrid.print_domain_info(constsfile, gridfile)
### ----- write thermodynamics binaries ----- ###
-thermodyngen = thermogen.ConstDryHydrostaticAdiabat(configfile, constsfile, PRESS0,
- THETA, qvapmethod, sratios, Zbase,
- qcond, WMAX, Zlength, Xlength,
- VVEL, moistlayer)
-cthermo.write_thermodynamics_binary(thermofile, thermodyngen, configfile,
- constsfile, gridfile)
+thermodyngen = thermogen.ConstDryHydrostaticAdiabat(
+ configfile,
+ constsfile,
+ PRESS0,
+ THETA,
+ qvapmethod,
+ sratios,
+ Zbase,
+ qcond,
+ WMAX,
+ Zlength,
+ Xlength,
+ VVEL,
+ moistlayer,
+)
+cthermo.write_thermodynamics_binary(
+ thermofile, thermodyngen, configfile, constsfile, gridfile
+)
### ----- write initial superdroplets binary ----- ###
nsupers = crdgens.nsupers_at_domain_base(gridfile, constsfile, npergbx, zlim)
-coord3gen = crdgens.SampleCoordGen(True) # sample coord3 randomly
-coord1gen = crdgens.SampleCoordGen(True) # sample coord1 randomly
-coord2gen = None # do not generate superdroplet coord2s
+coord3gen = crdgens.SampleCoordGen(True) # sample coord3 randomly
+coord1gen = crdgens.SampleCoordGen(True) # sample coord1 randomly
+coord2gen = None # do not generate superdroplet coord2s
xiprobdist = probdists.LnNormal(geomeans, geosigs, scalefacs)
-radiigen = rgens.SampleLog10RadiiGen(rspan) # randomly sample radii from rspan [m]
+radiigen = rgens.SampleLog10RadiiGen(rspan) # randomly sample radii from rspan [m]
dryradiigen = dryrgens.ScaledRadiiGen(1.0)
-initattrsgen = attrsgen.AttrsGenerator(radiigen, dryradiigen, xiprobdist,
- coord3gen, coord1gen, coord2gen)
-csupers.write_initsuperdrops_binary(initSDsfile, initattrsgen,
- configfile, constsfile,
- gridfile, nsupers, numconc)
+initattrsgen = attrsgen.AttrsGenerator(
+ radiigen, dryradiigen, xiprobdist, coord3gen, coord1gen, coord2gen
+)
+csupers.write_initsuperdrops_binary(
+ initSDsfile, initattrsgen, configfile, constsfile, gridfile, nsupers, numconc
+)
### ----- show (and save) plots of binary file data ----- ###
if isfigures[0]:
- if isfigures[1]:
- Path(savefigpath).mkdir(exist_ok=True)
- rgrid.plot_gridboxboundaries(constsfile, gridfile,
- savefigpath, isfigures[1])
- rthermo.plot_thermodynamics(constsfile, configfile, gridfile,
- thermofile, savefigpath, isfigures[1])
- rsupers.plot_initGBxs_distribs(configfile, constsfile, initSDsfile,
- gridfile, savefigpath, isfigures[1],
- SDgbxs2plt)
+ if isfigures[1]:
+ Path(savefigpath).mkdir(exist_ok=True)
+ rgrid.plot_gridboxboundaries(constsfile, gridfile, savefigpath, isfigures[1])
+ rthermo.plot_thermodynamics(
+ constsfile, configfile, gridfile, thermofile, savefigpath, isfigures[1]
+ )
+ rsupers.plot_initGBxs_distribs(
+ configfile,
+ constsfile,
+ initSDsfile,
+ gridfile,
+ savefigpath,
+ isfigures[1],
+ SDgbxs2plt,
+ )
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
@@ -164,12 +185,12 @@
### -------------------- RUN CLEO EXECUTABLE ------------------- ###
### ---------------------------------------------------------------- ###
os.chdir(path2build)
-os.system('pwd')
-os.system('rm -rf '+dataset) # delete any existing dataset
-executable = path2build+'/examples/constthermo2d/src/const2D'
-print('Executable: '+executable)
-print('Config file: '+configfile)
-os.system(executable + ' ' + configfile)
+os.system("pwd")
+os.system("rm -rf " + dataset) # delete any existing dataset
+executable = path2build + "/examples/constthermo2d/src/const2D"
+print("Executable: " + executable)
+print("Config file: " + configfile)
+os.system(executable + " " + configfile)
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
@@ -195,56 +216,83 @@
nsample = 500
savename = savefigpath + "const2d_randomsample.png"
-pltsds.plot_randomsample_superdrops(time, sddata,
- config["maxnsupers"],
- nsample,
- savename=savename)
+pltsds.plot_randomsample_superdrops(
+ time, sddata, config["maxnsupers"], nsample, savename=savename
+)
savename = savefigpath + "const2d_motion2d.png"
-pltsds.plot_randomsample_superdrops_2dmotion(sddata,
- config["maxnsupers"],
- nsample,
- savename=savename,
- arrows=False)
+pltsds.plot_randomsample_superdrops_2dmotion(
+ sddata, config["maxnsupers"], nsample, savename=savename, arrows=False
+)
+
### ----- plot 1-D .gif animations ----- ###
def horizontal_average(data4d):
- '''avg 4-D data with dims [time, y, x, z]
- over x and y dimensions '''
- return np.mean(data4d, axis=(1,2))
+ """avg 4-D data with dims [time, y, x, z]
+ over x and y dimensions"""
+ return np.mean(data4d, axis=(1, 2))
+
nframes = len(time.mins)
-norm = np.sum(gbxs["gbxvols"], axis=0)[None,None,:,:] * 1e6 # volume [cm^3]
-mom2ani = horizontal_average(massmoms.mom0/norm)
+norm = np.sum(gbxs["gbxvols"], axis=0)[None, None, :, :] * 1e6 # volume [cm^3]
+mom2ani = horizontal_average(massmoms.mom0 / norm)
xlims = [0, np.amax(mom2ani)]
xlabel = "mean number concentration /cm$^{-3}$"
-savename=savefigpath+"const2d_numconc1d"
-animations.animate1dprofile(gbxs, mom2ani, time.mins, nframes,
- xlabel=xlabel, xlims=xlims,
- color="green", saveani=True,
- savename=savename, fps=5)
+savename = savefigpath + "const2d_numconc1d"
+animations.animate1dprofile(
+ gbxs,
+ mom2ani,
+ time.mins,
+ nframes,
+ xlabel=xlabel,
+ xlims=xlims,
+ color="green",
+ saveani=True,
+ savename=savename,
+ fps=5,
+)
### ----- plot 2-D .gif animations ----- ###
nframes = len(time.mins)
-mom2ani = np.sum(massmoms.nsupers, axis=1) # sum over y dimension
-cmap="plasma_r"
+mom2ani = np.sum(massmoms.nsupers, axis=1) # sum over y dimension
+cmap = "plasma_r"
cmapnorm = Normalize(vmin=1, vmax=20)
-cbarlabel="number of superdroplets per gridbox"
-savename=savefigpath+"const2d_nsupers2d"
-animations.animate2dcmap(gbxs, mom2ani, time.mins, nframes,
- cbarlabel=cbarlabel, cmapnorm=cmapnorm, cmap=cmap,
- saveani=True, savename=savename, fps=5)
+cbarlabel = "number of superdroplets per gridbox"
+savename = savefigpath + "const2d_nsupers2d"
+animations.animate2dcmap(
+ gbxs,
+ mom2ani,
+ time.mins,
+ nframes,
+ cbarlabel=cbarlabel,
+ cmapnorm=cmapnorm,
+ cmap=cmap,
+ saveani=True,
+ savename=savename,
+ fps=5,
+)
nframes = len(time.mins)
-mom2ani = np.sum(massmoms.mom1, axis=1) # sum over y dimension
-norm = np.sum(gbxs["gbxvols"], axis=0)[None,:,:] # sum over y dimension and add time dimension for broadcasting [m^3]
+mom2ani = np.sum(massmoms.mom1, axis=1) # sum over y dimension
+norm = np.sum(gbxs["gbxvols"], axis=0)[
+ None, :, :
+] # sum over y dimension and add time dimension for broadcasting [m^3]
mom2ani = mom2ani / norm
-cmap="bone_r"
+cmap = "bone_r"
cmapnorm = LogNorm(vmin=1e-6, vmax=1e2)
cbarlabel = "mass concentration /g m$^{-3}$"
-savename=savefigpath+"const2d_massconc2d"
-animations.animate2dcmap(gbxs, mom2ani, time.mins, nframes,
- cbarlabel=cbarlabel, cmapnorm=cmapnorm, cmap=cmap,
- saveani=True, savename=savename, fps=5)
+savename = savefigpath + "const2d_massconc2d"
+animations.animate2dcmap(
+ gbxs,
+ mom2ani,
+ time.mins,
+ nframes,
+ cbarlabel=cbarlabel,
+ cmapnorm=cmapnorm,
+ cmap=cmap,
+ saveani=True,
+ savename=savename,
+ fps=5,
+)
### ------------------------------------------------------------ ###
### ------------------------------------------------------------ ###
diff --git a/examples/divfreemotion/divfree2d.py b/examples/divfreemotion/divfree2d.py
index fd7eabeec..a2c44124c 100644
--- a/examples/divfreemotion/divfree2d.py
+++ b/examples/divfreemotion/divfree2d.py
@@ -1,4 +1,7 @@
-'''
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
----- CLEO -----
File: divfree2d.py
Project: divfreemotion
@@ -6,24 +9,20 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Friday 3rd May 2024
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
https://opensource.org/licenses/BSD-3-Clause
-----
-Copyright (c) 2023 MPI-M, Clara Bayley
------
File Description:
Script generates input files, then runs CLEO executable "divfree2D" to create the
data to then plot for divergence free motion of superdroplets in a 2-D divergence
free wind field.
-'''
+"""
import os
import sys
-import numpy as np
-import matplotlib.pyplot as plt
from pathlib import Path
import divfree2d_inputfiles
@@ -32,45 +31,49 @@
configfile = sys.argv[3]
sys.path.append(path2CLEO) # for imports from pySD package
-sys.path.append(path2CLEO+"/examples/exampleplotting/") # for imports from example plotting package
+sys.path.append(
+ path2CLEO + "/examples/exampleplotting/"
+) # for imports from example plotting package
from plotssrc import pltsds, pltmoms
-from pySD.sdmout_src import *
+from pySD.sdmout_src import pyzarr, pysetuptxt, pygbxsdat
### ---------------------------------------------------------------- ###
### ----------------------- INPUT PARAMETERS ----------------------- ###
### ---------------------------------------------------------------- ###
### --- essential paths and filenames --- ###
# path and filenames for creating initial SD conditions
-binpath = path2build+"/bin/"
-sharepath = path2build+"/share/"
-gridfile = sharepath+"df2d_dimlessGBxboundaries.dat"
-initSDsfile = sharepath+"df2d_dimlessSDsinit.dat"
-thermofile = sharepath+"/df2d_dimlessthermo.dat"
-savefigpath = path2build+"/bin/" # directory for saving figures
+binpath = path2build + "/bin/"
+sharepath = path2build + "/share/"
+gridfile = sharepath + "df2d_dimlessGBxboundaries.dat"
+initSDsfile = sharepath + "df2d_dimlessSDsinit.dat"
+thermofile = sharepath + "/df2d_dimlessthermo.dat"
+savefigpath = path2build + "/bin/" # directory for saving figures
# path and file names for plotting results
-setupfile = binpath+"df2d_setup.txt"
-dataset = binpath+"df2d_sol.zarr"
+setupfile = binpath + "df2d_setup.txt"
+dataset = binpath + "df2d_sol.zarr"
### ---------------------------------------------------------------- ###
### ------------------- BINARY FILES GENERATION--------------------- ###
### ---------------------------------------------------------------- ###
### --- ensure build, share and bin directories exist --- ###
if path2CLEO == path2build:
- raise ValueError("build directory cannot be CLEO")
+ raise ValueError("build directory cannot be CLEO")
else:
- Path(path2build).mkdir(exist_ok=True)
- Path(sharepath).mkdir(exist_ok=True)
- Path(binpath).mkdir(exist_ok=True)
- Path(savefigpath).mkdir(exist_ok=True)
+ Path(path2build).mkdir(exist_ok=True)
+ Path(sharepath).mkdir(exist_ok=True)
+ Path(binpath).mkdir(exist_ok=True)
+ Path(savefigpath).mkdir(exist_ok=True)
### --- delete any existing initial conditions --- ###
-os.system("rm "+gridfile)
-os.system("rm "+initSDsfile)
-os.system("rm "+thermofile[:-4]+"*")
+os.system("rm " + gridfile)
+os.system("rm " + initSDsfile)
+os.system("rm " + thermofile[:-4] + "*")
-divfree2d_inputfiles.main(path2CLEO, path2build, configfile, gridfile, initSDsfile, thermofile)
+divfree2d_inputfiles.main(
+ path2CLEO, path2build, configfile, gridfile, initSDsfile, thermofile
+)
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
@@ -78,12 +81,12 @@
### ---------------------- RUN CLEO EXECUTABLE --------------------- ###
### ---------------------------------------------------------------- ###
os.chdir(path2build)
-os.system('pwd')
-os.system('rm -rf '+dataset) # delete any existing dataset
-executable = path2build+'/examples/divfreemotion/src/divfree2D'
-print('Executable: '+executable)
-print('Config file: '+configfile)
-os.system(executable + ' ' + configfile)
+os.system("pwd")
+os.system("rm -rf " + dataset) # delete any existing dataset
+executable = path2build + "/examples/divfreemotion/src/divfree2D"
+print("Executable: " + executable)
+print("Config file: " + configfile)
+os.system(executable + " " + configfile)
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
@@ -97,7 +100,7 @@
time = pyzarr.get_time(dataset)
sddata = pyzarr.get_supers(dataset, consts)
-maxnsupers =pyzarr.get_totnsupers(dataset)
+maxnsupers = pyzarr.get_totnsupers(dataset)
# 4. plot results
savename = savefigpath + "df2d_maxnsupers_validation.png"
@@ -105,10 +108,8 @@
nsample = 500
savename = savefigpath + "df2d_motion2d_validation.png"
-pltsds.plot_randomsample_superdrops_2dmotion(sddata,
- config["maxnsupers"],
- nsample,
- savename=savename,
- arrows=False)
+pltsds.plot_randomsample_superdrops_2dmotion(
+ sddata, config["maxnsupers"], nsample, savename=savename, arrows=False
+)
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
diff --git a/examples/divfreemotion/divfree2d_inputfiles.py b/examples/divfreemotion/divfree2d_inputfiles.py
index ea6f9c2e0..255a04c52 100644
--- a/examples/divfreemotion/divfree2d_inputfiles.py
+++ b/examples/divfreemotion/divfree2d_inputfiles.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: divfree2d_inputfiles.py
Project: divfreemotion
@@ -17,10 +17,11 @@
File Description:
Script generates input files for divergence free motion of superdroplets in
a 2-D divergence free wind field.
-'''
+"""
import sys
+
def main(path2CLEO, path2build, configfile, gridfile, initSDsfile, thermofile):
import numpy as np
import matplotlib.pyplot as plt
@@ -29,7 +30,13 @@ def main(path2CLEO, path2build, configfile, gridfile, initSDsfile, thermofile):
from pySD.gbxboundariesbinary_src import read_gbxboundaries as rgrid
from pySD.gbxboundariesbinary_src import create_gbxboundaries as cgrid
- from pySD.initsuperdropsbinary_src import crdgens, rgens, dryrgens, probdists, attrsgen
+ from pySD.initsuperdropsbinary_src import (
+ crdgens,
+ rgens,
+ dryrgens,
+ probdists,
+ attrsgen,
+ )
from pySD.initsuperdropsbinary_src import create_initsuperdrops as csupers
from pySD.initsuperdropsbinary_src import read_initsuperdrops as rsupers
from pySD.thermobinary_src import thermogen
@@ -41,44 +48,44 @@ def main(path2CLEO, path2build, configfile, gridfile, initSDsfile, thermofile):
### ---------------------------------------------------------------- ###
### --- essential paths and filenames --- ###
# path and filenames for creating initial SD conditions
- constsfile = path2CLEO+"/libs/cleoconstants.hpp"
+ constsfile = path2CLEO + "/libs/cleoconstants.hpp"
### --- plotting initialisation figures --- ###
- isfigures = [True, True] # booleans for [making, saving] initialisation figures
- savefigpath = path2build+"/bin/" # directory for saving figures
- SDgbxs2plt = [0] # gbxindex of SDs to plot (nb. "all" can be very slow)
+ isfigures = [True, True] # booleans for [making, saving] initialisation figures
+ savefigpath = path2build + "/bin/" # directory for saving figures
+ SDgbxs2plt = [0] # gbxindex of SDs to plot (nb. "all" can be very slow)
### --- settings for 2-D gridbox boundaries --- ###
- zgrid = [0, 1500, 50] # evenly spaced zhalf coords [zmin, zmax, zdelta] [m]
- xgrid = [0, 1500, 50] # evenly spaced xhalf coords [m]
+ zgrid = [0, 1500, 50] # evenly spaced zhalf coords [zmin, zmax, zdelta] [m]
+ xgrid = [0, 1500, 50] # evenly spaced xhalf coords [m]
ygrid = np.array([0, 20]) # array of yhalf coords [m]
### --- settings for initial superdroplets --- ###
# settings for initial superdroplet coordinates
- zlim = 750 # max z coord of superdroplets
- npergbx = 8 # number of superdroplets per gridbox
+ zlim = 750 # max z coord of superdroplets
+ npergbx = 8 # number of superdroplets per gridbox
# [min, max] range of initial superdroplet radii (and implicitly solute masses)
- rspan = [3e-9, 3e-6] # [m]
+ rspan = [3e-9, 3e-6] # [m]
# settings for initial superdroplet multiplicies
# (from bimodal Lognormal distribution)
- geomeans = [0.02e-6, 0.15e-6]
- geosigs = [1.4, 1.6]
- scalefacs = [6e6, 4e6]
+ geomeans = [0.02e-6, 0.15e-6]
+ geosigs = [1.4, 1.6]
+ scalefacs = [6e6, 4e6]
numconc = np.sum(scalefacs)
### --- settings for 2D Thermodynamics --- ###
- PRESS0 = 100000 # [Pa]
+ PRESS0 = 100000 # [Pa]
THETA = 298.15 # [K]
- qcond = 0.0 # [Kg/Kg]
- WMAX = 0.6 # [m/s]
- VVEL = None # [m/s]
+ qcond = 0.0 # [Kg/Kg]
+ WMAX = 0.6 # [m/s]
+ VVEL = None # [m/s]
Zlength = 1500 # [m]
Xlength = 1500 # [m]
qvapmethod = "sratio"
- Zbase = 750 # [m]
- sratios = [1.0, 1.0] # s_ratio [below, above] Zbase
+ Zbase = 750 # [m]
+ sratios = [1.0, 1.0] # s_ratio [below, above] Zbase
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
@@ -93,41 +100,60 @@ def main(path2CLEO, path2build, configfile, gridfile, initSDsfile, thermofile):
rgrid.print_domain_info(constsfile, gridfile)
### ----- write thermodynamics binaries ----- ###
- thermodyngen = thermogen.SimpleThermo2DFlowField(configfile, constsfile, PRESS0,
- THETA, qvapmethod, sratios, Zbase,
- qcond, WMAX, Zlength, Xlength,
- VVEL)
- cthermo.write_thermodynamics_binary(thermofile, thermodyngen, configfile,
- constsfile, gridfile)
+ thermodyngen = thermogen.SimpleThermo2DFlowField(
+ configfile,
+ constsfile,
+ PRESS0,
+ THETA,
+ qvapmethod,
+ sratios,
+ Zbase,
+ qcond,
+ WMAX,
+ Zlength,
+ Xlength,
+ VVEL,
+ )
+ cthermo.write_thermodynamics_binary(
+ thermofile, thermodyngen, configfile, constsfile, gridfile
+ )
### ----- write initial superdroplets binary ----- ###
nsupers = crdgens.nsupers_at_domain_base(gridfile, constsfile, npergbx, zlim)
- coord3gen = crdgens.SampleCoordGen(True) # sample coord3 randomly
- coord1gen = crdgens.SampleCoordGen(True) # sample coord1 randomly
- coord2gen = None # do not generate superdroplet coord2s
+ coord3gen = crdgens.SampleCoordGen(True) # sample coord3 randomly
+ coord1gen = crdgens.SampleCoordGen(True) # sample coord1 randomly
+ coord2gen = None # do not generate superdroplet coord2s
xiprobdist = probdists.LnNormal(geomeans, geosigs, scalefacs)
- radiigen = rgens.SampleLog10RadiiGen(rspan) # randomly sample radii from rspan [m]
+ radiigen = rgens.SampleLog10RadiiGen(rspan) # randomly sample radii from rspan [m]
dryradiigen = dryrgens.ScaledRadiiGen(1.0)
- initattrsgen = attrsgen.AttrsGenerator(radiigen, dryradiigen, xiprobdist,
- coord3gen, coord1gen, coord2gen)
- csupers.write_initsuperdrops_binary(initSDsfile, initattrsgen,
- configfile, constsfile,
- gridfile, nsupers, numconc)
+ initattrsgen = attrsgen.AttrsGenerator(
+ radiigen, dryradiigen, xiprobdist, coord3gen, coord1gen, coord2gen
+ )
+ csupers.write_initsuperdrops_binary(
+ initSDsfile, initattrsgen, configfile, constsfile, gridfile, nsupers, numconc
+ )
### ----- show (and save) plots of binary file data ----- ###
if isfigures[0]:
- rgrid.plot_gridboxboundaries(constsfile, gridfile,
- savefigpath, isfigures[1])
- rthermo.plot_thermodynamics(constsfile, configfile, gridfile,
- thermofile, savefigpath, isfigures[1])
- rsupers.plot_initGBxs_distribs(configfile, constsfile, initSDsfile,
- gridfile, savefigpath, isfigures[1],
- SDgbxs2plt)
- plt.close()
+ rgrid.plot_gridboxboundaries(constsfile, gridfile, savefigpath, isfigures[1])
+ rthermo.plot_thermodynamics(
+ constsfile, configfile, gridfile, thermofile, savefigpath, isfigures[1]
+ )
+ rsupers.plot_initGBxs_distribs(
+ configfile,
+ constsfile,
+ initSDsfile,
+ gridfile,
+ savefigpath,
+ isfigures[1],
+ SDgbxs2plt,
+ )
+ plt.close()
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
+
if __name__ == "__main__":
### args = path2CLEO, path2build, configfile, binpath, gridfile, initSDsfile, thermofile
main(*sys.argv[1:])
diff --git a/examples/exampleplotting/__init__.py b/examples/exampleplotting/__init__.py
index eba3ec8e5..0287508cf 100644
--- a/examples/exampleplotting/__init__.py
+++ b/examples/exampleplotting/__init__.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: __init__.py
Project: exampleplotting
@@ -15,4 +15,4 @@
Copyright (c) 2023 MPI-M, Clara Bayley
-----
File Description:
-'''
+"""
diff --git a/examples/exampleplotting/exampleplotting.py b/examples/exampleplotting/exampleplotting.py
index c679b377f..9c6a1b97d 100644
--- a/examples/exampleplotting/exampleplotting.py
+++ b/examples/exampleplotting/exampleplotting.py
@@ -1,4 +1,7 @@
-'''
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
----- CLEO -----
File: exampleplotting.py
Project: exampleplotting
@@ -6,29 +9,23 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Friday 3rd May 2024
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
https://opensource.org/licenses/BSD-3-Clause
-----
-Copyright (c) 2023 MPI-M, Clara Bayley
------
File Description:
-'''
+"""
import os
import sys
-import numpy as np
-import xarray as xr
import awkward as ak
-import matplotlib.pyplot as plt
-path2pySD = os.path.dirname(os.path.realpath(__file__))+"/../../"
+path2pySD = os.path.dirname(os.path.realpath(__file__)) + "/../../"
sys.path.append(path2pySD)
-from pySD.sdmout_src import *
-from pySD.sdmout_src import sdtracing
-from plotssrc import pltmoms, pltsds, pltdist
+from pySD.sdmout_src import pyzarr, pysetuptxt, pygbxsdat
+from plotssrc import pltsds, pltdist
### ---------------------- input parameters ------------------------ ###
### paths to data for plotting
@@ -72,39 +69,53 @@
### ----------------- plot individual superdroplets ---------------- ###
savename = ""
if savefig:
- savename = savefigpath+"/randomsample_attrs.png"
-pltsds.plot_randomsample_superdrops(time, sddata, config["maxnsupers"],
- nsample, savename=savename)
+ savename = savefigpath + "/randomsample_attrs.png"
+pltsds.plot_randomsample_superdrops(
+ time, sddata, config["maxnsupers"], nsample, savename=savename
+)
if savefig:
- savename = savefigpath+"/randomsample_2dmotion.png"
-pltsds.plot_randomsample_superdrops_2dmotion(sddata, config["maxnsupers"],
- nsample, arrows=False)
+ savename = savefigpath + "/randomsample_2dmotion.png"
+pltsds.plot_randomsample_superdrops_2dmotion(
+ sddata, config["maxnsupers"], nsample, arrows=False
+)
### ---------------------------------------------------------------- ###
### ------------------ plot droplet distributions ------------------ ###
if rspan == ["min", "max"]:
- non_nanradius = ak.nan_to_none(sddata["radius"])
- rspan = [ak.min(non_nanradius), ak.max(non_nanradius)]
+ non_nanradius = ak.nan_to_none(sddata["radius"])
+ rspan = [ak.min(non_nanradius), ak.max(non_nanradius)]
if smoothsig_mass:
- smoothsig_mass = smoothsig_mass*(config["maxnsupers"]**(-1/5))
+ smoothsig_mass = smoothsig_mass * (config["maxnsupers"] ** (-1 / 5))
if savefig:
- savename = savefigpath+"/domain_mass_distrib.png"
-fig, ax = pltdist.plot_domainmass_distribs(time.secs, sddata, t2plts,
- gbxs["domainvol"], rspan, nbins,
- smoothsig=smoothsig_mass,
- perlogR=perlogR_mass,
- ylog=ylog_mass,
- savename=savename)
+ savename = savefigpath + "/domain_mass_distrib.png"
+fig, ax = pltdist.plot_domainmass_distribs(
+ time.secs,
+ sddata,
+ t2plts,
+ gbxs["domainvol"],
+ rspan,
+ nbins,
+ smoothsig=smoothsig_mass,
+ perlogR=perlogR_mass,
+ ylog=ylog_mass,
+ savename=savename,
+)
if smoothsig_num:
- smoothsig_num = smoothsig_num*(config["maxnsupers"]**(-1/5))
+ smoothsig_num = smoothsig_num * (config["maxnsupers"] ** (-1 / 5))
if savefig:
- savename = savefigpath+"/domain_numconc_distrib.png"
-fig, ax = pltdist.plot_domainnumconc_distribs(time.secs, sddata, t2plts,
- gbxs["domainvol"], rspan, nbins,
- smoothsig=smoothsig_num,
- perlogR=perlogR_num,
- ylog=ylog_num,
- savename=savename)
+ savename = savefigpath + "/domain_numconc_distrib.png"
+fig, ax = pltdist.plot_domainnumconc_distribs(
+ time.secs,
+ sddata,
+ t2plts,
+ gbxs["domainvol"],
+ rspan,
+ nbins,
+ smoothsig=smoothsig_num,
+ perlogR=perlogR_num,
+ ylog=ylog_num,
+ savename=savename,
+)
### ---------------------------------------------------------------- ###
diff --git a/examples/exampleplotting/plotssrc/__init__.py b/examples/exampleplotting/plotssrc/__init__.py
index 5f9b99f38..67130ead5 100644
--- a/examples/exampleplotting/plotssrc/__init__.py
+++ b/examples/exampleplotting/plotssrc/__init__.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: __init__.py
Project: src
@@ -15,4 +15,4 @@
Copyright (c) 2023 MPI-M, Clara Bayley
-----
File Description:
-'''
+"""
diff --git a/examples/exampleplotting/plotssrc/animations.py b/examples/exampleplotting/plotssrc/animations.py
index 748683449..37703332f 100644
--- a/examples/exampleplotting/plotssrc/animations.py
+++ b/examples/exampleplotting/plotssrc/animations.py
@@ -1,4 +1,7 @@
-'''
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
----- CLEO -----
File: animations.py
Project: plotssrc
@@ -6,19 +9,16 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Monday 8th April 2024
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
https://opensource.org/licenses/BSD-3-Clause
-----
-Copyright (c) 2023 MPI-M, Clara Bayley
------
File Description:
functions to create 1D or 2D animations
of some output from CLEO e.g. mass moments
-'''
-
+"""
import numpy as np
import matplotlib.pyplot as plt
@@ -27,137 +27,176 @@
from matplotlib.cm import ScalarMappable
-def animate_me(fig, update_frame, frames, plot_init,
- saveani=False, savename=None, fargs=(), fps=5):
-
- print("making animation")
- ani = FuncAnimation(fig, update_frame, frames=range(0, frames),
- init_func=plot_init, fargs=fargs)
-
- if saveani:
- print("saving animation as "+savename+".gif")
- ani.save(savename+".gif",
- writer=animation.PillowWriter(fps=fps, bitrate=5000, codec='h264'),
- dpi=100, savefig_kwargs={'transparent': True})
-
-def animate1dprofile(gbxs, mom, timemins, nframes,
- xlabel=None, xlims=[None, None], color="black",
- saveani=False, savename=None, fps=5):
-
- fig, ax, plots, txt, zkm = prepare_1dprofile(gbxs, mom, timemins,
- xlabel, color=color)
-
- def init_1dprofile():
+def animate_me(
+ fig, update_frame, frames, plot_init, saveani=False, savename=None, fargs=(), fps=5
+):
+ print("making animation")
+ ani = FuncAnimation(
+ fig, update_frame, frames=range(0, frames), init_func=plot_init, fargs=fargs
+ )
+
+ if saveani:
+ print("saving animation as " + savename + ".gif")
+ ani.save(
+ savename + ".gif",
+ writer=animation.PillowWriter(fps=fps, bitrate=5000, codec="h264"),
+ dpi=100,
+ savefig_kwargs={"transparent": True},
+ )
+
+
+def animate1dprofile(
+ gbxs,
+ mom,
+ timemins,
+ nframes,
+ xlabel=None,
+ xlims=[None, None],
+ color="black",
+ saveani=False,
+ savename=None,
+ fps=5,
+):
+ fig, ax, plots, txt, zkm = prepare_1dprofile(
+ gbxs, mom, timemins, xlabel, color=color
+ )
+
+ def init_1dprofile():
+ ax.spines["top"].set_visible(False)
+ ax.spines["right"].set_visible(False)
+
+ ylims = [0, ax.get_ylim()[1]]
+ ax.set_ylim(ylims)
+ yticks = np.arange(ylims[0], ylims[1], 0.5)
+ ax.set_yticks(yticks, yticks, fontsize=16)
+
+ ax.set_xlim([xlims[0], xlims[1] * 1.1])
+ xticks = np.linspace(xlims[0], xlims[1], 3)
+ xticklabels = xticklabels = ["{:.1f}".format(x) for x in xticks]
+ ax.set_xticks(xticks, xticklabels, fontsize=16)
+
+ ax.tick_params(length=10, width=1)
+
+ fig.tight_layout()
+
+ return (
+ plots,
+ txt,
+ )
+
+ animate_me(
+ fig,
+ update_1dprofileframe,
+ nframes,
+ init_1dprofile,
+ saveani=saveani,
+ savename=savename,
+ fargs=(plots, txt, zkm, timemins, mom),
+ fps=fps,
+ )
- ax.spines['top'].set_visible(False)
- ax.spines['right'].set_visible(False)
-
- ylims = [0, ax.get_ylim()[1]]
- ax.set_ylim(ylims)
- yticks = np.arange(ylims[0], ylims[1], 0.5)
- ax.set_yticks(yticks, yticks, fontsize=16)
-
- ax.set_xlim([xlims[0], xlims[1]*1.1])
- xticks = np.linspace(xlims[0], xlims[1], 3)
- xticklabels = xticklabels = ["{:.1f}".format(x) for x in xticks]
- ax.set_xticks(xticks, xticklabels, fontsize=16)
-
- ax.tick_params(length=10, width=1)
-
- fig.tight_layout()
-
- return plots, txt,
-
- animate_me(fig, update_1dprofileframe, nframes, init_1dprofile,
- saveani=saveani, savename=savename,
- fargs=(plots, txt, zkm, timemins, mom), fps=fps)
def prepare_1dprofile(gbxs, massmom, timemins, xlabel, color):
+ fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(7, 8))
- fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(7, 8))
+ zkm = gbxs["zfull"] / 1000 # convert m to km
- alpha = 0.3
- zkm = gbxs["zfull"] / 1000 # convert m to km
+ f = 0
+ plots = ax.plot(massmom[f], zkm, color=color)[0]
- f = 0
- plots = ax.plot(massmom[f], zkm, color=color)[0]
+ timetext = "t = {:.0f}min".format(timemins[f])
+ txt = fig.text(0.7, 0.875, timetext, fontsize=16)
- timetext = "t = {:.0f}min".format(timemins[f])
- txt = fig.text(0.7, 0.875, timetext, fontsize=16)
+ ax.set_ylabel("z /km", fontsize=16)
+ ax.set_xlabel(xlabel, fontsize=16)
- ax.set_ylabel("z /km", fontsize=16)
- ax.set_xlabel(xlabel, fontsize=16)
+ fig.tight_layout()
- fig.tight_layout()
+ return fig, ax, plots, txt, zkm
- return fig, ax, plots, txt, zkm
def update_1dprofileframe(f, plots, txt, zkm, timemins, massmom):
+ plots.set_data(massmom[f], zkm)
+
+ timetext = "t = {:.0f}min".format(timemins[f])
+ txt.set_text(timetext)
+
+ return (
+ plots,
+ txt,
+ )
+
+
+def animate2dcmap(
+ gbxs,
+ mom2ani,
+ timemins,
+ nframes,
+ cbarlabel=None,
+ cmapnorm=None,
+ cmap="viridis",
+ saveani=False,
+ savename=None,
+ fps=5,
+):
+ fig, ax, cbar, plot, txt = prepare_2dplot(gbxs, mom2ani, timemins, cmap, cmapnorm)
+ cbar.set_label(cbarlabel, fontsize=16)
+
+ def init_2dcmap():
+ ax.set_aspect("equal")
+ ax.set_xlim([0, 1.5])
+ ax.set_ylim([0, 1.5])
+
+ ax.set_xlabel("x / km", fontsize=16)
+ ax.set_ylabel("z / km", fontsize=16)
+
+ ticks = [0, 0.75, 1.5]
+ ax.set_xticks(ticks, ticks, fontsize=16)
+ ax.set_yticks(ticks, ticks, fontsize=16)
+
+ ax.tick_params(length=10, width=1)
+
+ fig.tight_layout()
+ return plot
+
+ animate_me(
+ fig,
+ update_2dcmapframe,
+ nframes,
+ init_2dcmap,
+ saveani,
+ savename,
+ fargs=(plot, txt, timemins, mom2ani),
+ fps=fps,
+ )
- plots.set_data(massmom[f], zkm)
-
- timetext = "t = {:.0f}min".format(timemins[f])
- txt.set_text(timetext)
-
- return plots, txt,
-
-def animate2dcmap(gbxs, mom2ani, timemins, nframes,
- cbarlabel=None, cmapnorm=None, cmap="viridis",
- saveani=False, savename=None, fps=5):
-
- fig, ax, cbar, plot, txt = prepare_2dplot(gbxs, mom2ani, timemins,
- cmap, cmapnorm)
- cbar.set_label(cbarlabel, fontsize=16)
-
- def init_2dcmap():
-
- ax.set_aspect("equal")
- ax.set_xlim([0, 1.5])
- ax.set_ylim([0, 1.5])
-
- ax.set_xlabel("x / km", fontsize=16)
- ax.set_ylabel("z / km", fontsize=16)
-
- ticks = [0, 0.75, 1.5]
- ax.set_xticks(ticks, ticks, fontsize=16)
- ax.set_yticks(ticks, ticks, fontsize=16)
-
- ax.tick_params(length=10, width=1)
-
- fig.tight_layout()
- return plot
-
- animate_me(fig, update_2dcmapframe, nframes,
- init_2dcmap, saveani, savename,
- fargs=(plot, txt, timemins, mom2ani), fps=fps)
def prepare_2dplot(gbxs, massmom, timemins, cmap, cmapnorm):
+ fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(10, 9))
+
+ f = 0
+ data2d = massmom[f, :, :]
+ plot = ax.pcolormesh(
+ gbxs["xxh"] / 1000, gbxs["zzh"] / 1000, data2d, cmap=cmap, norm=cmapnorm
+ )
+ plot.cmap.set_under("w")
+ timetext = "t = {:.0f}min".format(timemins[f])
+ txt = fig.text(0.765, 0.925, timetext, fontsize=16, ha="right")
+
+ cbar = fig.colorbar(ScalarMappable(norm=cmapnorm, cmap=cmap), ax=ax, extend="both")
+ cbar.ax.tick_params(labelsize=16, which="both")
+ cbar.ax.tick_params(length=10, width=1, which="major")
+ cbar.ax.tick_params(length=10 / 3, width=1, which="minor")
- fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(10, 9))
-
- f=0
- data2d = massmom[f,:,:]
- plot = ax.pcolormesh(gbxs["xxh"]/1000, gbxs["zzh"]/1000, data2d,
- cmap=cmap, norm=cmapnorm)
- plot.cmap.set_under("w")
- timetext = "t = {:.0f}min".format(timemins[f])
- txt = fig.text(0.765, 0.925, timetext, fontsize=16, ha="right")
-
- cbar = fig.colorbar(ScalarMappable(norm=cmapnorm, cmap=cmap),
- ax=ax, extend="both")
- cbar.ax.tick_params(labelsize=16, which="both")
- cbar.ax.tick_params(length=10, width=1, which="major")
- cbar.ax.tick_params(length=10/3, width=1, which="minor")
+ fig.tight_layout()
- fig.tight_layout()
+ return fig, ax, cbar, plot, txt
- return fig, ax, cbar, plot, txt
def update_2dcmapframe(f, plot, txt, timemins, massmom):
+ plot.set_array(np.array(massmom[f, :, :]).ravel())
- plot.set_array(np.array(massmom[f,:,:]).ravel())
-
- timetext = "t = {:.0f}min".format(timemins[f])
- txt.set_text(timetext)
+ timetext = "t = {:.0f}min".format(timemins[f])
+ txt.set_text(timetext)
- return plot, txt
+ return plot, txt
diff --git a/examples/exampleplotting/plotssrc/as2017fig.py b/examples/exampleplotting/plotssrc/as2017fig.py
index 156055383..d3b0fb112 100644
--- a/examples/exampleplotting/plotssrc/as2017fig.py
+++ b/examples/exampleplotting/plotssrc/as2017fig.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: as2017fig.py
Project: plotssrc
@@ -18,20 +18,21 @@
functions for plotting similar to figure 5 of
"On the CCN (de)activation nonlinearities"
S. Arabas and S. Shima 2017
-'''
+"""
import numpy as np
import matplotlib.pyplot as plt
+
def kohler_curve(r, msol, temp, ionic, Mr_sol, criticalpoints=False):
- '''returns size and solute dependent
+ """returns size and solute dependent
equilibrium saturation ratio, s_eq,
for droplet according to kohler theory.
Equations from An Introduction to Clouds
- by Lohmann, Luond and Mahrt, 1st edition'''
+ by Lohmann, Luond and Mahrt, 1st edition"""
- r = r/1e6 # convert from micron to m
- msol = msol/1000 # convert from g to Kg
+ r = r / 1e6 # convert from micron to m
+ msol = msol / 1000 # convert from g to Kg
# eqn [6.24]
a = 3.3e-7 / temp
@@ -40,85 +41,139 @@ def kohler_curve(r, msol, temp, ionic, Mr_sol, criticalpoints=False):
b = 4.3e-6 * ionic * msol / Mr_sol
# eqn [6.25]
- s_eq = (a/r) - (b/(r**3))
+ s_eq = (a / r) - (b / (r**3))
if criticalpoints:
- rcrit = np.sqrt(3*b/a)
- scrit = 2*a/(3*rcrit)
+ rcrit = np.sqrt(3 * b / a)
+ scrit = 2 * a / (3 * rcrit)
return s_eq, rcrit, scrit
else:
return s_eq
-def plot_kohlercurve_with_criticalpoints(ax, r, solutemass,
- temperature, IONIC, MR_SOL):
-
+def plot_kohlercurve_with_criticalpoints(ax, r, solutemass, temperature, IONIC, MR_SOL):
rkoh = np.linspace(np.amin(r), np.amax(r), 300)
- s_eq, rcrit, scrit = kohler_curve(rkoh, solutemass, temperature, IONIC,
- MR_SOL, criticalpoints=True)
- ax.plot(rkoh, s_eq*100, label="K\u00F6hler curve",
- color="grey", linestyle="-", linewidth=3, zorder=-1)
- ax.scatter(rcrit, scrit*100, marker="x",
- color="darkred", s=100, zorder=-2)
-
-def condensation_validation_subplots(axs, time, radius, supersat, zprof,
- lwdth=1, lab=""):
- '''adds the subplots of displacement, supersaturation
+ s_eq, rcrit, scrit = kohler_curve(
+ rkoh, solutemass, temperature, IONIC, MR_SOL, criticalpoints=True
+ )
+ ax.plot(
+ rkoh,
+ s_eq * 100,
+ label="K\u00F6hler curve",
+ color="grey",
+ linestyle="-",
+ linewidth=3,
+ zorder=-1,
+ )
+ ax.scatter(rcrit, scrit * 100, marker="x", color="darkred", s=100, zorder=-2)
+
+
+def condensation_validation_subplots(
+ axs, time, radius, supersat, zprof, lwdth=1, lab=""
+):
+ """adds the subplots of displacement, supersaturation
and radial growth from Figure 5 of "On the CCN (de)activation
- nonlinearities" S. Arabas and S. Shima 2017'''
+ nonlinearities" S. Arabas and S. Shima 2017"""
- hlf = len(time)//2
- qtr = len(time)//4
+ hlf = len(time) // 2
+ qtr = len(time) // 4
- lab_a = "ascent "+lab
+ lab_a = "ascent " + lab
col_a, lstyle_a = "k", "-"
lab_b = "descent"
col_b, lstyle_b = "orange", "--"
- axs[0].plot(supersat[:hlf]*100, zprof[:hlf], label=lab_a,
- color=col_a, linestyle=lstyle_a, linewidth=lwdth)
- axs[0].plot(supersat[hlf:]*100, zprof[hlf:], label=lab_b,
- color=col_b, linestyle=lstyle_b, linewidth=lwdth)
+ axs[0].plot(
+ supersat[:hlf] * 100,
+ zprof[:hlf],
+ label=lab_a,
+ color=col_a,
+ linestyle=lstyle_a,
+ linewidth=lwdth,
+ )
+ axs[0].plot(
+ supersat[hlf:] * 100,
+ zprof[hlf:],
+ label=lab_b,
+ color=col_b,
+ linestyle=lstyle_b,
+ linewidth=lwdth,
+ )
axs[0].set_xlabel("supersaturation /%")
axs[0].set_ylabel("displacement /m")
- axs[1].plot(radius[:hlf], supersat[:hlf]*100,
- color=col_a, linestyle=lstyle_a, linewidth=lwdth)
- axs[1].plot(radius[hlf:], supersat[hlf:]*100,
- color=col_b, linestyle=lstyle_b, linewidth=lwdth)
+ axs[1].plot(
+ radius[:hlf],
+ supersat[:hlf] * 100,
+ color=col_a,
+ linestyle=lstyle_a,
+ linewidth=lwdth,
+ )
+ axs[1].plot(
+ radius[hlf:],
+ supersat[hlf:] * 100,
+ color=col_b,
+ linestyle=lstyle_b,
+ linewidth=lwdth,
+ )
axs[1].set_xlabel("radius /\u03BCm")
axs[1].set_ylabel("supersaturation /%")
- axs[2].plot(radius[:qtr], zprof[:qtr],
- color=col_a, linestyle=lstyle_a, linewidth=lwdth)
- axs[2].plot(radius[3*qtr:], zprof[3*qtr:],
- color=col_b, linestyle=lstyle_b, linewidth=lwdth)
+ axs[2].plot(
+ radius[:qtr], zprof[:qtr], color=col_a, linestyle=lstyle_a, linewidth=lwdth
+ )
+ axs[2].plot(
+ radius[3 * qtr :],
+ zprof[3 * qtr :],
+ color=col_b,
+ linestyle=lstyle_b,
+ linewidth=lwdth,
+ )
axs[2].set_xlabel("radius /\u03BCm")
axs[2].set_ylabel("displacement /m")
return axs
-def arabas_shima_2017_fig(time, zprof, radius, msol, temp, supersat,
- IONIC, MR_SOL, W_avg, numconc, savename=""):
- ''' plots the same plots as in Figure 5 of
+
+def arabas_shima_2017_fig(
+ time,
+ zprof,
+ radius,
+ msol,
+ temp,
+ supersat,
+ IONIC,
+ MR_SOL,
+ W_avg,
+ numconc,
+ savename="",
+):
+ """plots the same plots as in Figure 5 of
"On the CCN (de)activation nonlinearities"
S. Arabas and S. Shima 2017 to check radius
- growth due to condensation is correct '''
+ growth due to condensation is correct"""
fig, axs = plt.subplots(nrows=1, ncols=3, figsize=(12, 5))
- plot_kohlercurve_with_criticalpoints(axs[1], radius, msol[0],
- temp[0], IONIC, MR_SOL)
+ plot_kohlercurve_with_criticalpoints(
+ axs[1], radius, msol[0], temp[0], IONIC, MR_SOL
+ )
axs = condensation_validation_subplots(axs, time, radius, supersat, zprof)
axs[0].legend(loc="lower right")
axs[1].legend(loc="upper left")
- textlab = "N = "+str(numconc)+"cm$^{-3}$\n" +\
- "r$_{dry}$ = "+"{:.2g}\u03BCm\n".format(radius[0]) +\
- " = {:.1f}".format(W_avg*100)+"cm s$^{-1}$"
+ textlab = (
+ "N = "
+ + str(numconc)
+ + "cm$^{-3}$\n"
+ + "r$_{dry}$ = "
+ + "{:.2g}\u03BCm\n".format(radius[0])
+ + " = {:.1f}".format(W_avg * 100)
+ + "cm s$^{-1}$"
+ )
axs[0].text(0.03, 0.82, textlab, transform=axs[0].transAxes)
axs[0].set_xlim([-1, 1])
@@ -133,9 +188,8 @@ def arabas_shima_2017_fig(time, zprof, radius, msol, temp, supersat,
fig.tight_layout()
if savename != "":
- fig.savefig(savename, dpi=400, bbox_inches="tight",
- facecolor='w', format="png")
- print("Figure .png saved as: "+savename)
+ fig.savefig(savename, dpi=400, bbox_inches="tight", facecolor="w", format="png")
+ print("Figure .png saved as: " + savename)
plt.show()
diff --git a/examples/exampleplotting/plotssrc/pltdist.py b/examples/exampleplotting/plotssrc/pltdist.py
index 955db50d6..026d7e2c5 100644
--- a/examples/exampleplotting/plotssrc/pltdist.py
+++ b/examples/exampleplotting/plotssrc/pltdist.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: pltdist.py
Project: plotssrc
@@ -17,7 +17,7 @@
File Description:
mass, number concentration etc. plots against
radius in evenly spaced ln(radius) bins
-'''
+"""
import sys
import numpy as np
@@ -26,86 +26,96 @@
sys.path.append("../../../") # for imports from pySD package
from pySD.sdmout_src import sdtracing
-def gaussian_kernel_smoothing(hist, hcens, sig):
+def gaussian_kernel_smoothing(hist, hcens, sig):
smoothhist = []
for h in range(len(hist)):
- kernel = 1/(np.sqrt(2*np.pi)*sig)*np.exp(-(hcens - hcens[h])**2/(2*sig**2))
- kernel = kernel/np.sum(kernel)
- smoothhist.append(np.sum(hist*kernel))
+ kernel = (
+ 1
+ / (np.sqrt(2 * np.pi) * sig)
+ * np.exp(-((hcens - hcens[h]) ** 2) / (2 * sig**2))
+ )
+ kernel = kernel / np.sum(kernel)
+ smoothhist.append(np.sum(hist * kernel))
smoothhist = np.asarray(smoothhist)
- smoothhist = np.where(smoothhist<1e-16, 0, smoothhist)
+ smoothhist = np.where(smoothhist < 1e-16, 0, smoothhist)
return smoothhist, hcens
-def logr_distribution(rspan, nbins, radius, wghts,
- perlogR=False, smooth=False):
- ''' get distribution of data with weights 'wghts' against
- logr. Uses np.histogram to get frequency of a particular
- value of data that falls in each ln(r) -> ln(r) + dln(r) bin.
- Apply gaussian kernel smoothing if wanted. Note log base e not 10! '''
- # create lnr bins (linearly spaced in lnr)
- hedgs = np.linspace(np.log(rspan[0]), np.log(rspan[1]), nbins+1) # edges to lnr bins
- logrwdths = hedgs[1:]- hedgs[:-1] # lnr bin widths
- hcens = np.log((np.exp(hedgs[1:])+np.exp(hedgs[:-1]))/2) # lnr bin centres
+def logr_distribution(rspan, nbins, radius, wghts, perlogR=False, smooth=False):
+ """get distribution of data with weights 'wghts' against
+ logr. Uses np.histogram to get frequency of a particular
+ value of data that falls in each ln(r) -> ln(r) + dln(r) bin.
+ Apply gaussian kernel smoothing if wanted. Note log base e not 10!"""
- # get number frequency in each bin
- hist, hedgs = np.histogram(np.log(radius), bins=hedgs,
- weights=wghts, density=None)
+ # create lnr bins (linearly spaced in lnr)
+ hedgs = np.linspace(
+ np.log(rspan[0]), np.log(rspan[1]), nbins + 1
+ ) # edges to lnr bins
+ logrwdths = hedgs[1:] - hedgs[:-1] # lnr bin widths
+ hcens = np.log((np.exp(hedgs[1:]) + np.exp(hedgs[:-1])) / 2) # lnr bin centres
- if perlogR == True: # get frequency / bin width
- hist = hist/logrwdths
+ # get number frequency in each bin
+ hist, hedgs = np.histogram(np.log(radius), bins=hedgs, weights=wghts, density=None)
- if smooth:
- hist, hcens = gaussian_kernel_smoothing(hist, hcens, smooth)
+ if perlogR is True: # get frequency / bin width
+ hist = hist / logrwdths
- return hist, np.exp(hedgs), np.exp(hcens) # units of hedgs and hcens [microns]
+ if smooth:
+ hist, hcens = gaussian_kernel_smoothing(hist, hcens, smooth)
-def massdens_distrib(radius, xi, mass, vol, rspan,
- nbins, perlogR, smooth):
+ return hist, np.exp(hedgs), np.exp(hcens) # units of hedgs and hcens [microns]
- weights = xi * mass / vol # real droplets [g/m^3]
+def massdens_distrib(radius, xi, mass, vol, rspan, nbins, perlogR, smooth):
+ weights = xi * mass / vol # real droplets [g/m^3]
- hist, hedges, hcens = logr_distribution(rspan, nbins, radius,
- weights, perlogR=perlogR,
- smooth=smooth)
+ hist, hedges, hcens = logr_distribution(
+ rspan, nbins, radius, weights, perlogR=perlogR, smooth=smooth
+ )
- return hcens, hist
+ return hcens, hist
-def nsupers_distrib(radius, xi, mass, vol, rspan,
- nbins, perlogR, smooth):
- weights = None # number superdroplets []
- hist, hedges, hcens = logr_distribution(rspan, nbins, radius,
- weights, perlogR=perlogR,
- smooth=smooth)
+def nsupers_distrib(radius, xi, mass, vol, rspan, nbins, perlogR, smooth):
+ weights = None # number superdroplets []
+ hist, hedges, hcens = logr_distribution(
+ rspan, nbins, radius, weights, perlogR=perlogR, smooth=smooth
+ )
- return hcens, hist
+ return hcens, hist
-def numconc_distrib(radius, xi, mass, vol, rspan,
- nbins, perlogR, smooth):
+def numconc_distrib(radius, xi, mass, vol, rspan, nbins, perlogR, smooth):
+ weights = xi / vol / 1e6 # real droplets [/cm^3]
- weights = xi / vol / 1e6 # real droplets [/cm^3]
+ hist, hedges, hcens = logr_distribution(
+ rspan, nbins, radius, weights, perlogR=perlogR, smooth=smooth
+ )
- hist, hedges, hcens = logr_distribution(rspan, nbins, radius,
- weights, perlogR=perlogR,
- smooth=smooth)
+ return hcens, hist
- return hcens, hist
-
-def plot_dists(ax, distribcalc, timesecs, data2plt, t2plts,
- vol, rspan, nbins, masscalc=None,
- smoothsig=False, perlogR=True, ylog=False):
+def plot_dists(
+ ax,
+ distribcalc,
+ timesecs,
+ data2plt,
+ t2plts,
+ vol,
+ rspan,
+ nbins,
+ masscalc=None,
+ smoothsig=False,
+ perlogR=True,
+ ylog=False,
+):
for t, tplt in enumerate(t2plts):
-
- ind = np.argmin(abs(timesecs-tplt))
- tlab = 't = {:.2f}s'.format(timesecs[ind])
- c = 'C'+str(t)
+ ind = np.argmin(abs(timesecs - tplt))
+ tlab = "t = {:.2f}s".format(timesecs[ind])
+ c = "C" + str(t)
radius = data2plt["radius"][t]
xi = data2plt["xi"][t]
@@ -113,123 +123,164 @@ def plot_dists(ax, distribcalc, timesecs, data2plt, t2plts,
msol = data2plt["msol"][t]
mass = masscalc(radius, msol)
else:
- mass = None
+ mass = None
- hcens, hist = distribcalc(radius, xi, mass, vol,
- rspan, nbins,
- perlogR=perlogR,
- smooth=smoothsig)
+ hcens, hist = distribcalc(
+ radius, xi, mass, vol, rspan, nbins, perlogR=perlogR, smooth=smoothsig
+ )
if smoothsig:
ax.plot(hcens, hist, label=tlab, color=c)
else:
- ax.step(hcens, hist, label=tlab, where='mid', color=c)
+ ax.step(hcens, hist, label=tlab, where="mid", color=c)
if ylog:
- ax.set_yscale("log")
+ ax.set_yscale("log")
ax.set_xscale("log")
ax.set_xlabel("radius, r, /\u03BCm")
ax.legend()
return ax
-def plot_domainmass_distribs(timesecs, sddata, t2plts,
- domainvol, rspan, nbins,
- smoothsig=False,
- perlogR=True,
- ylog=False,
- savename=""):
-
- fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(8,6))
-
- attrs2sel = ["radius", "xi", "msol"]
- data2plt = sdtracing.attributes_at_times(sddata, timesecs,
- t2plts, attrs2sel)
-
- plot_dists(ax, massdens_distrib, timesecs,
- data2plt, t2plts,
- domainvol, rspan, nbins, sddata.mass,
- smoothsig=smoothsig, perlogR=perlogR, ylog=ylog)
-
- if perlogR:
- ax.set_ylabel("droplet mass distribution,\n g(lnR) /g m$^{-3}$ / unit lnR")
- else:
- ax.set_ylabel("droplet mass distribution,\n M(lnR) /g m$^{-3}$")
-
- fig.tight_layout()
-
- if savename != "":
- fig.savefig(savename, dpi=400, bbox_inches="tight",
- facecolor='w', format="png")
- print("Figure .png saved as: "+savename)
-
- plt.show()
-
- return fig, ax
-
-def plot_domainnsupers_distribs(timesecs, sddata, t2plts,
- domainvol, rspan, nbins,
- smoothsig=False,
- perlogR=True,
- ylog=False,
- savename=""):
-
- fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(8,6))
-
- attrs2sel = ["radius", "xi", "msol"]
- data2plt = sdtracing.attributes_at_times(sddata, timesecs,
- t2plts, attrs2sel)
-
- plot_dists(ax, nsupers_distrib, timesecs,
- data2plt, t2plts,
- domainvol, rspan, nbins, None,
- smoothsig=smoothsig, perlogR=perlogR, ylog=ylog)
-
- if perlogR:
- ax.set_ylabel("number of superdroplets / unit lnR")
- else:
- ax.set_ylabel("number of superdroplets")
-
- fig.tight_layout()
-
- if savename != "":
- fig.savefig(savename, dpi=400, bbox_inches="tight",
- facecolor='w', format="png")
- print("Figure .png saved as: "+savename)
-
- plt.show()
-
- return fig, ax
-
-def plot_domainnumconc_distribs(timesecs, sddata, t2plts,
- domainvol, rspan, nbins,
- smoothsig=False,
- perlogR=True,
- ylog=False,
- savename=""):
-
- fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(8,6))
-
- attrs2sel = ["radius", "xi", "msol"]
- data2plt = sdtracing.attributes_at_times(sddata, timesecs,
- t2plts, attrs2sel)
-
- plot_dists(ax, numconc_distrib, timesecs,
- data2plt, t2plts,
- domainvol, rspan, nbins, None,
- smoothsig=smoothsig, perlogR=perlogR, ylog=ylog)
-
- if perlogR:
- ax.set_ylabel("real droplet concentration /cm$^{-3}$ / unit lnR")
- else:
- ax.set_ylabel("real droplet concentration /cm$^{-3}$")
-
- fig.tight_layout()
-
- if savename != "":
- fig.savefig(savename, dpi=400, bbox_inches="tight",
- facecolor='w', format="png")
- print("Figure .png saved as: "+savename)
-
- plt.show()
- return fig, ax
+def plot_domainmass_distribs(
+ timesecs,
+ sddata,
+ t2plts,
+ domainvol,
+ rspan,
+ nbins,
+ smoothsig=False,
+ perlogR=True,
+ ylog=False,
+ savename="",
+):
+ fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(8, 6))
+
+ attrs2sel = ["radius", "xi", "msol"]
+ data2plt = sdtracing.attributes_at_times(sddata, timesecs, t2plts, attrs2sel)
+
+ plot_dists(
+ ax,
+ massdens_distrib,
+ timesecs,
+ data2plt,
+ t2plts,
+ domainvol,
+ rspan,
+ nbins,
+ sddata.mass,
+ smoothsig=smoothsig,
+ perlogR=perlogR,
+ ylog=ylog,
+ )
+
+ if perlogR:
+ ax.set_ylabel("droplet mass distribution,\n g(lnR) /g m$^{-3}$ / unit lnR")
+ else:
+ ax.set_ylabel("droplet mass distribution,\n M(lnR) /g m$^{-3}$")
+
+ fig.tight_layout()
+
+ if savename != "":
+ fig.savefig(savename, dpi=400, bbox_inches="tight", facecolor="w", format="png")
+ print("Figure .png saved as: " + savename)
+
+ plt.show()
+
+ return fig, ax
+
+
+def plot_domainnsupers_distribs(
+ timesecs,
+ sddata,
+ t2plts,
+ domainvol,
+ rspan,
+ nbins,
+ smoothsig=False,
+ perlogR=True,
+ ylog=False,
+ savename="",
+):
+ fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(8, 6))
+
+ attrs2sel = ["radius", "xi", "msol"]
+ data2plt = sdtracing.attributes_at_times(sddata, timesecs, t2plts, attrs2sel)
+
+ plot_dists(
+ ax,
+ nsupers_distrib,
+ timesecs,
+ data2plt,
+ t2plts,
+ domainvol,
+ rspan,
+ nbins,
+ None,
+ smoothsig=smoothsig,
+ perlogR=perlogR,
+ ylog=ylog,
+ )
+
+ if perlogR:
+ ax.set_ylabel("number of superdroplets / unit lnR")
+ else:
+ ax.set_ylabel("number of superdroplets")
+
+ fig.tight_layout()
+
+ if savename != "":
+ fig.savefig(savename, dpi=400, bbox_inches="tight", facecolor="w", format="png")
+ print("Figure .png saved as: " + savename)
+
+ plt.show()
+
+ return fig, ax
+
+
+def plot_domainnumconc_distribs(
+ timesecs,
+ sddata,
+ t2plts,
+ domainvol,
+ rspan,
+ nbins,
+ smoothsig=False,
+ perlogR=True,
+ ylog=False,
+ savename="",
+):
+ fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(8, 6))
+
+ attrs2sel = ["radius", "xi", "msol"]
+ data2plt = sdtracing.attributes_at_times(sddata, timesecs, t2plts, attrs2sel)
+
+ plot_dists(
+ ax,
+ numconc_distrib,
+ timesecs,
+ data2plt,
+ t2plts,
+ domainvol,
+ rspan,
+ nbins,
+ None,
+ smoothsig=smoothsig,
+ perlogR=perlogR,
+ ylog=ylog,
+ )
+
+ if perlogR:
+ ax.set_ylabel("real droplet concentration /cm$^{-3}$ / unit lnR")
+ else:
+ ax.set_ylabel("real droplet concentration /cm$^{-3}$")
+
+ fig.tight_layout()
+
+ if savename != "":
+ fig.savefig(savename, dpi=400, bbox_inches="tight", facecolor="w", format="png")
+ print("Figure .png saved as: " + savename)
+
+ plt.show()
+
+ return fig, ax
diff --git a/examples/exampleplotting/plotssrc/pltmoms.py b/examples/exampleplotting/plotssrc/pltmoms.py
index 497e3afb0..1098000e4 100644
--- a/examples/exampleplotting/plotssrc/pltmoms.py
+++ b/examples/exampleplotting/plotssrc/pltmoms.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: pltmoms.py
Project: plotssrc
@@ -16,57 +16,55 @@
-----
File Description:
examples for ploting momments of the droplet distirbution
-'''
+"""
import numpy as np
import matplotlib.pyplot as plt
+
def plot_totnsupers(time, totnsupers, savename=""):
+ fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8, 5), sharex=True)
- fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8,5), sharex=True)
+ ax.plot(time.mins, totnsupers)
- ax.plot(time.mins, totnsupers)
+ ax.set_ylabel("domain total number of superdroplets")
+ ax.set_xlabel("time /min")
- ax.set_ylabel("domain total number of superdroplets")
- ax.set_xlabel("time /min")
+ fig.tight_layout()
+ if savename != "":
+ fig.savefig(savename, dpi=400, bbox_inches="tight", facecolor="w", format="png")
+ print("Figure .png saved as: " + savename)
- fig.tight_layout()
- if savename != "":
- fig.savefig(savename, dpi=400, bbox_inches="tight",
- facecolor='w', format="png")
- print("Figure .png saved as: "+savename)
+ plt.show()
- plt.show()
def plot_domainmassmoments(time, massmoms, savename=""):
+ def totmassmom(massmom):
+ """mass moment summed over entire domain"""
+ return np.sum(massmom, axis=(1, 2, 3))
- def totmassmom(massmom):
- '''mass moment summed over entire domain'''
- return np.sum(massmom, axis=(1,2,3))
-
- fig, axs = plt.subplots(nrows=5, ncols=1, figsize=(6,8), sharex=True)
- fig.suptitle("Total Mass Moments Over Domain")
+ fig, axs = plt.subplots(nrows=5, ncols=1, figsize=(6, 8), sharex=True)
+ fig.suptitle("Total Mass Moments Over Domain")
- axs[0].plot(time.mins, totmassmom(massmoms.nsupers))
- axs[1].plot(time.mins, totmassmom(massmoms.mom0))
- axs[2].plot(time.mins, totmassmom(massmoms.mom1))
- axs[3].plot(time.mins, totmassmom(massmoms.mom2))
- meaneffmass = np.mean((massmoms.effmass), axis=(1,2,3))
- axs[4].plot(time.mins, meaneffmass)
+ axs[0].plot(time.mins, totmassmom(massmoms.nsupers))
+ axs[1].plot(time.mins, totmassmom(massmoms.mom0))
+ axs[2].plot(time.mins, totmassmom(massmoms.mom1))
+ axs[3].plot(time.mins, totmassmom(massmoms.mom2))
+ meaneffmass = np.mean((massmoms.effmass), axis=(1, 2, 3))
+ axs[4].plot(time.mins, meaneffmass)
- axs[0].set_ylabel("number of\nsuperdroplets")
- axs[1].set_ylabel("$\u03BB^{m}_{0}$, number\nof droplets")
- axs[2].set_ylabel("$\u03BB^{m}_{1}$, droplet\nmass /g")
- axs[3].set_ylabel("$\u03BB^{m}_{2}$\n~reflectivity /g$^2$")
- ylab4 = "mean effective\ndroplet mass,\n<$\u03BB^{m}_{2}$/$\u03BB^{m}_{1}>$ /g"
- axs[4].set_ylabel(ylab4)
+ axs[0].set_ylabel("number of\nsuperdroplets")
+ axs[1].set_ylabel("$\u03BB^{m}_{0}$, number\nof droplets")
+ axs[2].set_ylabel("$\u03BB^{m}_{1}$, droplet\nmass /g")
+ axs[3].set_ylabel("$\u03BB^{m}_{2}$\n~reflectivity /g$^2$")
+ ylab4 = "mean effective\ndroplet mass,\n<$\u03BB^{m}_{2}$/$\u03BB^{m}_{1}>$ /g"
+ axs[4].set_ylabel(ylab4)
- axs[-1].set_xlabel("time /min")
+ axs[-1].set_xlabel("time /min")
- fig.tight_layout()
- if savename != "":
- fig.savefig(savename, dpi=400, bbox_inches="tight",
- facecolor='w', format="png")
- print("Figure .png saved as: "+savename)
+ fig.tight_layout()
+ if savename != "":
+ fig.savefig(savename, dpi=400, bbox_inches="tight", facecolor="w", format="png")
+ print("Figure .png saved as: " + savename)
- plt.show()
+ plt.show()
diff --git a/examples/exampleplotting/plotssrc/pltsds.py b/examples/exampleplotting/plotssrc/pltsds.py
index 79bcfcf7f..a04c2923a 100644
--- a/examples/exampleplotting/plotssrc/pltsds.py
+++ b/examples/exampleplotting/plotssrc/pltsds.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: pltsds.py
Project: plotssrc
@@ -6,7 +6,7 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Friday 3rd May 2024
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
@@ -16,7 +16,7 @@
-----
File Description:
examples for plotting individual superdroplets
-'''
+"""
import sys
import numpy as np
@@ -27,153 +27,162 @@
sys.path.append("../../../") # for imports from pySD package
from pySD.sdmout_src import sdtracing
+
def maxmin_radii_label(r, allradii):
- ''' if radius, r, is biggest or smallest
- out of allradii, return appropraite label'''
+ """if radius, r, is biggest or smallest
+ out of allradii, return appropraite label"""
+
+ label = None
+ if r == np.amin(allradii):
+ label = "{:.2g}\u03BCm".format(r)
+ label = "min r0 = " + label
+ elif r == np.amax(allradii):
+ label = "{:.2g}\u03BCm".format(r)
+ label = "max r0 = " + label
- label = None
- if r == np.amin(allradii):
- label = '{:.2g}\u03BCm'.format(r)
- label = 'min r0 = '+label
- elif r == np.amax(allradii):
- label = '{:.2g}\u03BCm'.format(r)
- label = 'max r0 = '+label
+ return label
- return label
def individ_radiusgrowths_figure(time, radii, savename=""):
- ''' plots of droplet radii growth given array of radii
- of shape [time, SDs] '''
-
- fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(10, 8))
-
- for i in range(radii.shape[1]):
- label = maxmin_radii_label(radii[0,i], radii[0,:])
- ax.plot(time, radii[:,i], linewidth=0.8, label=label)
-
- ax.set_xlabel('time /s')
- ax.set_yscale('log')
- ax.legend(fontsize=13)
-
- ax.set_ylabel('droplet radius /\u03BCm')
-
- fig.tight_layout()
-
- if savename != "":
- fig.savefig(savename, dpi=400,
- bbox_inches="tight", facecolor='w', format="png")
- print("Figure .png saved as: "+savename)
-
- plt.show()
-
- return fig, ax
-
-def plot_randomsample_superdrops(time, sddata, totnsupers,
- nsample, savename=""):
- ''' plot timeseries of the attributes of a
- random sample of superdroplets '''
-
- fig, axs = plt.subplots(nrows=2, ncols=3, figsize=(10,6))
- fig.suptitle("Time Evolution of a Random Sample of Superdroplets")
-
- minid, maxid = 0, int(totnsupers) # largest value of ids to sample
- ids2plot = random.sample(list(range(minid, maxid, 1)), nsample)
-
- attrs = ["radius", "xi", "msol"]
- for a, attr in enumerate(attrs):
- try:
- data = sdtracing.attribute_for_superdroplets_sample(sddata,
- attr,
- ids=ids2plot)
- axs[0, a].plot(time.mins, data, linewidth=0.8)
- except:
- print("WARNING: didn't plot "+attr)
-
- mks = MarkerStyle('o', fillstyle='full')
- coords = ["coord3", "coord1", "coord2"]
- for a, coord in enumerate(coords):
- try:
- data = sdtracing.attribute_for_superdroplets_sample(sddata,
- coord,
- ids=ids2plot)
- data = data / 1000 # [km]
- axs[1, a].plot(time.mins, data, linestyle="",
- marker=mks, markersize=0.2)
- except:
- print("WARNING: didn't plot "+coord)
-
- axs[0, 0].set_yscale('log')
- axs[0, 0].set_ylabel('radius, r /\u03BCm')
- axs[0, 1].set_ylabel('multiplicity, \u03BE')
- axs[0, 2].set_ylabel('solute mass, msol /g')
- axs[1, 0].set_ylabel('zcoord /km')
- axs[1, 1].set_ylabel('xcoord /km')
- axs[1, 2].set_ylabel('ycoord /km')
- for ax in axs[1,:]:
- ax.set_xlabel("time /min")
-
- fig.tight_layout()
-
- if savename != "":
- fig.savefig(savename, dpi=400, bbox_inches="tight",
- facecolor='w', format="png")
- print("Figure .png saved as: "+savename)
-
- plt.show()
-
- return fig, axs
-
-
-def plot_randomsample_superdrops_2dmotion(sddata, totnsupers,
- nsample, savename="",
- arrows=False, israndom=True):
- ''' plot timeseries of the attributes of a
- random sample of superdroplets '''
-
- fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(6,6))
-
- minid, maxid = 0, int(totnsupers) # largest value of ids to sample
- if israndom:
- ids2plot = random.sample(list(range(minid, maxid, 1)), nsample)
- else:
- ids2plot = np.linspace(0, maxid-1, nsample, dtype=int)
+ """plots of droplet radii growth given array of radii
+ of shape [time, SDs]"""
+
+ fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(10, 8))
- mks = MarkerStyle('o', fillstyle='full')
- coordz = sdtracing.attribute_for_superdroplets_sample(sddata, "coord3",
- ids=ids2plot) / 1000 # [km]
- coordx = sdtracing.attribute_for_superdroplets_sample(sddata, "coord1",
- ids=ids2plot) / 1000 # [km]
+ for i in range(radii.shape[1]):
+ label = maxmin_radii_label(radii[0, i], radii[0, :])
+ ax.plot(time, radii[:, i], linewidth=0.8, label=label)
- ax.plot(coordx, coordz, linestyle="", marker=mks, markersize=0.4)
+ ax.set_xlabel("time /s")
+ ax.set_yscale("log")
+ ax.legend(fontsize=13)
- if arrows:
- n2plt = min(300, coordx.shape[1])
- drops2arrow = random.sample(list(range(0, coordx.shape[1], 1)),
- n2plt)
- for n in drops2arrow: # must loop over drops to get nice positioning of arrows
- x = coordx[:,n][np.logical_not(np.isnan(coordx[:,n]))]
- z = coordz[:,n][np.logical_not(np.isnan(coordz[:,n]))]
+ ax.set_ylabel("droplet radius /\u03BCm")
- u = np.diff(x)
- w = np.diff(z)
- norm = np.sqrt(u**2+w**2)
- pos_x = x[:-1] + u/2
- pos_z = z[:-1] + w/2
+ fig.tight_layout()
- sl = list(range(0, len(pos_x), 100))
- ax.quiver(pos_x[sl], pos_z[sl], (u/norm)[sl], (w/norm)[sl],
- angles="xy", zorder=5, pivot="mid", scale=50)
+ if savename != "":
+ fig.savefig(savename, dpi=400, bbox_inches="tight", facecolor="w", format="png")
+ print("Figure .png saved as: " + savename)
- ax.set_ylabel('zcoord /km')
- ax.set_xlabel('xcoord /km')
+ plt.show()
- fig.tight_layout()
+ return fig, ax
- if savename != "":
- fig.savefig(savename, dpi=400, bbox_inches="tight",
- facecolor='w', format="png")
- print("Figure .png saved as: "+savename)
- plt.show()
+def plot_randomsample_superdrops(time, sddata, totnsupers, nsample, savename=""):
+ """plot timeseries of the attributes of a
+ random sample of superdroplets"""
+
+ fig, axs = plt.subplots(nrows=2, ncols=3, figsize=(10, 6))
+ fig.suptitle("Time Evolution of a Random Sample of Superdroplets")
+
+ minid, maxid = 0, int(totnsupers) # largest value of ids to sample
+ ids2plot = random.sample(list(range(minid, maxid, 1)), nsample)
- return fig, ax
+ attrs = ["radius", "xi", "msol"]
+ for a, attr in enumerate(attrs):
+ try:
+ data = sdtracing.attribute_for_superdroplets_sample(
+ sddata, attr, ids=ids2plot
+ )
+ axs[0, a].plot(time.mins, data, linewidth=0.8)
+ except IndexError:
+ print("WARNING: didn't plot " + attr)
+
+ mks = MarkerStyle("o", fillstyle="full")
+ coords = ["coord3", "coord1", "coord2"]
+ for a, coord in enumerate(coords):
+ try:
+ data = sdtracing.attribute_for_superdroplets_sample(
+ sddata, coord, ids=ids2plot
+ )
+ data = data / 1000 # [km]
+ axs[1, a].plot(time.mins, data, linestyle="", marker=mks, markersize=0.2)
+ except IndexError:
+ print("WARNING: didn't plot " + coord)
+
+ axs[0, 0].set_yscale("log")
+ axs[0, 0].set_ylabel("radius, r /\u03BCm")
+ axs[0, 1].set_ylabel("multiplicity, \u03BE")
+ axs[0, 2].set_ylabel("solute mass, msol /g")
+ axs[1, 0].set_ylabel("zcoord /km")
+ axs[1, 1].set_ylabel("xcoord /km")
+ axs[1, 2].set_ylabel("ycoord /km")
+ for ax in axs[1, :]:
+ ax.set_xlabel("time /min")
+
+ fig.tight_layout()
+
+ if savename != "":
+ fig.savefig(savename, dpi=400, bbox_inches="tight", facecolor="w", format="png")
+ print("Figure .png saved as: " + savename)
+
+ plt.show()
+
+ return fig, axs
+
+
+def plot_randomsample_superdrops_2dmotion(
+ sddata, totnsupers, nsample, savename="", arrows=False, israndom=True
+):
+ """plot timeseries of the attributes of a
+ random sample of superdroplets"""
+
+ fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(6, 6))
+
+ minid, maxid = 0, int(totnsupers) # largest value of ids to sample
+ if israndom:
+ ids2plot = random.sample(list(range(minid, maxid, 1)), nsample)
+ else:
+ ids2plot = np.linspace(0, maxid - 1, nsample, dtype=int)
+
+ mks = MarkerStyle("o", fillstyle="full")
+ coordz = (
+ sdtracing.attribute_for_superdroplets_sample(sddata, "coord3", ids=ids2plot)
+ / 1000
+ ) # [km]
+ coordx = (
+ sdtracing.attribute_for_superdroplets_sample(sddata, "coord1", ids=ids2plot)
+ / 1000
+ ) # [km]
+
+ ax.plot(coordx, coordz, linestyle="", marker=mks, markersize=0.4)
+
+ if arrows:
+ n2plt = min(300, coordx.shape[1])
+ drops2arrow = random.sample(list(range(0, coordx.shape[1], 1)), n2plt)
+ for n in drops2arrow: # must loop over drops to get nice positioning of arrows
+ x = coordx[:, n][np.logical_not(np.isnan(coordx[:, n]))]
+ z = coordz[:, n][np.logical_not(np.isnan(coordz[:, n]))]
+
+ u = np.diff(x)
+ w = np.diff(z)
+ norm = np.sqrt(u**2 + w**2)
+ pos_x = x[:-1] + u / 2
+ pos_z = z[:-1] + w / 2
+
+ sl = list(range(0, len(pos_x), 100))
+ ax.quiver(
+ pos_x[sl],
+ pos_z[sl],
+ (u / norm)[sl],
+ (w / norm)[sl],
+ angles="xy",
+ zorder=5,
+ pivot="mid",
+ scale=50,
+ )
+
+ ax.set_ylabel("zcoord /km")
+ ax.set_xlabel("xcoord /km")
+
+ fig.tight_layout()
+
+ if savename != "":
+ fig.savefig(savename, dpi=400, bbox_inches="tight", facecolor="w", format="png")
+ print("Figure .png saved as: " + savename)
+
+ plt.show()
+
+ return fig, ax
diff --git a/examples/exampleplotting/plotssrc/shima2009fig.py b/examples/exampleplotting/plotssrc/shima2009fig.py
index 771f9a29c..959bb73f9 100644
--- a/examples/exampleplotting/plotssrc/shima2009fig.py
+++ b/examples/exampleplotting/plotssrc/shima2009fig.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: shima2009fig.py
Project: plotssrc
@@ -17,7 +17,7 @@
File Description:
functions for plotting similar to
figure 2(a) from Shima et al. 2009
-'''
+"""
import sys
import numpy as np
@@ -29,132 +29,149 @@
from pySD.sdmout_src import sdtracing
from .pltdist import logr_distribution
-def plot_validation_figure(witherr, time, sddata, tplt, domainvol,
- n_a, r_a, smoothsig,
- savename="", withgol=False):
- attrs2sel = ["radius", "xi"]
- selsddata = sdtracing.attributes_at_times(sddata, time, tplt, attrs2sel)
+def plot_validation_figure(
+ witherr,
+ time,
+ sddata,
+ tplt,
+ domainvol,
+ n_a,
+ r_a,
+ smoothsig,
+ savename="",
+ withgol=False,
+):
+ attrs2sel = ["radius", "xi"]
+ selsddata = sdtracing.attributes_at_times(sddata, time, tplt, attrs2sel)
- nbins = 500
- non_nanradius = ak.nan_to_none(sddata["radius"])
- rspan = [ak.min(non_nanradius), ak.max(non_nanradius)]
+ nbins = 500
+ non_nanradius = ak.nan_to_none(sddata["radius"])
+ rspan = [ak.min(non_nanradius), ak.max(non_nanradius)]
- fig, ax, ax_err = setup_validation_figure(witherr=witherr)
+ fig, ax, ax_err = setup_validation_figure(witherr=witherr)
- for n in range(len(tplt)):
- ind = np.argmin(abs(time-tplt[n]))
- tlab = 't = {:.2f}s'.format(time[ind])
- c = 'C'+str(n)
+ for n in range(len(tplt)):
+ ind = np.argmin(abs(time - tplt[n]))
+ tlab = "t = {:.2f}s".format(time[ind])
+ c = "C" + str(n)
- if withgol:
- golsol, hcens = golovin_analytical(rspan, time[ind], nbins,
- n_a, r_a, sddata.RHO_L)
- plot_golovin_analytical_solution(ax, hcens, golsol, n, c)
+ if withgol:
+ golsol, hcens = golovin_analytical(
+ rspan, time[ind], nbins, n_a, r_a, sddata.RHO_L
+ )
+ plot_golovin_analytical_solution(ax, hcens, golsol, n, c)
- radius = selsddata["radius"][n]
- xi = selsddata["xi"][n]
- hist, hcens = plot_massdens_distrib(ax, rspan, nbins, domainvol,
- xi, radius, sddata, smoothsig, tlab, c)
+ radius = selsddata["radius"][n]
+ xi = selsddata["xi"][n]
+ hist, hcens = plot_massdens_distrib(
+ ax, rspan, nbins, domainvol, xi, radius, sddata, smoothsig, tlab, c
+ )
- if witherr:
- golsol, hcens = golovin_analytical(rspan, time[ind], nbins,
- n_a, r_a, sddata.RHO_L)
- diff = (hist - golsol)
- ax_err.plot(hcens, diff, c=c)
+ if witherr:
+ golsol, hcens = golovin_analytical(
+ rspan, time[ind], nbins, n_a, r_a, sddata.RHO_L
+ )
+ diff = hist - golsol
+ ax_err.plot(hcens, diff, c=c)
+
+ ax.legend()
- ax.legend()
+ fig.tight_layout()
- fig.tight_layout()
+ if savename != "":
+ fig.savefig(savename, dpi=400, bbox_inches="tight", facecolor="w", format="png")
+ print("Figure .png saved as: " + savename)
+ plt.show()
- if savename != "":
- fig.savefig(savename, dpi=400,
- bbox_inches="tight", facecolor='w', format="png")
- print("Figure .png saved as: "+savename)
- plt.show()
+ return fig, ax
- return fig, ax
def setup_validation_figure(witherr):
+ if witherr:
+ gd = dict(height_ratios=[5, 1])
+ fig, [ax, ax_err] = plt.subplots(
+ ncols=1, nrows=2, figsize=(8, 7), gridspec_kw=gd
+ )
+ else:
+ fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(8, 6))
- if witherr:
- gd = dict(height_ratios=[5,1])
- fig, [ax, ax_err] = plt.subplots(ncols=1, nrows=2,
- figsize=(8,7), gridspec_kw=gd)
- else:
- fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(8,6))
+ xlims = [10, 5000]
+ ax.set_xscale("log")
+ ax.set_xlim(xlims)
+ ax.set_xlabel("radius, r, /\u03BCm")
- xlims = [10, 5000]
- ax.set_xscale("log")
- ax.set_xlim(xlims)
- ax.set_xlabel("radius, r, /\u03BCm")
+ ax.set_ylabel("mass density distribution,\n g(lnR) /g m$^{-3}$ / unit lnR")
+ ax.set_ylim([0, 1.8])
- ax.set_ylabel("mass density distribution,\n g(lnR) /g m$^{-3}$ / unit lnR")
- ax.set_ylim([0,1.8])
+ if witherr:
+ ax_err.set_xscale("log")
+ ax_err.set_xlim(xlims)
+ ax_err.set_xlabel("radius, r, /\u03BCm")
+ ax.set_xlabel(None)
+ ax.set_xticklabels([])
- if witherr:
- ax_err.set_xscale("log")
- ax_err.set_xlim(xlims)
- ax_err.set_xlabel("radius, r, /\u03BCm")
- ax.set_xlabel(None)
- ax.set_xticklabels([])
+ ax_err.set_ylabel("error /g m$^{-3}$\n/ unit lnR")
+ ax_err.set_ylim([-0.25, 0.25])
- ax_err.set_ylabel("error /g m$^{-3}$\n/ unit lnR")
- ax_err.set_ylim([-0.25,0.25])
+ return fig, ax, ax_err
- return fig, ax, ax_err
+ else:
+ return fig, ax, []
- else:
- return fig, ax, []
def golovin_analytical(rspan, t, nbins, n_a, r_a, RHO_L):
+ b = 1500
+ rspan = [r / 1e6 for r in rspan] # convert from microns to m
- b = 1500
- rspan = [r/1e6 for r in rspan] # convert from microns to m
+ if t < 1e-16:
+ t = 1e-10
- if t < 1e-16:
- t=1e-10
+ hedgs = np.linspace(
+ np.log(rspan[0]), np.log(rspan[1]), nbins + 1
+ ) # edges to lnr bins
+ hcens = (hedgs[1:] + hedgs[:-1]) / 2 # lnr bin centres
+ r = np.exp(hcens)
- hedgs = np.linspace(np.log(rspan[0]), np.log(rspan[1]), nbins+1) # edges to lnr bins
- hcens = (hedgs[1:]+hedgs[:-1])/2 # lnr bin centres
- r = np.exp(hcens)
+ vol_a = 4 / 3 * np.pi * r_a**3
+ x = 4 / 3 * np.pi * r**3 / vol_a
+ tau = 1 - np.exp(-b * n_a * vol_a * t)
+ bsl_exp = iv(1, 2 * x * np.sqrt(tau)) * np.exp(-(1 + tau) * x)
+ asym = 1 / (2 * np.sqrt(np.pi * x)) * np.exp(x * (2 * np.sqrt(tau) - 1 - tau))
+ np.nan_to_num(bsl_exp, copy=False, nan=asym, posinf=asym, neginf=np.inf)
- vol_a = 4/3*np.pi*r_a**3
- x = 4/3*np.pi*r**3/vol_a
- tau = 1-np.exp(-b*n_a*vol_a*t)
- bsl_exp = iv(1, 2*x*np.sqrt(tau))*np.exp(-(1+tau)*x)
- asym = 1/(2*np.sqrt(np.pi*x))*np.exp(x*(2*np.sqrt(tau)-1-tau))
- np.nan_to_num(bsl_exp, copy=False, nan=asym, posinf=asym, neginf=np.inf)
+ phi = (1 - tau) / (x * np.sqrt(tau)) * bsl_exp
+ n = n_a / vol_a * phi
+ # dv_dlnR = 4/3*np.pi*((rwdths[1:])**3-(rwdths[:-1])**3)/hwdths
+ dv_dlnR = 3 * (4 / 3 * np.pi * r**3)
+ massdens = (
+ n * RHO_L * (4 / 3 * np.pi * r**3) * dv_dlnR
+ ) # mass density as if water [Kg m^-3 /unit lnR]]
- phi = (1-tau)/(x*np.sqrt(tau))*bsl_exp
- n = n_a/vol_a*phi
- #dv_dlnR = 4/3*np.pi*((rwdths[1:])**3-(rwdths[:-1])**3)/hwdths
- dv_dlnR = 3*(4/3*np.pi*r**3)
- massdens = n*RHO_L*(4/3*np.pi*r**3)*dv_dlnR # mass density as if water [Kg m^-3 /unit lnR]]
+ return massdens * 1000, r * 1e6 # units: [g m^-3 /unit lnR] , [microns]
- return massdens*1000, r*1e6 # units: [g m^-3 /unit lnR] , [microns]
def plot_golovin_analytical_solution(ax, hcens, golsol, n, c):
+ if n == 0:
+ # add legend to analytical solution
+ glab = "analytical solution"
+ ax.plot(hcens, golsol, label=glab, color="k", linestyle="--")
- if n==0:
- # add legend to analytical solution
- glab = "analytical solution"
- ax.plot(hcens, golsol, label=glab, color='k', linestyle='--')
-
- ax.plot(hcens, golsol, label=None, color=c, linestyle='--')
+ ax.plot(hcens, golsol, label=None, color=c, linestyle="--")
- return ax
+ return ax
-def plot_massdens_distrib(ax, rspan, nbins, domainvol,
- xi, radius, sddata, smoothsig, tlab, c):
- m_asif_water = sddata.vol(radius) * sddata.RHO_L # superdrops mass as if water [g]
- weights = xi * m_asif_water * 1000 / domainvol # real droplets [g/m^3]
+def plot_massdens_distrib(
+ ax, rspan, nbins, domainvol, xi, radius, sddata, smoothsig, tlab, c
+):
+ m_asif_water = sddata.vol(radius) * sddata.RHO_L # superdrops mass as if water [g]
+ weights = xi * m_asif_water * 1000 / domainvol # real droplets [g/m^3]
- hist, hedges, hcens = logr_distribution(rspan, nbins, radius,
- weights, perlogR=True,
- smooth=smoothsig)
+ hist, hedges, hcens = logr_distribution(
+ rspan, nbins, radius, weights, perlogR=True, smooth=smoothsig
+ )
- ax.plot(hcens, hist, label=tlab, color=c)
+ ax.plot(hcens, hist, label=tlab, color=c)
- return hist, hcens
+ return hist, hcens
diff --git a/examples/rainshaft1d/rainshaft1d.py b/examples/rainshaft1d/rainshaft1d.py
index 7e098cbce..20cd7e9dd 100644
--- a/examples/rainshaft1d/rainshaft1d.py
+++ b/examples/rainshaft1d/rainshaft1d.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: rainshaft1d.py
Project: rainshaft1d
@@ -18,7 +18,7 @@
Script generates input files, then runs CLEO executable "rshaft1D" to create the
data which is then plotted to demonstrate precipitation example in 1-D rainshaft
with constant thermodynamics read from a file.
-'''
+"""
import os
import sys
@@ -31,13 +31,15 @@
configfile = sys.argv[3]
sys.path.append(path2CLEO) # for imports from pySD package
-sys.path.append(path2CLEO+"/examples/exampleplotting/") # for imports from example plotting package
+sys.path.append(
+ path2CLEO + "/examples/exampleplotting/"
+) # for imports from example plotting package
from plotssrc import pltsds, pltmoms, animations
-from pySD.sdmout_src import *
+from pySD.sdmout_src import pyzarr, pysetuptxt, pygbxsdat
from pySD.gbxboundariesbinary_src import read_gbxboundaries as rgrid
from pySD.gbxboundariesbinary_src import create_gbxboundaries as cgrid
-from pySD.initsuperdropsbinary_src import *
+from pySD.initsuperdropsbinary_src import crdgens, rgens, dryrgens, probdists, attrsgen
from pySD.initsuperdropsbinary_src import create_initsuperdrops as csupers
from pySD.initsuperdropsbinary_src import read_initsuperdrops as rsupers
from pySD.thermobinary_src import thermogen
@@ -49,52 +51,54 @@
### ---------------------------------------------------------------- ###
### --- essential paths and filenames --- ###
# path and filenames for creating initial SD conditions
-constsfile = path2CLEO+"/libs/cleoconstants.hpp"
-binpath = path2build+"/bin/"
-sharepath = path2build+"/share/"
-gridfile = sharepath+"rain1d_dimlessGBxboundaries.dat"
-initSDsfile = sharepath+"rain1d_dimlessSDsinit.dat"
-thermofile = sharepath+"rain1d_dimlessthermo.dat"
+constsfile = path2CLEO + "/libs/cleoconstants.hpp"
+binpath = path2build + "/bin/"
+sharepath = path2build + "/share/"
+gridfile = sharepath + "rain1d_dimlessGBxboundaries.dat"
+initSDsfile = sharepath + "rain1d_dimlessSDsinit.dat"
+thermofile = sharepath + "rain1d_dimlessthermo.dat"
# path and file names for plotting results
-setupfile = binpath+"rain1d_setup.txt"
-dataset = binpath+"rain1d_sol.zarr"
+setupfile = binpath + "rain1d_setup.txt"
+dataset = binpath + "rain1d_sol.zarr"
### --- plotting initialisation figures --- ###
-isfigures = [True, True] # booleans for [making, saving] initialisation figures
-savefigpath = path2build+"/bin/" # directory for saving figures
-SDgbxs2plt = list(range(39, 124))
-SDgbxs2plt = [random.choice(SDgbxs2plt)] # choose random gbx from list to plot
+isfigures = [True, True] # booleans for [making, saving] initialisation figures
+savefigpath = path2build + "/bin/" # directory for saving figures
+SDgbxs2plt = list(range(39, 124))
+SDgbxs2plt = [random.choice(SDgbxs2plt)] # choose random gbx from list to plot
### --- settings for 1-D gridbox boundaries --- ###
-zgrid = [0, 2500, 20] # evenly spaced zhalf coords [zmin, zmax, zdelta] [m]
-xgrid = np.array([0, 20]) # array of xhalf coords [m]
-ygrid = np.array([0, 20]) # array of yhalf coords [m]
+zgrid = [0, 2500, 20] # evenly spaced zhalf coords [zmin, zmax, zdelta] [m]
+xgrid = np.array([0, 20]) # array of xhalf coords [m]
+ygrid = np.array([0, 20]) # array of yhalf coords [m]
### --- settings for 1-D Thermodynamics --- ###
-PRESS0 = 101315 # [Pa]
-TEMP0 = 297.9 # [K]
-qvap0 = 0.016 # [Kg/Kg]
-Zbase = 800 # [m]
-TEMPlapses = [9.8, 6.5] # -dT/dz [K/km]
-qvaplapses = [2.97, "saturated"] # -dvap/dz [g/Kg km^-1]
-qcond = 0.0 # [Kg/Kg]
-WVEL = 4.0 # [m/s]
-Wlength = 1000 # [m] use constant W (Wlength=0.0), or sinusoidal 1-D profile below cloud base
+PRESS0 = 101315 # [Pa]
+TEMP0 = 297.9 # [K]
+qvap0 = 0.016 # [Kg/Kg]
+Zbase = 800 # [m]
+TEMPlapses = [9.8, 6.5] # -dT/dz [K/km]
+qvaplapses = [2.97, "saturated"] # -dvap/dz [g/Kg km^-1]
+qcond = 0.0 # [Kg/Kg]
+WVEL = 4.0 # [m/s]
+Wlength = (
+ 1000 # [m] use constant W (Wlength=0.0), or sinusoidal 1-D profile below cloud base
+)
### --- settings for initial superdroplets --- ###
# initial superdroplet coordinates
-zlim = 800 # min z coord of superdroplets [m]
-npergbx = 256 # number of superdroplets per gridbox
+zlim = 800 # min z coord of superdroplets [m]
+npergbx = 256 # number of superdroplets per gridbox
# initial superdroplet radii (and implicitly solute masses)
-rspan = [3e-9, 5e-5] # min and max range of radii to sample [m]
-dryr_sf = 1.0 # dryradii are 1/sf of radii [m]
+rspan = [3e-9, 5e-5] # min and max range of radii to sample [m]
+dryr_sf = 1.0 # dryradii are 1/sf of radii [m]
# settings for initial superdroplet multiplicies
-geomeans = [0.02e-6, 0.2e-6, 3.5e-6]
-geosigs = [1.55, 2.3, 2]
-scalefacs = [1e6, 0.3e6, 0.025e6]
+geomeans = [0.02e-6, 0.2e-6, 3.5e-6]
+geosigs = [1.55, 2.3, 2]
+scalefacs = [1e6, 0.3e6, 0.025e6]
numconc = np.sum(scalefacs) * 1000
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
@@ -105,58 +109,75 @@
### ---------------------------------------------------------------- ###
### --- ensure build, share and bin directories exist --- ###
if path2CLEO == path2build:
- raise ValueError("build directory cannot be CLEO")
+ raise ValueError("build directory cannot be CLEO")
else:
- Path(path2build).mkdir(exist_ok=True)
- Path(sharepath).mkdir(exist_ok=True)
- Path(binpath).mkdir(exist_ok=True)
+ Path(path2build).mkdir(exist_ok=True)
+ Path(sharepath).mkdir(exist_ok=True)
+ Path(binpath).mkdir(exist_ok=True)
### --- delete any existing initial conditions --- ###
-os.system("rm "+gridfile)
-os.system("rm "+initSDsfile)
-os.system("rm "+thermofile[:-4]+"*")
+os.system("rm " + gridfile)
+os.system("rm " + initSDsfile)
+os.system("rm " + thermofile[:-4] + "*")
### ----- write gridbox boundaries binary ----- ###
cgrid.write_gridboxboundaries_binary(gridfile, zgrid, xgrid, ygrid, constsfile)
rgrid.print_domain_info(constsfile, gridfile)
### ----- write thermodynamics binaries ----- ###
-thermodyngen = thermogen.ConstHydrostaticLapseRates(configfile, constsfile,
- PRESS0, TEMP0, qvap0,
- Zbase, TEMPlapses,
- qvaplapses, qcond,
- WVEL, None, None,
- Wlength)
-cthermo.write_thermodynamics_binary(thermofile, thermodyngen, configfile,
- constsfile, gridfile)
+thermodyngen = thermogen.ConstHydrostaticLapseRates(
+ configfile,
+ constsfile,
+ PRESS0,
+ TEMP0,
+ qvap0,
+ Zbase,
+ TEMPlapses,
+ qvaplapses,
+ qcond,
+ WVEL,
+ None,
+ None,
+ Wlength,
+)
+cthermo.write_thermodynamics_binary(
+ thermofile, thermodyngen, configfile, constsfile, gridfile
+)
### ----- write initial superdroplets binary ----- ###
nsupers = crdgens.nsupers_at_domain_top(gridfile, constsfile, npergbx, zlim)
-coord3gen = crdgens.SampleCoordGen(True) # sample coord3 randomly
-coord1gen = None # do not generate superdroplet coord2s
-coord2gen = None # do not generate superdroplet coord2s
+coord3gen = crdgens.SampleCoordGen(True) # sample coord3 randomly
+coord1gen = None # do not generate superdroplet coord2s
+coord2gen = None # do not generate superdroplet coord2s
xiprobdist = probdists.LnNormal(geomeans, geosigs, scalefacs)
radiigen = rgens.SampleLog10RadiiGen(rspan)
-dryradiigen = dryrgens.ScaledRadiiGen(dryr_sf)
+dryradiigen = dryrgens.ScaledRadiiGen(dryr_sf)
-initattrsgen = attrsgen.AttrsGenerator(radiigen, dryradiigen, xiprobdist,
- coord3gen, coord1gen, coord2gen)
-csupers.write_initsuperdrops_binary(initSDsfile, initattrsgen,
- configfile, constsfile,
- gridfile, nsupers, numconc)
+initattrsgen = attrsgen.AttrsGenerator(
+ radiigen, dryradiigen, xiprobdist, coord3gen, coord1gen, coord2gen
+)
+csupers.write_initsuperdrops_binary(
+ initSDsfile, initattrsgen, configfile, constsfile, gridfile, nsupers, numconc
+)
### ----- show (and save) plots of binary file data ----- ###
if isfigures[0]:
- if isfigures[1]:
- Path(savefigpath).mkdir(exist_ok=True)
- rgrid.plot_gridboxboundaries(constsfile, gridfile,
- savefigpath, isfigures[1])
- rthermo.plot_thermodynamics(constsfile, configfile, gridfile,
- thermofile, savefigpath, isfigures[1])
- rsupers.plot_initGBxs_distribs(configfile, constsfile, initSDsfile,
- gridfile, savefigpath, isfigures[1],
- SDgbxs2plt)
+ if isfigures[1]:
+ Path(savefigpath).mkdir(exist_ok=True)
+ rgrid.plot_gridboxboundaries(constsfile, gridfile, savefigpath, isfigures[1])
+ rthermo.plot_thermodynamics(
+ constsfile, configfile, gridfile, thermofile, savefigpath, isfigures[1]
+ )
+ rsupers.plot_initGBxs_distribs(
+ configfile,
+ constsfile,
+ initSDsfile,
+ gridfile,
+ savefigpath,
+ isfigures[1],
+ SDgbxs2plt,
+ )
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
@@ -164,12 +185,12 @@
### ---------------------- RUN CLEO EXECUTABLE --------------------- ###
### ---------------------------------------------------------------- ###
os.chdir(path2build)
-os.system('pwd')
-os.system('rm -rf '+dataset) # delete any existing dataset
-executable = path2build+'/examples/rainshaft1d/src/rshaft1D'
-print('Executable: '+executable)
-print('Config file: '+configfile)
-os.system(executable + ' ' + configfile)
+os.system("pwd")
+os.system("rm -rf " + dataset) # delete any existing dataset
+executable = path2build + "/examples/rainshaft1d/src/rshaft1D"
+print("Executable: " + executable)
+print("Config file: " + configfile)
+os.system(executable + " " + configfile)
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
@@ -195,42 +216,65 @@
nsample = 500
savename = savefigpath + "rain1d_randomsample.png"
-pltsds.plot_randomsample_superdrops(time, sddata,
- config["maxnsupers"],
- nsample,
- savename=savename)
+pltsds.plot_randomsample_superdrops(
+ time, sddata, config["maxnsupers"], nsample, savename=savename
+)
### ----- plot 1-D .gif animations ----- ###
nframes = len(time.mins)
-mom2ani = np.sum(massmoms.nsupers, axis=(1,2))
+mom2ani = np.sum(massmoms.nsupers, axis=(1, 2))
xlims = [0, np.amax(mom2ani)]
xlabel = "number of super-droplets"
-savename=savefigpath+"rain1d_nsupers1d"
-animations.animate1dprofile(gbxs, mom2ani, time.mins, nframes,
- xlabel=xlabel, xlims=xlims,
- color="green", saveani=True,
- savename=savename, fps=5)
+savename = savefigpath + "rain1d_nsupers1d"
+animations.animate1dprofile(
+ gbxs,
+ mom2ani,
+ time.mins,
+ nframes,
+ xlabel=xlabel,
+ xlims=xlims,
+ color="green",
+ saveani=True,
+ savename=savename,
+ fps=5,
+)
nframes = len(time.mins)
-norm = gbxs["gbxvols"] * 1e6 # volume [cm^3]
-mom2ani = np.sum(massmoms.mom0 / norm[None,:], axis=(1,2))
+norm = gbxs["gbxvols"] * 1e6 # volume [cm^3]
+mom2ani = np.sum(massmoms.mom0 / norm[None, :], axis=(1, 2))
xlims = [0, np.amax(mom2ani)]
xlabel = "number concentration /cm$^{-3}$"
-savename=savefigpath+"rain1d_numconc1d"
-animations.animate1dprofile(gbxs, mom2ani, time.mins, nframes,
- xlabel=xlabel, xlims=xlims,
- color="green", saveani=True,
- savename=savename, fps=5)
+savename = savefigpath + "rain1d_numconc1d"
+animations.animate1dprofile(
+ gbxs,
+ mom2ani,
+ time.mins,
+ nframes,
+ xlabel=xlabel,
+ xlims=xlims,
+ color="green",
+ saveani=True,
+ savename=savename,
+ fps=5,
+)
nframes = len(time.mins)
-norm = gbxs["gbxvols"] # volume [m^3]
-mom2ani = np.sum(massmoms.mom1/ norm[None,:], axis=(1,2))
+norm = gbxs["gbxvols"] # volume [m^3]
+mom2ani = np.sum(massmoms.mom1 / norm[None, :], axis=(1, 2))
xlims = [0, np.amax(mom2ani)]
xlabel = "mass concentration /g m$^{-3}$"
-savename=savefigpath+"rain1d_massconc1d"
-animations.animate1dprofile(gbxs, mom2ani, time.mins, nframes,
- xlabel=xlabel, xlims=xlims,
- color="green", saveani=True,
- savename=savename, fps=5)
+savename = savefigpath + "rain1d_massconc1d"
+animations.animate1dprofile(
+ gbxs,
+ mom2ani,
+ time.mins,
+ nframes,
+ xlabel=xlabel,
+ xlims=xlims,
+ color="green",
+ saveani=True,
+ savename=savename,
+ fps=5,
+)
### ------------------------------------------------------------ ###
### ------------------------------------------------------------ ###
diff --git a/examples/speedtest/speedtest.py b/examples/speedtest/speedtest.py
index b97053a0f..8d4991d1e 100644
--- a/examples/speedtest/speedtest.py
+++ b/examples/speedtest/speedtest.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: speedtest.py
Project: speedtest
@@ -17,7 +17,7 @@
File Description:
Script generates input files, then runs CLEO executable "spdtest" to check performance
of CLEO usign different build configurations (e.g. serial, OpenmP and CUDA parallelism).
-'''
+"""
import os
import sys
@@ -33,13 +33,13 @@
nruns = 2
sys.path.append(path2CLEO) # for imports from pySD package
-sys.path.append(path2CLEO+"/examples/exampleplotting/") # for imports from example plotting package
+sys.path.append(
+ path2CLEO + "/examples/exampleplotting/"
+) # for imports from example plotting package
-from plotssrc import pltsds, pltmoms
-from pySD.sdmout_src import *
from pySD.gbxboundariesbinary_src import read_gbxboundaries as rgrid
from pySD.gbxboundariesbinary_src import create_gbxboundaries as cgrid
-from pySD.initsuperdropsbinary_src import *
+from pySD.initsuperdropsbinary_src import crdgens, rgens, dryrgens, probdists, attrsgen
from pySD.initsuperdropsbinary_src import create_initsuperdrops as csupers
from pySD.initsuperdropsbinary_src import read_initsuperdrops as rsupers
from pySD.thermobinary_src import thermogen
@@ -51,104 +51,105 @@
### ---------------------------------------------------------------- ###
### --- essential paths and filenames --- ###
# path and filenames for creating initial SD conditions
-constsfile = path2CLEO+"/libs/cleoconstants.hpp"
-binpath = path2build+"/bin/"
-sharepath = path2build+"/share/"
-gridfile = sharepath+"/spd_dimlessGBxboundaries.dat"
-initSDsfile = sharepath+"/spd_dimlessSDsinit.dat"
-thermofile = sharepath+"/spd_dimlessthermo.dat"
+constsfile = path2CLEO + "/libs/cleoconstants.hpp"
+binpath = path2build + "/bin/"
+sharepath = path2build + "/share/"
+gridfile = sharepath + "/spd_dimlessGBxboundaries.dat"
+initSDsfile = sharepath + "/spd_dimlessSDsinit.dat"
+thermofile = sharepath + "/spd_dimlessthermo.dat"
# path and file names for plotting results
-setupfile = binpath+"spd_setup.txt"
-statsfile = binpath+"spd_stats.txt"
-dataset = binpath+"spd_sol.zarr"
+setupfile = binpath + "spd_setup.txt"
+statsfile = binpath + "spd_stats.txt"
+dataset = binpath + "spd_sol.zarr"
### --- plotting initialisation figures --- ###
-isfigures = [False, False] # booleans for [making, saving] initialisation figures
-savefigpath = outputdir # directory for saving figures
-SDgbxs2plt = [0] # gbxindex of SDs to plot (nb. "all" can be very slow)
-outdatafile = outputdir+"/spd_allstats.txt" # file to write out stats to
+isfigures = [False, False] # booleans for [making, saving] initialisation figures
+savefigpath = outputdir # directory for saving figures
+SDgbxs2plt = [0] # gbxindex of SDs to plot (nb. "all" can be very slow)
+outdatafile = outputdir + "/spd_allstats.txt" # file to write out stats to
### --- settings for 2-D gridbox boundaries --- ###
-zgrid = [0, 1500, 50] # evenly spaced zhalf coords [zmin, zmax, zdelta] [m]
-xgrid = [0, 1500, 50] # evenly spaced xhalf coords [m]
+zgrid = [0, 1500, 50] # evenly spaced zhalf coords [zmin, zmax, zdelta] [m]
+xgrid = [0, 1500, 50] # evenly spaced xhalf coords [m]
ygrid = np.array([0, 25, 50]) # array of yhalf coords [m]
### --- settings for initial superdroplets --- ###
# settings for initial superdroplet coordinates
-zlim = 1500 # max z coord of superdroplets
-npergbx = 4 # number of superdroplets per gridbox
+zlim = 1500 # max z coord of superdroplets
+npergbx = 4 # number of superdroplets per gridbox
# [min, max] range of initial superdroplet radii (and implicitly solute masses)
-rspan = [3e-9, 3e-6] # [m]
+rspan = [3e-9, 3e-6] # [m]
# settings for initial superdroplet multiplicies
# (from bimodal Lognormal distribution)
-geomeans = [0.02e-6, 0.15e-6]
-geosigs = [1.4, 1.6]
-scalefacs = [6e6, 4e6]
+geomeans = [0.02e-6, 0.15e-6]
+geosigs = [1.4, 1.6]
+scalefacs = [6e6, 4e6]
numconc = np.sum(scalefacs)
### --- settings for 3D Thermodynamics --- ###
-PRESS0 = 100000 # [Pa]
+PRESS0 = 100000 # [Pa]
THETA = 298.15 # [K]
-qcond = 0.0 # [Kg/Kg]
-WMAX = 3.0 # [m/s]
-VVEL = 1.0 # [m/s]
+qcond = 0.0 # [Kg/Kg]
+WMAX = 3.0 # [m/s]
+VVEL = 1.0 # [m/s]
Zlength = 1500 # [m]
Xlength = 1500 # [m]
qvapmethod = "sratio"
-Zbase = 750 # [m]
-sratios = [0.85, 1.1] # s_ratio [below, above] Zbase
+Zbase = 750 # [m]
+sratios = [0.85, 1.1] # s_ratio [below, above] Zbase
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
+
### ---------------------------------------------------------------- ###
### --------------------- FUNCTION DEFINITIONS --------------------- ###
### ---------------------------------------------------------------- ###
def read_statsfile(statsfile):
-
- stats = {}
- with open(statsfile, 'r') as file:
- for line in file:
+ stats = {}
+ with open(statsfile, "r") as file:
+ for line in file:
# Check if the line starts with '###'
- if not line.startswith('###'):
+ if not line.startswith("###"):
# Process the line
line = line.strip().split()
stats[line[0]] = float(line[1])
- return stats
+ return stats
+
def write_outstats(nruns, n, outdatafile, buildtype, stats):
- ''' if outdatafile doesn't already exist, creates new file with
- a header. else appends to end of file '''
+ """if outdatafile doesn't already exist, creates new file with
+ a header. else appends to end of file"""
- try:
- # Try to open the file for exclusive creation
- with open(outdatafile, 'x') as file:
- # Perform operations on the new file if needed
- header = "### Wall Clock time For Timestepping\n"
- header += "### columns are: "
- header += "test_run gpus_cpus/s cpus/s serial/s"
- file.write(header)
- print(f"--- new stats output: '{outdatafile}' created ---")
- except FileExistsError:
- print(f"stats output file '{outdatafile}' already exists")
+ try:
+ # Try to open the file for exclusive creation
+ with open(outdatafile, "x") as file:
+ # Perform operations on the new file if needed
+ header = "### Wall Clock time For Timestepping\n"
+ header += "### columns are: "
+ header += "test_run gpus_cpus/s cpus/s serial/s"
+ file.write(header)
+ print(f"--- new stats output: '{outdatafile}' created ---")
+ except FileExistsError:
+ print(f"stats output file '{outdatafile}' already exists")
- # write new line at number = existing number of (non-header) lines + 1
- with open(outdatafile, 'r') as file:
- lines = file.readlines()
+ # write new line at number = existing number of (non-header) lines + 1
+ with open(outdatafile, "r") as file:
+ lines = file.readlines()
- if buildtype == "cuda":
- line = "\n"+str(n)+" "+str(stats["tstep"])
- with open(outdatafile, 'a') as file:
- file.write(line)
+ if buildtype == "cuda":
+ line = "\n" + str(n) + " " + str(stats["tstep"])
+ with open(outdatafile, "a") as file:
+ file.write(line)
- else:
- nline = len(lines)-nruns+n
- lines[nline] = lines[nline].rstrip() + " "+str(stats["tstep"])+"\n"
- with open(outdatafile, 'w') as file:
- file.writelines(lines)
+ else:
+ nline = len(lines) - nruns + n
+ lines[nline] = lines[nline].rstrip() + " " + str(stats["tstep"]) + "\n"
+ with open(outdatafile, "w") as file:
+ file.writelines(lines)
### ---------------------------------------------------------------- ###
@@ -159,77 +160,95 @@ def write_outstats(nruns, n, outdatafile, buildtype, stats):
### ---------------------------------------------------------------- ###
### --- ensure build, share and bin directories exist --- ###
if path2CLEO == path2build:
- raise ValueError("build directory cannot be CLEO")
+ raise ValueError("build directory cannot be CLEO")
else:
- Path(path2build).mkdir(exist_ok=True)
- Path(sharepath).mkdir(exist_ok=True)
- Path(binpath).mkdir(exist_ok=True)
- if isfigures[1]:
- Path(savefigpath).mkdir(exist_ok=True)
+ Path(path2build).mkdir(exist_ok=True)
+ Path(sharepath).mkdir(exist_ok=True)
+ Path(binpath).mkdir(exist_ok=True)
+ if isfigures[1]:
+ Path(savefigpath).mkdir(exist_ok=True)
### --- delete any existing initial conditions --- ###
-os.system("rm "+gridfile)
-os.system("rm "+initSDsfile)
-os.system("rm "+thermofile[:-4]+"*")
+os.system("rm " + gridfile)
+os.system("rm " + initSDsfile)
+os.system("rm " + thermofile[:-4] + "*")
### ----- write gridbox boundaries binary ----- ###
cgrid.write_gridboxboundaries_binary(gridfile, zgrid, xgrid, ygrid, constsfile)
rgrid.print_domain_info(constsfile, gridfile)
### ----- write thermodynamics binaries ----- ###
-thermodyngen = thermogen.SimpleThermo2DFlowField(configfile, constsfile, PRESS0,
- THETA, qvapmethod, sratios, Zbase,
- qcond, WMAX, Zlength, Xlength,
- VVEL)
-cthermo.write_thermodynamics_binary(thermofile, thermodyngen, configfile,
- constsfile, gridfile)
+thermodyngen = thermogen.SimpleThermo2DFlowField(
+ configfile,
+ constsfile,
+ PRESS0,
+ THETA,
+ qvapmethod,
+ sratios,
+ Zbase,
+ qcond,
+ WMAX,
+ Zlength,
+ Xlength,
+ VVEL,
+)
+cthermo.write_thermodynamics_binary(
+ thermofile, thermodyngen, configfile, constsfile, gridfile
+)
### ----- write initial superdroplets binary ----- ###
nsupers = crdgens.nsupers_at_domain_base(gridfile, constsfile, npergbx, zlim)
-coord3gen = crdgens.SampleCoordGen(True) # sample coord3 randomly
-coord1gen = crdgens.SampleCoordGen(True) # sample coord1 randomly
-coord2gen = crdgens.SampleCoordGen(True) # sample coord2 randomly
+coord3gen = crdgens.SampleCoordGen(True) # sample coord3 randomly
+coord1gen = crdgens.SampleCoordGen(True) # sample coord1 randomly
+coord2gen = crdgens.SampleCoordGen(True) # sample coord2 randomly
xiprobdist = probdists.LnNormal(geomeans, geosigs, scalefacs)
-radiigen = rgens.SampleLog10RadiiGen(rspan) # randomly sample radii from rspan [m]
+radiigen = rgens.SampleLog10RadiiGen(rspan) # randomly sample radii from rspan [m]
dryradiigen = dryrgens.ScaledRadiiGen(1.0)
-initattrsgen = attrsgen.AttrsGenerator(radiigen, dryradiigen, xiprobdist,
- coord3gen, coord1gen, coord2gen)
-csupers.write_initsuperdrops_binary(initSDsfile, initattrsgen,
- configfile, constsfile,
- gridfile, nsupers, numconc)
+initattrsgen = attrsgen.AttrsGenerator(
+ radiigen, dryradiigen, xiprobdist, coord3gen, coord1gen, coord2gen
+)
+csupers.write_initsuperdrops_binary(
+ initSDsfile, initattrsgen, configfile, constsfile, gridfile, nsupers, numconc
+)
### ----- show (and save) plots of binary file data ----- ###
if isfigures[0]:
- rgrid.plot_gridboxboundaries(constsfile, gridfile,
- savefigpath, isfigures[1])
- rthermo.plot_thermodynamics(constsfile, configfile, gridfile,
- thermofile, savefigpath, isfigures[1])
- rsupers.plot_initGBxs_distribs(configfile, constsfile, initSDsfile,
- gridfile, savefigpath, isfigures[1],
- SDgbxs2plt)
- plt.close()
+ rgrid.plot_gridboxboundaries(constsfile, gridfile, savefigpath, isfigures[1])
+ rthermo.plot_thermodynamics(
+ constsfile, configfile, gridfile, thermofile, savefigpath, isfigures[1]
+ )
+ rsupers.plot_initGBxs_distribs(
+ configfile,
+ constsfile,
+ initSDsfile,
+ gridfile,
+ savefigpath,
+ isfigures[1],
+ SDgbxs2plt,
+ )
+ plt.close()
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
### ---------------------- RUN CLEO EXECUTABLE --------------------- ###
### ---------------------------------------------------------------- ###
-executable = path2build+'/examples/speedtest/src/spdtest'
+executable = path2build + "/examples/speedtest/src/spdtest"
for n in range(nruns):
- os.chdir(path2build)
- os.system('rm -rf '+dataset) # delete any existing dataset
- print('Executable: '+executable)
- print('Config file: '+configfile)
- os.system(executable + ' ' + configfile)
-
- # copy speed results to new file
- print("--- reading runtime statistics ---")
- stats = read_statsfile(statsfile)
- for key, value in stats.items():
- print(key+": {:.3f}s".format(value))
- write_outstats(nruns, n, outdatafile, buildtype, stats)
- print("--- runtime stats written to file ---")
+ os.chdir(path2build)
+ os.system("rm -rf " + dataset) # delete any existing dataset
+ print("Executable: " + executable)
+ print("Config file: " + configfile)
+ os.system(executable + " " + configfile)
+
+ # copy speed results to new file
+ print("--- reading runtime statistics ---")
+ stats = read_statsfile(statsfile)
+ for key, value in stats.items():
+ print(key + ": {:.3f}s".format(value))
+ write_outstats(nruns, n, outdatafile, buildtype, stats)
+ print("--- runtime stats written to file ---")
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
diff --git a/examples/yac/divfreemotion/yac_data_reader.py b/examples/yac/divfreemotion/yac_data_reader.py
index 468c20ba7..1ec010ee6 100755
--- a/examples/yac/divfreemotion/yac_data_reader.py
+++ b/examples/yac/divfreemotion/yac_data_reader.py
@@ -1,49 +1,77 @@
-from yac import *
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
+----- CLEO -----
+File: yac_data_reader.py
+Project: divfreemotion
+Created Date: Tuesday 7th May 2024
+Author: Wilton Loch (WL)
+Additional Contributors:
+-----
+Last Modified: Tuesday 7th May 2024
+Modified By: CB
+-----
+License: BSD 3-Clause "New" or "Revised" License
+https://opensource.org/licenses/BSD-3-Clause
+-----
+File Description:
+"""
+
+from yac import YAC, def_calendar, Calendar, Reg2dGrid, Location, Field, TimeUnit
import numpy as np
# --- Subroutines for binary data reading ---
+
class variable_metadata:
def __init__(self, b0, bsize, nvar, vtype, units, scale_factor):
- self.b0 = b0
- self.bsize = bsize
- self.nvar = nvar
- self.vtype = vtype
- self.units = units
+ self.b0 = b0
+ self.bsize = bsize
+ self.nvar = nvar
+ self.vtype = vtype
+ self.units = units
self.scale_factor = scale_factor
+
def thermodynamicvar_from_binary(filename):
- file = open(filename, 'rb')
+ file = open(filename, "rb")
print("Opened file", filename)
variable_metadata = read_metadata(file)
return vector_from_binary(file, variable_metadata[0])
+
def vector_from_binary(file, variable_metadata):
file.seek(variable_metadata.b0)
- data = np.array(np.frombuffer(file.read(8 * variable_metadata.nvar), dtype = np.float64), copy=True)
+ data = np.array(
+ np.frombuffer(file.read(8 * variable_metadata.nvar), dtype=np.float64),
+ copy=True,
+ )
return data
+
def read_variable_metadata(file, offset):
- b0 = int.from_bytes(file.read(4), "little")
+ b0 = int.from_bytes(file.read(4), "little")
bsize = int.from_bytes(file.read(4), "little")
- nvar = int.from_bytes(file.read(4), "little")
+ nvar = int.from_bytes(file.read(4), "little")
vtype = file.read(1).decode("UTF-8")
units = file.read(1).decode("UTF-8")
- scale_factor = np.frombuffer(file.read(8), dtype = np.float64)[0]
+ scale_factor = np.frombuffer(file.read(8), dtype=np.float64)[0]
return variable_metadata(b0, bsize, nvar, vtype, units, scale_factor)
+
def read_metadata(file):
- d0byte = int.from_bytes(file.read(4), "little")
- charbytes = int.from_bytes(file.read(4), "little")
- nvars = int.from_bytes(file.read(4), "little")
+ # d0byte = int.from_bytes(file.read(4), "little")
+ charbytes = int.from_bytes(file.read(4), "little")
+ nvars = int.from_bytes(file.read(4), "little")
mbytes_pervar = int.from_bytes(file.read(4), "little")
- global_meta_string = file.read(charbytes).decode("UTF-8")
+ # global_meta_string = file.read(charbytes).decode("UTF-8")
variable_metadata = []
offset = 4 + charbytes
@@ -54,6 +82,7 @@ def read_metadata(file):
return variable_metadata
+
# --- Start of YAC definitions ---
yac = YAC()
@@ -64,18 +93,18 @@ def read_metadata(file):
def_calendar(Calendar.PROLEPTIC_GREGORIAN)
# --- Grid definition ---
-lon = np.linspace(0,2*np.pi,32)[:-1]
-lat = np.linspace(-0.5*np.pi,0.5*np.pi, 33)[1:-1]
-grid = Reg2dGrid(f"yac_reader_grid", lon, lat)
+lon = np.linspace(0, 2 * np.pi, 32)[:-1]
+lat = np.linspace(-0.5 * np.pi, 0.5 * np.pi, 33)[1:-1]
+grid = Reg2dGrid("yac_reader_grid", lon, lat)
# --- Point definitions ---
-cell_centers_lon = (lon + np.pi/32)[:-1]
-cell_centers_lat = (lat + np.pi/66)[:-1]
+cell_centers_lon = (lon + np.pi / 32)[:-1]
+cell_centers_lat = (lat + np.pi / 66)[:-1]
edge_centers_lat = []
edge_centers_lon = []
for lat_index in range(0, len(lat) * 2 - 1):
- if (lat_index % 2 == 0):
+ if lat_index % 2 == 0:
edge_centers_lon.extend(cell_centers_lon)
edge_centers_lat.extend([lat[lat_index // 2]] * len(cell_centers_lon))
else:
@@ -83,33 +112,45 @@ def read_metadata(file):
edge_centers_lat.extend([cell_centers_lat[(lat_index - 1) // 2]] * len(lon))
cell_centers = grid.def_points(Location.CELL, cell_centers_lon, cell_centers_lat)
-edge_centers = grid.def_points_unstruct(Location.EDGE, edge_centers_lon, edge_centers_lat)
+edge_centers = grid.def_points_unstruct(
+ Location.EDGE, edge_centers_lon, edge_centers_lat
+)
# --- Field definitions ---
-press = Field.create("pressure", component, cell_centers, 1, "PT1M", TimeUnit.ISO_FORMAT)
-temp = Field.create("temperature", component, cell_centers, 1, "PT1M", TimeUnit.ISO_FORMAT)
-qvap = Field.create("qvap", component, cell_centers, 1, "PT1M", TimeUnit.ISO_FORMAT)
-qcond = Field.create("qcond", component, cell_centers, 1, "PT1M", TimeUnit.ISO_FORMAT)
-hor_wind_velocities = Field.create("hor_wind_velocities", component, edge_centers, 1, "PT1M", TimeUnit.ISO_FORMAT)
+press = Field.create(
+ "pressure", component, cell_centers, 1, "PT1M", TimeUnit.ISO_FORMAT
+)
+temp = Field.create(
+ "temperature", component, cell_centers, 1, "PT1M", TimeUnit.ISO_FORMAT
+)
+qvap = Field.create("qvap", component, cell_centers, 1, "PT1M", TimeUnit.ISO_FORMAT)
+qcond = Field.create("qcond", component, cell_centers, 1, "PT1M", TimeUnit.ISO_FORMAT)
+hor_wind_velocities = Field.create(
+ "hor_wind_velocities", component, edge_centers, 1, "PT1M", TimeUnit.ISO_FORMAT
+)
# --- End of YAC definitions ---
yac.enddef()
np.set_printoptions(threshold=np.inf)
# Read binary data from files
-press_values = thermodynamicvar_from_binary("../build/share/df2d_dimlessthermo_press.dat")
-temp_values = thermodynamicvar_from_binary("../build/share/df2d_dimlessthermo_temp.dat")
-qvap_values = thermodynamicvar_from_binary("../build/share/df2d_dimlessthermo_qvap.dat")
-qcond_values = thermodynamicvar_from_binary("../build/share/df2d_dimlessthermo_qcond.dat")
-uvel = thermodynamicvar_from_binary("../build/share/df2d_dimlessthermo_uvel.dat")
-wvel = thermodynamicvar_from_binary("../build/share/df2d_dimlessthermo_wvel.dat")
+press_values = thermodynamicvar_from_binary(
+ "../build/share/df2d_dimlessthermo_press.dat"
+)
+temp_values = thermodynamicvar_from_binary("../build/share/df2d_dimlessthermo_temp.dat")
+qvap_values = thermodynamicvar_from_binary("../build/share/df2d_dimlessthermo_qvap.dat")
+qcond_values = thermodynamicvar_from_binary(
+ "../build/share/df2d_dimlessthermo_qcond.dat"
+)
+uvel = thermodynamicvar_from_binary("../build/share/df2d_dimlessthermo_uvel.dat")
+wvel = thermodynamicvar_from_binary("../build/share/df2d_dimlessthermo_wvel.dat")
# Pack all wind velocities edge data into one united field for YAC exchange
united_edge_data = []
for timestep in range(0, 3):
- timestep_offset = timestep * (len(edge_centers_lon) // 2 )
+ timestep_offset = timestep * (len(edge_centers_lon) // 2)
for lat_index in range(0, len(lat) * 2 - 1):
- if (lat_index % 2 == 0):
+ if lat_index % 2 == 0:
lower_index = timestep_offset + (lat_index // 2) * (len(lon) - 1)
upper_index = timestep_offset + (lat_index // 2 + 1) * (len(lon) - 1)
united_edge_data.extend(uvel[lower_index:upper_index])
diff --git a/examples/yac/fromfile/src/gen_input_thermo.py b/examples/yac/fromfile/src/gen_input_thermo.py
index f5f488565..dda56df7c 100644
--- a/examples/yac/fromfile/src/gen_input_thermo.py
+++ b/examples/yac/fromfile/src/gen_input_thermo.py
@@ -1,4 +1,4 @@
-'''
+"""
Copyright (c) 2024 MPI-M, Clara Bayley
@@ -18,104 +18,105 @@
File Description:
Python functions used by yac1_fromfile example to make thermo and wind fields
for CLEO to run example with 3-D time-varying thermodynamics.
-'''
+"""
import sys
import numpy as np
-sys.path.append("../../../..") # for imports from pySD package
-from pySD.thermobinary_src.create_thermodynamics import thermoinputsdict
+sys.path.append("../../../..") # for imports from pySD package
from pySD.gbxboundariesbinary_src import read_gbxboundaries as rgrid
-class TimeVarying3DThermo:
- ''' create some sinusoidal thermodynamics that varies in time and is
- hetergenous throughout 3D domain '''
-
- def __init__(self, PRESSz0, TEMPz0, qvapz0, qcondz0,
- WMAX, Zlength, Xlength, VMAX, Ylength):
-
- ### parameters of profile ###
- self.PRESSz0 = PRESSz0 # pressure at z=0m [Pa]
- self.TEMPz0 = TEMPz0 # temperature at z=0m [K]
- self.qvapz0 = qvapz0 # vapour mass mixing ratio at z=0m [Kg/Kg]
- self.qcondz0 = qcondz0 # liquid mass mixing ratio at z=0m [Kg/Kg]
- self.dimless_omega = np.pi/4.0 # ~ frequency of time modulation []
-
- self.WMAX = WMAX # max velocities constant [m/s]
- self.Zlength = Zlength # wavelength of velocity modulation in z direction [m]
- self.Xlength = Xlength # wavelength of velocity modulation in x direction [m]
- self.VMAX = VMAX # max horizontal (y) velocity
- self.Ylength = Ylength # wavelength of velocity modulation in y direction [m]
-
- def idealised_flowfield2D(self, gbxbounds, ndims):
-
- zfaces, xcens_z, ycens_z = rgrid.coords_forgridboxfaces(gbxbounds, ndims, 'z')
- zcens_x, xfaces, ycens_x = rgrid.coords_forgridboxfaces(gbxbounds, ndims, 'x')
-
- ztilda = self.Zlength / np.pi
- xtilda = self.Xlength / (2*np.pi)
- wamp = 2 * self.WMAX
-
- WVEL = wamp * np.sin(zfaces / ztilda) * np.sin(xcens_z / xtilda)
- UVEL = wamp * xtilda / ztilda * np.cos(zcens_x/ztilda) * np.cos(xfaces/xtilda)
-
- # modulation in y direction
- WVEL *= (1.0 + 0.5 * np.cos(self.Ylength / np.pi * ycens_z))
- UVEL *= (1.0 + 0.5 * np.cos(self.Ylength / np.pi * ycens_x))
-
- return WVEL, UVEL
-
- def gen_3dvvelocity(self, gbxbounds, ndims):
-
- zcens_y, xcens_y, yfaces = rgrid.coords_forgridboxfaces(gbxbounds, ndims, 'y')
- zxmod = zcens_y / self.Zlength + xcens_y / self.Xlength
- VVEL = self.VMAX * (zxmod + np.cos(self.Ylength / np.pi * yfaces))
-
- return VVEL
-
- def generate_timevarying_3dwinds(self, gbxbounds, ndims, ntime, THERMODATA):
- # time modulation factor for variables at each timestep
- tmod = np.full(ntime, -0.5)
- tmod = np.power(tmod, np.array(range(0, ntime, 1)))
-
- WVEL, UVEL = self.idealised_flowfield2D(gbxbounds, ndims)
- VVEL = self.gen_3dvvelocity(gbxbounds, ndims)
-
- THERMODATA["WVEL"] = np.outer(tmod, WVEL).flatten()
- THERMODATA["UVEL"] = np.outer(tmod, UVEL).flatten()
- THERMODATA["VVEL"] = np.outer(tmod, VVEL).flatten()
-
- return THERMODATA
-
- def generate_3dsinusoidal_variable(self, gbxbounds, ndims, amp):
-
- zfulls, xfulls, yfulls = rgrid.fullcoords_forallgridboxes(gbxbounds, ndims)
-
- ztilda = self.Zlength / np.pi / 3.0
- xtilda = self.Xlength / np.pi / 4.0
- ytilda = self.Ylength / np.pi / 1.33
-
- return amp + 0.25 * amp * (np.sin(zfulls / ztilda) * np.sin(xfulls / xtilda) + np.sin(yfulls/ ytilda))
-
- def generate_thermo(self, gbxbounds, ndims, ntime):
-
- PRESS = self.generate_3dsinusoidal_variable(gbxbounds, ndims, self.PRESSz0)
- TEMP = self.generate_3dsinusoidal_variable(gbxbounds, ndims, self.TEMPz0)
- qvap = self.generate_3dsinusoidal_variable(gbxbounds, ndims, self.qvapz0)
- qcond = self.generate_3dsinusoidal_variable(gbxbounds, ndims, self.qcondz0)
-
- tmod = np.cos(self.dimless_omega * np.arange(0.0, ntime, 1.0))
- tmod = 1 + 0.5 * tmod
-
- THERMODATA = {
- "PRESS": np.outer(tmod, PRESS).flatten(),
- "TEMP": np.outer(tmod, TEMP).flatten(),
- "qvap": np.outer(tmod, qvap).flatten(),
- "qcond": np.outer(tmod, qcond).flatten(),
- }
-
- THERMODATA = self.generate_timevarying_3dwinds(gbxbounds, ndims, ntime, THERMODATA)
-
- return THERMODATA
+class TimeVarying3DThermo:
+ """create some sinusoidal thermodynamics that varies in time and is
+ hetergenous throughout 3D domain"""
+
+ def __init__(
+ self, PRESSz0, TEMPz0, qvapz0, qcondz0, WMAX, Zlength, Xlength, VMAX, Ylength
+ ):
+ ### parameters of profile ###
+ self.PRESSz0 = PRESSz0 # pressure at z=0m [Pa]
+ self.TEMPz0 = TEMPz0 # temperature at z=0m [K]
+ self.qvapz0 = qvapz0 # vapour mass mixing ratio at z=0m [Kg/Kg]
+ self.qcondz0 = qcondz0 # liquid mass mixing ratio at z=0m [Kg/Kg]
+ self.dimless_omega = np.pi / 4.0 # ~ frequency of time modulation []
+
+ self.WMAX = WMAX # max velocities constant [m/s]
+ self.Zlength = Zlength # wavelength of velocity modulation in z direction [m]
+ self.Xlength = Xlength # wavelength of velocity modulation in x direction [m]
+ self.VMAX = VMAX # max horizontal (y) velocity
+ self.Ylength = Ylength # wavelength of velocity modulation in y direction [m]
+
+ def idealised_flowfield2D(self, gbxbounds, ndims):
+ zfaces, xcens_z, ycens_z = rgrid.coords_forgridboxfaces(gbxbounds, ndims, "z")
+ zcens_x, xfaces, ycens_x = rgrid.coords_forgridboxfaces(gbxbounds, ndims, "x")
+
+ ztilda = self.Zlength / np.pi
+ xtilda = self.Xlength / (2 * np.pi)
+ wamp = 2 * self.WMAX
+
+ WVEL = wamp * np.sin(zfaces / ztilda) * np.sin(xcens_z / xtilda)
+ UVEL = (
+ wamp * xtilda / ztilda * np.cos(zcens_x / ztilda) * np.cos(xfaces / xtilda)
+ )
+
+ # modulation in y direction
+ WVEL *= 1.0 + 0.5 * np.cos(self.Ylength / np.pi * ycens_z)
+ UVEL *= 1.0 + 0.5 * np.cos(self.Ylength / np.pi * ycens_x)
+
+ return WVEL, UVEL
+
+ def gen_3dvvelocity(self, gbxbounds, ndims):
+ zcens_y, xcens_y, yfaces = rgrid.coords_forgridboxfaces(gbxbounds, ndims, "y")
+ zxmod = zcens_y / self.Zlength + xcens_y / self.Xlength
+ VVEL = self.VMAX * (zxmod + np.cos(self.Ylength / np.pi * yfaces))
+
+ return VVEL
+
+ def generate_timevarying_3dwinds(self, gbxbounds, ndims, ntime, THERMODATA):
+ # time modulation factor for variables at each timestep
+ tmod = np.full(ntime, -0.5)
+ tmod = np.power(tmod, np.array(range(0, ntime, 1)))
+
+ WVEL, UVEL = self.idealised_flowfield2D(gbxbounds, ndims)
+ VVEL = self.gen_3dvvelocity(gbxbounds, ndims)
+
+ THERMODATA["WVEL"] = np.outer(tmod, WVEL).flatten()
+ THERMODATA["UVEL"] = np.outer(tmod, UVEL).flatten()
+ THERMODATA["VVEL"] = np.outer(tmod, VVEL).flatten()
+
+ return THERMODATA
+
+ def generate_3dsinusoidal_variable(self, gbxbounds, ndims, amp):
+ zfulls, xfulls, yfulls = rgrid.fullcoords_forallgridboxes(gbxbounds, ndims)
+
+ ztilda = self.Zlength / np.pi / 3.0
+ xtilda = self.Xlength / np.pi / 4.0
+ ytilda = self.Ylength / np.pi / 1.33
+
+ return amp + 0.25 * amp * (
+ np.sin(zfulls / ztilda) * np.sin(xfulls / xtilda) + np.sin(yfulls / ytilda)
+ )
+
+ def generate_thermo(self, gbxbounds, ndims, ntime):
+ PRESS = self.generate_3dsinusoidal_variable(gbxbounds, ndims, self.PRESSz0)
+ TEMP = self.generate_3dsinusoidal_variable(gbxbounds, ndims, self.TEMPz0)
+ qvap = self.generate_3dsinusoidal_variable(gbxbounds, ndims, self.qvapz0)
+ qcond = self.generate_3dsinusoidal_variable(gbxbounds, ndims, self.qcondz0)
+
+ tmod = np.cos(self.dimless_omega * np.arange(0.0, ntime, 1.0))
+ tmod = 1 + 0.5 * tmod
+
+ THERMODATA = {
+ "PRESS": np.outer(tmod, PRESS).flatten(),
+ "TEMP": np.outer(tmod, TEMP).flatten(),
+ "qvap": np.outer(tmod, qvap).flatten(),
+ "qcond": np.outer(tmod, qcond).flatten(),
+ }
+
+ THERMODATA = self.generate_timevarying_3dwinds(
+ gbxbounds, ndims, ntime, THERMODATA
+ )
+
+ return THERMODATA
diff --git a/examples/yac/fromfile/src/plot_output_thermo.py b/examples/yac/fromfile/src/plot_output_thermo.py
index 9d40109d4..52d5d2297 100644
--- a/examples/yac/fromfile/src/plot_output_thermo.py
+++ b/examples/yac/fromfile/src/plot_output_thermo.py
@@ -1,4 +1,4 @@
-'''
+"""
Copyright (c) 2024 MPI-M, Clara Bayley
@@ -9,7 +9,7 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Tuesday 9th April 2024
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
@@ -18,98 +18,121 @@
File Description:
Python functions used to make plots of CLEO's thermodynamics output for
yac1_fromfile example.
-'''
-
+"""
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
from matplotlib import colors
+
def plot_domain_thermodynamics_timeseries(time, gbxs, thermo, winds, savedir):
- ''' plot 2-D cross-sections of domain along y axis for thermodynamics and
- wind fields at a series of time slices. '''
-
- labels = {
- "press": "Pressure /hPa",
- "temp": "Temperature /K",
- "qvap": "q$_{v}$ g/Kg",
- "qcond": "q$_{c}$ g/Kg",
- "wvel": "w m/s",
- "uvel": "u m/s",
- "vvel": "v m/s",
- }
-
- cmaps = {
- "press": ["PRGn", colors.CenteredNorm(vcenter=1015)],
- "temp": ["RdBu_r", colors.CenteredNorm(vcenter=300)],
- "qvap": ["BrBG", colors.CenteredNorm(vcenter=0.01)],
- "qcond": ["BrBG", colors.CenteredNorm(vcenter=0.001)],
- "wvel": ["coolwarm", colors.CenteredNorm(vcenter=0.0, halfrange=3.0)],
- "uvel": ["coolwarm", colors.CenteredNorm(vcenter=0.0, halfrange=3.0)],
- "vvel": ["coolwarm", colors.CenteredNorm(vcenter=0.0, halfrange=3.0)],
- }
-
- xxh, zzh = np.meshgrid(gbxs["xhalf"], gbxs["zhalf"], indexing="ij") # dims [xdims, zdims]
- t2plts = np.linspace(time.mins[0], time.mins[-1], 5)
-
- for key in labels.keys():
- try:
- data4d = thermo[key]
- except:
- data4d = winds[key]
-
- plot_timeseries_domain_slices(key, labels[key], cmaps[key][0], cmaps[key][1],
- time, t2plts, xxh, zzh, gbxs["yfull"],
- data4d, savedir)
-
-def plot_timeseries_domain_slices(key, label, cmap, norm, time, t2plts,
- xxh, zzh, yfull, data4d, savedir):
- ''' plot 2-D cross-sections along y axis of 3-D data 'data3d' for several timeslices
- given times and meshgrid for centres in x and z, 'xxh' and 'zzh'. '''
-
- fig = plt.figure(figsize=(16, 9))
- ncols = len(t2plts)
- nrows = data4d.shape[1]
- gs = GridSpec(nrows=nrows+1, ncols=ncols, figure=fig, height_ratios=[1]+[8]*nrows)
- cax = fig.add_subplot(gs[0,:])
-
- cax.set_title(label)
- pcm = plot_domain_slices(fig, gs, nrows, ncols, time, t2plts,
- xxh, zzh, yfull, data4d, cmap, norm)
- cbar = plt.colorbar(pcm, cax=cax, orientation='horizontal')
-
- fig.tight_layout()
-
- if savedir != "":
- savename = savedir+"/timeseries_"+key+".png"
- fig.savefig(savename, dpi=400,
- bbox_inches="tight", facecolor='w', format="png")
- print("Figure .png saved as: "+savename)
- plt.show()
-
-def plot_domain_slices(fig, gs, nrows, ncols, time, t2plts,
- xxh, zzh, yfull, data4d, cmap, norm):
- ''' plot 2-D cross-sections along y axis of 4-D data 'data4d' at a series of time slices
- given meshgrid for centres in x and z, 'xxh' and 'zzh'. '''
-
- for m in range(ncols):
- t = np.argmin(abs(time.mins-t2plts[m]))
- data3d = data4d[t, :, :, :]
- for n in range(nrows):
- ax = fig.add_subplot(gs[n+1, m])
- ax.set_title("t={:.0f}min,\ny={:.2f}km".format(time.mins[t], yfull[n]/1000))
- pcm = ax_colormap(ax, xxh, zzh, data3d[n, :, :], cmap, norm)
-
- return pcm
+ """plot 2-D cross-sections of domain along y axis for thermodynamics and
+ wind fields at a series of time slices."""
+
+ labels = {
+ "press": "Pressure /hPa",
+ "temp": "Temperature /K",
+ "qvap": "q$_{v}$ g/Kg",
+ "qcond": "q$_{c}$ g/Kg",
+ "wvel": "w m/s",
+ "uvel": "u m/s",
+ "vvel": "v m/s",
+ }
+
+ cmaps = {
+ "press": ["PRGn", colors.CenteredNorm(vcenter=1015)],
+ "temp": ["RdBu_r", colors.CenteredNorm(vcenter=300)],
+ "qvap": ["BrBG", colors.CenteredNorm(vcenter=0.01)],
+ "qcond": ["BrBG", colors.CenteredNorm(vcenter=0.001)],
+ "wvel": ["coolwarm", colors.CenteredNorm(vcenter=0.0, halfrange=3.0)],
+ "uvel": ["coolwarm", colors.CenteredNorm(vcenter=0.0, halfrange=3.0)],
+ "vvel": ["coolwarm", colors.CenteredNorm(vcenter=0.0, halfrange=3.0)],
+ }
+
+ xxh, zzh = np.meshgrid(
+ gbxs["xhalf"], gbxs["zhalf"], indexing="ij"
+ ) # dims [xdims, zdims]
+ t2plts = np.linspace(time.mins[0], time.mins[-1], 5)
+
+ for key in labels.keys():
+ try:
+ data4d = thermo[key]
+ except ValueError:
+ data4d = winds[key]
+
+ plot_timeseries_domain_slices(
+ key,
+ labels[key],
+ cmaps[key][0],
+ cmaps[key][1],
+ time,
+ t2plts,
+ xxh,
+ zzh,
+ gbxs["yfull"],
+ data4d,
+ savedir,
+ )
+
+
+def plot_timeseries_domain_slices(
+ key, label, cmap, norm, time, t2plts, xxh, zzh, yfull, data4d, savedir
+):
+ """plot 2-D cross-sections along y axis of 3-D data 'data3d' for several timeslices
+ given times and meshgrid for centres in x and z, 'xxh' and 'zzh'."""
+
+ fig = plt.figure(figsize=(16, 9))
+ ncols = len(t2plts)
+ nrows = data4d.shape[1]
+ gs = GridSpec(
+ nrows=nrows + 1, ncols=ncols, figure=fig, height_ratios=[1] + [8] * nrows
+ )
+ cax = fig.add_subplot(gs[0, :])
+
+ cax.set_title(label)
+ pcm = plot_domain_slices(
+ fig, gs, nrows, ncols, time, t2plts, xxh, zzh, yfull, data4d, cmap, norm
+ )
+ plt.colorbar(pcm, cax=cax, orientation="horizontal")
+
+ fig.tight_layout()
+
+ if savedir != "":
+ savename = savedir + "/timeseries_" + key + ".png"
+ fig.savefig(savename, dpi=400, bbox_inches="tight", facecolor="w", format="png")
+ print("Figure .png saved as: " + savename)
+ plt.show()
+
+
+def plot_domain_slices(
+ fig, gs, nrows, ncols, time, t2plts, xxh, zzh, yfull, data4d, cmap, norm
+):
+ """plot 2-D cross-sections along y axis of 4-D data 'data4d' at a series of time slices
+ given meshgrid for centres in x and z, 'xxh' and 'zzh'."""
+
+ for m in range(ncols):
+ t = np.argmin(abs(time.mins - t2plts[m]))
+ data3d = data4d[t, :, :, :]
+ for n in range(nrows):
+ ax = fig.add_subplot(gs[n + 1, m])
+ ax.set_title(
+ "t={:.0f}min,\ny={:.2f}km".format(time.mins[t], yfull[n] / 1000)
+ )
+ pcm = ax_colormap(ax, xxh, zzh, data3d[n, :, :], cmap, norm)
+
+ return pcm
+
def ax_colormap(ax, xxh, zzh, data2d, cmap, norm):
- ''' plot pcolormesh for 2-D data 'data2d' given meshgrid for
- centres in x and z, 'xxh' and 'zzh'. '''
+ """plot pcolormesh for 2-D data 'data2d' given meshgrid for
+ centres in x and z, 'xxh' and 'zzh'."""
- ax.set_aspect("equal")
- pcm = ax.pcolormesh(xxh[:,:]/1000, zzh[:,:]/1000, data2d, cmap=cmap, norm=norm)
- ax.set_xlabel("x /km")
- ax.set_ylabel("z /km")
+ ax.set_aspect("equal")
+ pcm = ax.pcolormesh(
+ xxh[:, :] / 1000, zzh[:, :] / 1000, data2d, cmap=cmap, norm=norm
+ )
+ ax.set_xlabel("x /km")
+ ax.set_ylabel("z /km")
- return pcm
+ return pcm
diff --git a/examples/yac/fromfile/yac1_fromfile.py b/examples/yac/fromfile/yac1_fromfile.py
index 02db1c001..89e6933fe 100644
--- a/examples/yac/fromfile/yac1_fromfile.py
+++ b/examples/yac/fromfile/yac1_fromfile.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: yac1_fromfile.py
Project: fromfile
@@ -18,13 +18,11 @@
Script generates input files, then runs CLEO executable for 3D example with time varying
thermodynamics read from binary files to test that YAC can send the data to CLEO correctly.
Plots output data as .png files for visual checks.
-'''
+"""
import os
import sys
from pathlib import Path
-import numpy as np
-import matplotlib.pyplot as plt
import yac1_fromfile_inputfiles
path2CLEO = sys.argv[1]
@@ -33,27 +31,27 @@
sys.path.append(path2CLEO) # for imports from pySD package
# for imports from example plotting package
-sys.path.append(path2CLEO+"/examples/exampleplotting/")
+sys.path.append(path2CLEO + "/examples/exampleplotting/")
from src import plot_output_thermo
from plotssrc import pltsds, pltmoms
-from pySD.sdmout_src import *
+from pySD.sdmout_src import pyzarr, pysetuptxt, pygbxsdat
### ---------------------------------------------------------------- ###
### ----------------------- INPUT PARAMETERS ----------------------- ###
### ---------------------------------------------------------------- ###
### --- essential paths and filenames --- ###
# path and filenames for creating initial SD conditions
-binpath = path2build+"/bin/"
-sharepath = path2build+"/share/"
-gridfile = sharepath+"yac1_dimlessGBxboundaries.dat"
-initSDsfile = sharepath+"yac1_dimlessSDsinit.dat"
-thermofile = sharepath+"/yac1_dimlessthermo.dat"
-savefigpath = path2build+"/bin/" # directory for saving figures
+binpath = path2build + "/bin/"
+sharepath = path2build + "/share/"
+gridfile = sharepath + "yac1_dimlessGBxboundaries.dat"
+initSDsfile = sharepath + "yac1_dimlessSDsinit.dat"
+thermofile = sharepath + "/yac1_dimlessthermo.dat"
+savefigpath = path2build + "/bin/" # directory for saving figures
# path and file names for plotting results
-setupfile = binpath+"yac1_setup.txt"
-dataset = binpath+"yac1_sol.zarr"
+setupfile = binpath + "yac1_setup.txt"
+dataset = binpath + "yac1_sol.zarr"
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
@@ -70,11 +68,13 @@
Path(savefigpath).mkdir(exist_ok=True)
### --- delete any existing initial conditions --- ###
-os.system("rm "+gridfile)
-os.system("rm "+initSDsfile)
-os.system("rm "+thermofile[:-4]+"*")
+os.system("rm " + gridfile)
+os.system("rm " + initSDsfile)
+os.system("rm " + thermofile[:-4] + "*")
-yac1_fromfile_inputfiles.main(path2CLEO, path2build, configfile, gridfile, initSDsfile, thermofile)
+yac1_fromfile_inputfiles.main(
+ path2CLEO, path2build, configfile, gridfile, initSDsfile, thermofile
+)
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
@@ -82,12 +82,12 @@
### ---------------------- RUN CLEO EXECUTABLE --------------------- ###
### ---------------------------------------------------------------- ###
os.chdir(path2build)
-os.system('pwd')
-os.system('rm -rf '+dataset) # delete any existing dataset
-executable = path2build+'/examples/yac/fromfile/src/yac1'
-print('Executable: '+executable)
-print('Config file: '+configfile)
-os.system(executable + ' ' + configfile)
+os.system("pwd")
+os.system("rm -rf " + dataset) # delete any existing dataset
+executable = path2build + "/examples/yac/fromfile/src/yac1"
+print("Executable: " + executable)
+print("Config file: " + configfile)
+os.system(executable + " " + configfile)
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
@@ -103,8 +103,9 @@
time = pyzarr.get_time(dataset)
sddata = pyzarr.get_supers(dataset, consts)
maxnsupers = pyzarr.get_totnsupers(dataset)
-thermo, winds = pyzarr.get_thermodata(dataset, config["ntime"], gbxs["ndims"],
- consts, getwinds=True)
+thermo, winds = pyzarr.get_thermodata(
+ dataset, config["ntime"], gbxs["ndims"], consts, getwinds=True
+)
# plot super-droplet results
savename = savefigpath + "yac1_maxnsupers_validation.png"
@@ -112,16 +113,19 @@
nsample = 1000
savename = savefigpath + "yac1_motion2d_validation.png"
-pltsds.plot_randomsample_superdrops_2dmotion(sddata,
- config["maxnsupers"],
- nsample,
- savename=savename,
- arrows=False,
- israndom=False)
+pltsds.plot_randomsample_superdrops_2dmotion(
+ sddata,
+ config["maxnsupers"],
+ nsample,
+ savename=savename,
+ arrows=False,
+ israndom=False,
+)
# plot thermodynamics results
-plot_output_thermo.plot_domain_thermodynamics_timeseries(time, gbxs, thermo, winds,
- savedir=savefigpath)
+plot_output_thermo.plot_domain_thermodynamics_timeseries(
+ time, gbxs, thermo, winds, savedir=savefigpath
+)
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
diff --git a/examples/yac/fromfile/yac1_fromfile_inputfiles.py b/examples/yac/fromfile/yac1_fromfile_inputfiles.py
index 1001ac3c8..c4383ca36 100644
--- a/examples/yac/fromfile/yac1_fromfile_inputfiles.py
+++ b/examples/yac/fromfile/yac1_fromfile_inputfiles.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: yac1_fromfile_inputfiles.py
Project: fromfile
@@ -18,12 +18,12 @@
Script generates input files for 3D example with time varying
thermodynamics read from binary files to test that YAC can send
the data to CLEO correctly.
-'''
+"""
import sys
-def main(path2CLEO, path2build, configfile, gridfile, initSDsfile, thermofile):
+def main(path2CLEO, path2build, configfile, gridfile, initSDsfile, thermofile):
import numpy as np
import matplotlib.pyplot as plt
@@ -32,7 +32,13 @@ def main(path2CLEO, path2build, configfile, gridfile, initSDsfile, thermofile):
from src import gen_input_thermo
from pySD.gbxboundariesbinary_src import read_gbxboundaries as rgrid
from pySD.gbxboundariesbinary_src import create_gbxboundaries as cgrid
- from pySD.initsuperdropsbinary_src import crdgens, rgens, dryrgens, probdists, attrsgen
+ from pySD.initsuperdropsbinary_src import (
+ crdgens,
+ rgens,
+ dryrgens,
+ probdists,
+ attrsgen,
+ )
from pySD.initsuperdropsbinary_src import create_initsuperdrops as csupers
from pySD.thermobinary_src import create_thermodynamics as cthermo
from pySD.thermobinary_src import read_thermodynamics as rthermo
@@ -42,38 +48,38 @@ def main(path2CLEO, path2build, configfile, gridfile, initSDsfile, thermofile):
### ---------------------------------------------------------------- ###
### --- essential paths and filenames --- ###
# path and filenames for creating initial SD conditions
- constsfile = path2CLEO+"/libs/cleoconstants.hpp"
+ constsfile = path2CLEO + "/libs/cleoconstants.hpp"
### --- plotting initialisation figures --- ###
# booleans for [making, saving] initialisation figures
isfigures = [True, True]
- savefigpath = path2build+"/bin/" # directory for saving figures
+ savefigpath = path2build + "/bin/" # directory for saving figures
### --- settings for 2-D gridbox boundaries --- ###
- zgrid = [0, 1500, 60] # evenly spaced zhalf coords [zmin, zmax, zdelta] [m]
- xgrid = [0, 1500, 50] # evenly spaced xhalf coords [m]
- ygrid = np.array([0, 100, 200, 300]) # array of yhalf coords [m]
+ zgrid = [0, 1500, 60] # evenly spaced zhalf coords [zmin, zmax, zdelta] [m]
+ xgrid = [0, 1500, 50] # evenly spaced xhalf coords [m]
+ ygrid = np.array([0, 100, 200, 300]) # array of yhalf coords [m]
### --- settings for initial superdroplets --- ###
# settings for initial superdroplet coordinates
- zlim = 1000 # max z coord of superdroplets
- npergbx = 2 # number of superdroplets per gridbox
+ zlim = 1000 # max z coord of superdroplets
+ npergbx = 2 # number of superdroplets per gridbox
- monor = 1e-6 # all SDs have this same radius [m]
- dryr_sf = 1.0 # scale factor for dry radii [m]
- numconc = 5e8 # total no. conc of real droplets [m^-3]
- randcoord = False # sample SD spatial coordinates randomly or not
+ monor = 1e-6 # all SDs have this same radius [m]
+ dryr_sf = 1.0 # scale factor for dry radii [m]
+ numconc = 5e8 # total no. conc of real droplets [m^-3]
+ randcoord = False # sample SD spatial coordinates randomly or not
### --- settings for 2D Thermodynamics --- ###
- PRESSz0 = 101500 # [Pa]
- TEMPz0 = 300 # [K]
- qvapz0 = 0.05 # [Kg/Kg]
+ PRESSz0 = 101500 # [Pa]
+ TEMPz0 = 300 # [K]
+ qvapz0 = 0.05 # [Kg/Kg]
qcondz0 = 0.001 # [Kg/Kg]
- WMAX = 1.5 # [m/s]
- Zlength = 1500 # [m]
- Xlength = 1500 # [m]
- VMAX = 1.0 # [m/s]
- Ylength = 300 # [m]
+ WMAX = 1.5 # [m/s]
+ Zlength = 1500 # [m]
+ Xlength = 1500 # [m]
+ VMAX = 1.0 # [m/s]
+ Ylength = 300 # [m]
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
@@ -88,36 +94,40 @@ def main(path2CLEO, path2build, configfile, gridfile, initSDsfile, thermofile):
rgrid.print_domain_info(constsfile, gridfile)
### ----- write thermodynamics binaries ----- ###
- thermodyngen = gen_input_thermo.TimeVarying3DThermo(PRESSz0, TEMPz0, qvapz0, qcondz0,
- WMAX, Zlength, Xlength, VMAX, Ylength)
- cthermo.write_thermodynamics_binary(thermofile, thermodyngen, configfile,
- constsfile, gridfile)
+ thermodyngen = gen_input_thermo.TimeVarying3DThermo(
+ PRESSz0, TEMPz0, qvapz0, qcondz0, WMAX, Zlength, Xlength, VMAX, Ylength
+ )
+ cthermo.write_thermodynamics_binary(
+ thermofile, thermodyngen, configfile, constsfile, gridfile
+ )
### ----- write initial superdroplets binary ----- ###
nsupers = crdgens.nsupers_at_domain_base(gridfile, constsfile, npergbx, zlim)
- radiigen = rgens.MonoAttrGen(monor) # all SDs have the same radius [m]
- dryradiigen = dryrgens.ScaledRadiiGen(dryr_sf) # dryradii are 1/sf of radii [m]
- coord3gen = crdgens.SampleCoordGen(randcoord) # (not) random coord3 of SDs
- coord1gen = crdgens.SampleCoordGen(randcoord) # (not) random coord1 of SDs
- coord2gen = crdgens.SampleCoordGen(randcoord) # (not) random coord2 of SDs
- xiprobdist = probdists.DiracDelta(monor) # monodisperse droplet probability distrib
-
- initattrsgen = attrsgen.AttrsGenerator(radiigen, dryradiigen, xiprobdist,
- coord3gen, coord1gen, coord2gen)
- csupers.write_initsuperdrops_binary(initSDsfile, initattrsgen,
- configfile, constsfile,
- gridfile, nsupers, numconc)
+ radiigen = rgens.MonoAttrGen(monor) # all SDs have the same radius [m]
+ dryradiigen = dryrgens.ScaledRadiiGen(dryr_sf) # dryradii are 1/sf of radii [m]
+ coord3gen = crdgens.SampleCoordGen(randcoord) # (not) random coord3 of SDs
+ coord1gen = crdgens.SampleCoordGen(randcoord) # (not) random coord1 of SDs
+ coord2gen = crdgens.SampleCoordGen(randcoord) # (not) random coord2 of SDs
+ xiprobdist = probdists.DiracDelta(monor) # monodisperse droplet probability distrib
+
+ initattrsgen = attrsgen.AttrsGenerator(
+ radiigen, dryradiigen, xiprobdist, coord3gen, coord1gen, coord2gen
+ )
+ csupers.write_initsuperdrops_binary(
+ initSDsfile, initattrsgen, configfile, constsfile, gridfile, nsupers, numconc
+ )
### ----- show (and save) plots of binary file data ----- ###
if isfigures[0]:
- rgrid.plot_gridboxboundaries(constsfile, gridfile,
- savefigpath, isfigures[1])
- rthermo.plot_thermodynamics(constsfile, configfile, gridfile,
- thermofile, savefigpath, isfigures[1])
+ rgrid.plot_gridboxboundaries(constsfile, gridfile, savefigpath, isfigures[1])
+ rthermo.plot_thermodynamics(
+ constsfile, configfile, gridfile, thermofile, savefigpath, isfigures[1]
+ )
plt.close()
### ---------------------------------------------------------------- ###
### ---------------------------------------------------------------- ###
+
if __name__ == "__main__":
### args = path2CLEO, path2build, configfile, binpath, gridfile, initSDsfile, thermofile
main(*sys.argv[1:])
diff --git a/examples/yac/yac_3d/src/gen_input_thermo.py b/examples/yac/yac_3d/src/gen_input_thermo.py
index 56dd32593..8a448acb7 100644
--- a/examples/yac/yac_3d/src/gen_input_thermo.py
+++ b/examples/yac/yac_3d/src/gen_input_thermo.py
@@ -1,4 +1,4 @@
-'''
+"""
Copyright (c) 2024 MPI-M, Clara Bayley
@@ -18,104 +18,105 @@
File Description:
Python functions used by yac_3d example to make thermo and wind fields
for CLEO to run example with 3-D time-varying thermodynamics.
-'''
+"""
import sys
import numpy as np
-sys.path.append("../../../..") # for imports from pySD package
-from pySD.thermobinary_src.create_thermodynamics import thermoinputsdict
+sys.path.append("../../../..") # for imports from pySD package
from pySD.gbxboundariesbinary_src import read_gbxboundaries as rgrid
-class TimeVarying3DThermo:
- ''' create some sinusoidal thermodynamics that varies in time and is
- hetergenous throughout 3D domain '''
-
- def __init__(self, PRESSz0, TEMPz0, qvapz0, qcondz0,
- WMAX, Zlength, Xlength, VMAX, Ylength):
-
- ### parameters of profile ###
- self.PRESSz0 = PRESSz0 # pressure at z=0m [Pa]
- self.TEMPz0 = TEMPz0 # temperature at z=0m [K]
- self.qvapz0 = qvapz0 # vapour mass mixing ratio at z=0m [Kg/Kg]
- self.qcondz0 = qcondz0 # liquid mass mixing ratio at z=0m [Kg/Kg]
- self.dimless_omega = np.pi/4.0 # ~ frequency of time modulation []
-
- self.WMAX = WMAX # max velocities constant [m/s]
- self.Zlength = Zlength # wavelength of velocity modulation in z direction [m]
- self.Xlength = Xlength # wavelength of velocity modulation in x direction [m]
- self.VMAX = VMAX # max horizontal (y) velocity
- self.Ylength = Ylength # wavelength of velocity modulation in y direction [m]
-
- def idealised_flowfield2D(self, gbxbounds, ndims):
-
- zfaces, xcens_z, ycens_z = rgrid.coords_forgridboxfaces(gbxbounds, ndims, 'z')
- zcens_x, xfaces, ycens_x = rgrid.coords_forgridboxfaces(gbxbounds, ndims, 'x')
-
- ztilda = self.Zlength / np.pi
- xtilda = self.Xlength / (2*np.pi)
- wamp = 2 * self.WMAX
-
- WVEL = wamp * np.sin(zfaces / ztilda) * np.sin(xcens_z / xtilda)
- UVEL = wamp * xtilda / ztilda * np.cos(zcens_x/ztilda) * np.cos(xfaces/xtilda)
-
- # modulation in y direction
- WVEL *= (1.0 + 0.5 * np.cos(self.Ylength / np.pi * ycens_z))
- UVEL *= (1.0 + 0.5 * np.cos(self.Ylength / np.pi * ycens_x))
-
- return WVEL, UVEL
-
- def gen_3dvvelocity(self, gbxbounds, ndims):
-
- zcens_y, xcens_y, yfaces = rgrid.coords_forgridboxfaces(gbxbounds, ndims, 'y')
- zxmod = zcens_y / self.Zlength + xcens_y / self.Xlength
- VVEL = self.VMAX * (zxmod + np.cos(self.Ylength / np.pi * yfaces))
-
- return VVEL
-
- def generate_timevarying_3dwinds(self, gbxbounds, ndims, ntime, THERMODATA):
- # time modulation factor for variables at each timestep
- tmod = np.full(ntime, -0.5)
- tmod = np.power(tmod, np.array(range(0, ntime, 1)))
-
- WVEL, UVEL = self.idealised_flowfield2D(gbxbounds, ndims)
- VVEL = self.gen_3dvvelocity(gbxbounds, ndims)
-
- THERMODATA["WVEL"] = np.outer(tmod, WVEL).flatten()
- THERMODATA["UVEL"] = np.outer(tmod, UVEL).flatten()
- THERMODATA["VVEL"] = np.outer(tmod, VVEL).flatten()
-
- return THERMODATA
-
- def generate_3dsinusoidal_variable(self, gbxbounds, ndims, amp):
-
- zfulls, xfulls, yfulls = rgrid.fullcoords_forallgridboxes(gbxbounds, ndims)
-
- ztilda = self.Zlength / np.pi / 3.0
- xtilda = self.Xlength / np.pi / 4.0
- ytilda = self.Ylength / np.pi / 1.33
-
- return amp + 0.25 * amp * (np.sin(zfulls / ztilda) * np.sin(xfulls / xtilda) + np.sin(yfulls/ ytilda))
-
- def generate_thermo(self, gbxbounds, ndims, ntime):
-
- PRESS = self.generate_3dsinusoidal_variable(gbxbounds, ndims, self.PRESSz0)
- TEMP = self.generate_3dsinusoidal_variable(gbxbounds, ndims, self.TEMPz0)
- qvap = self.generate_3dsinusoidal_variable(gbxbounds, ndims, self.qvapz0)
- qcond = self.generate_3dsinusoidal_variable(gbxbounds, ndims, self.qcondz0)
-
- tmod = np.cos(self.dimless_omega * np.arange(0.0, ntime, 1.0))
- tmod = 1 + 0.5 * tmod
-
- THERMODATA = {
- "PRESS": np.outer(tmod, PRESS).flatten(),
- "TEMP": np.outer(tmod, TEMP).flatten(),
- "qvap": np.outer(tmod, qvap).flatten(),
- "qcond": np.outer(tmod, qcond).flatten(),
- }
-
- THERMODATA = self.generate_timevarying_3dwinds(gbxbounds, ndims, ntime, THERMODATA)
-
- return THERMODATA
+class TimeVarying3DThermo:
+ """create some sinusoidal thermodynamics that varies in time and is
+ hetergenous throughout 3D domain"""
+
+ def __init__(
+ self, PRESSz0, TEMPz0, qvapz0, qcondz0, WMAX, Zlength, Xlength, VMAX, Ylength
+ ):
+ ### parameters of profile ###
+ self.PRESSz0 = PRESSz0 # pressure at z=0m [Pa]
+ self.TEMPz0 = TEMPz0 # temperature at z=0m [K]
+ self.qvapz0 = qvapz0 # vapour mass mixing ratio at z=0m [Kg/Kg]
+ self.qcondz0 = qcondz0 # liquid mass mixing ratio at z=0m [Kg/Kg]
+ self.dimless_omega = np.pi / 4.0 # ~ frequency of time modulation []
+
+ self.WMAX = WMAX # max velocities constant [m/s]
+ self.Zlength = Zlength # wavelength of velocity modulation in z direction [m]
+ self.Xlength = Xlength # wavelength of velocity modulation in x direction [m]
+ self.VMAX = VMAX # max horizontal (y) velocity
+ self.Ylength = Ylength # wavelength of velocity modulation in y direction [m]
+
+ def idealised_flowfield2D(self, gbxbounds, ndims):
+ zfaces, xcens_z, ycens_z = rgrid.coords_forgridboxfaces(gbxbounds, ndims, "z")
+ zcens_x, xfaces, ycens_x = rgrid.coords_forgridboxfaces(gbxbounds, ndims, "x")
+
+ ztilda = self.Zlength / np.pi
+ xtilda = self.Xlength / (2 * np.pi)
+ wamp = 2 * self.WMAX
+
+ WVEL = wamp * np.sin(zfaces / ztilda) * np.sin(xcens_z / xtilda)
+ UVEL = (
+ wamp * xtilda / ztilda * np.cos(zcens_x / ztilda) * np.cos(xfaces / xtilda)
+ )
+
+ # modulation in y direction
+ WVEL *= 1.0 + 0.5 * np.cos(self.Ylength / np.pi * ycens_z)
+ UVEL *= 1.0 + 0.5 * np.cos(self.Ylength / np.pi * ycens_x)
+
+ return WVEL, UVEL
+
+ def gen_3dvvelocity(self, gbxbounds, ndims):
+ zcens_y, xcens_y, yfaces = rgrid.coords_forgridboxfaces(gbxbounds, ndims, "y")
+ zxmod = zcens_y / self.Zlength + xcens_y / self.Xlength
+ VVEL = self.VMAX * (zxmod + np.cos(self.Ylength / np.pi * yfaces))
+
+ return VVEL
+
+ def generate_timevarying_3dwinds(self, gbxbounds, ndims, ntime, THERMODATA):
+ # time modulation factor for variables at each timestep
+ tmod = np.full(ntime, -0.5)
+ tmod = np.power(tmod, np.array(range(0, ntime, 1)))
+
+ WVEL, UVEL = self.idealised_flowfield2D(gbxbounds, ndims)
+ VVEL = self.gen_3dvvelocity(gbxbounds, ndims)
+
+ THERMODATA["WVEL"] = np.outer(tmod, WVEL).flatten()
+ THERMODATA["UVEL"] = np.outer(tmod, UVEL).flatten()
+ THERMODATA["VVEL"] = np.outer(tmod, VVEL).flatten()
+
+ return THERMODATA
+
+ def generate_3dsinusoidal_variable(self, gbxbounds, ndims, amp):
+ zfulls, xfulls, yfulls = rgrid.fullcoords_forallgridboxes(gbxbounds, ndims)
+
+ ztilda = self.Zlength / np.pi / 3.0
+ xtilda = self.Xlength / np.pi / 4.0
+ ytilda = self.Ylength / np.pi / 1.33
+
+ return amp + 0.25 * amp * (
+ np.sin(zfulls / ztilda) * np.sin(xfulls / xtilda) + np.sin(yfulls / ytilda)
+ )
+
+ def generate_thermo(self, gbxbounds, ndims, ntime):
+ PRESS = self.generate_3dsinusoidal_variable(gbxbounds, ndims, self.PRESSz0)
+ TEMP = self.generate_3dsinusoidal_variable(gbxbounds, ndims, self.TEMPz0)
+ qvap = self.generate_3dsinusoidal_variable(gbxbounds, ndims, self.qvapz0)
+ qcond = self.generate_3dsinusoidal_variable(gbxbounds, ndims, self.qcondz0)
+
+ tmod = np.cos(self.dimless_omega * np.arange(0.0, ntime, 1.0))
+ tmod = 1 + 0.5 * tmod
+
+ THERMODATA = {
+ "PRESS": np.outer(tmod, PRESS).flatten(),
+ "TEMP": np.outer(tmod, TEMP).flatten(),
+ "qvap": np.outer(tmod, qvap).flatten(),
+ "qcond": np.outer(tmod, qcond).flatten(),
+ }
+
+ THERMODATA = self.generate_timevarying_3dwinds(
+ gbxbounds, ndims, ntime, THERMODATA
+ )
+
+ return THERMODATA
diff --git a/examples/yac/yac_3d/src/plot_output_thermo.py b/examples/yac/yac_3d/src/plot_output_thermo.py
index b53b2ab4b..3dabc93d0 100644
--- a/examples/yac/yac_3d/src/plot_output_thermo.py
+++ b/examples/yac/yac_3d/src/plot_output_thermo.py
@@ -1,4 +1,4 @@
-'''
+"""
Copyright (c) 2024 MPI-M, Clara Bayley
@@ -9,7 +9,7 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Tuesday 9th April 2024
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
@@ -18,98 +18,121 @@
File Description:
Python functions used to make plots of CLEO's thermodynamics output for
yac_3d example.
-'''
-
+"""
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
from matplotlib import colors
+
def plot_domain_thermodynamics_timeseries(time, gbxs, thermo, winds, savedir):
- ''' plot 2-D cross-sections of domain along y axis for thermodynamics and
- wind fields at a series of time slices. '''
-
- labels = {
- "press": "Pressure /hPa",
- "temp": "Temperature /K",
- "qvap": "q$_{v}$ g/Kg",
- "qcond": "q$_{c}$ g/Kg",
- "wvel": "w m/s",
- "uvel": "u m/s",
- "vvel": "v m/s",
- }
-
- cmaps = {
- "press": ["PRGn", colors.CenteredNorm(vcenter=1015)],
- "temp": ["RdBu_r", colors.CenteredNorm(vcenter=300)],
- "qvap": ["BrBG", colors.CenteredNorm(vcenter=0.01)],
- "qcond": ["BrBG", colors.CenteredNorm(vcenter=0.001)],
- "wvel": ["coolwarm", colors.CenteredNorm(vcenter=0.0, halfrange=3.0)],
- "uvel": ["coolwarm", colors.CenteredNorm(vcenter=0.0, halfrange=3.0)],
- "vvel": ["coolwarm", colors.CenteredNorm(vcenter=0.0, halfrange=3.0)],
- }
-
- xxh, zzh = np.meshgrid(gbxs["xhalf"], gbxs["zhalf"], indexing="ij") # dims [xdims, zdims]
- t2plts = np.linspace(time.mins[0], time.mins[-1], 5)
-
- for key in labels.keys():
- try:
- data4d = thermo[key]
- except:
- data4d = winds[key]
-
- plot_timeseries_domain_slices(key, labels[key], cmaps[key][0], cmaps[key][1],
- time, t2plts, xxh, zzh, gbxs["yfull"],
- data4d, savedir)
-
-def plot_timeseries_domain_slices(key, label, cmap, norm, time, t2plts,
- xxh, zzh, yfull, data4d, savedir):
- ''' plot 2-D cross-sections along y axis of 3-D data 'data3d' for several timeslices
- given times and meshgrid for centres in x and z, 'xxh' and 'zzh'. '''
-
- fig = plt.figure(figsize=(16, 9))
- ncols = len(t2plts)
- nrows = data4d.shape[1]
- gs = GridSpec(nrows=nrows+1, ncols=ncols, figure=fig, height_ratios=[1]+[8]*nrows)
- cax = fig.add_subplot(gs[0,:])
-
- cax.set_title(label)
- pcm = plot_domain_slices(fig, gs, nrows, ncols, time, t2plts,
- xxh, zzh, yfull, data4d, cmap, norm)
- cbar = plt.colorbar(pcm, cax=cax, orientation='horizontal')
-
- fig.tight_layout()
-
- if savedir != "":
- savename = savedir+"/timeseries_"+key+".png"
- fig.savefig(savename, dpi=400,
- bbox_inches="tight", facecolor='w', format="png")
- print("Figure .png saved as: "+savename)
- plt.show()
-
-def plot_domain_slices(fig, gs, nrows, ncols, time, t2plts,
- xxh, zzh, yfull, data4d, cmap, norm):
- ''' plot 2-D cross-sections along y axis of 4-D data 'data4d' at a series of time slices
- given meshgrid for centres in x and z, 'xxh' and 'zzh'. '''
-
- for m in range(ncols):
- t = np.argmin(abs(time.mins-t2plts[m]))
- data3d = data4d[t, :, :, :]
- for n in range(nrows):
- ax = fig.add_subplot(gs[n+1, m])
- ax.set_title("t={:.0f}min,\ny={:.2f}km".format(time.mins[t], yfull[n]/1000))
- pcm = ax_colormap(ax, xxh, zzh, data3d[n, :, :], cmap, norm)
-
- return pcm
+ """plot 2-D cross-sections of domain along y axis for thermodynamics and
+ wind fields at a series of time slices."""
+
+ labels = {
+ "press": "Pressure /hPa",
+ "temp": "Temperature /K",
+ "qvap": "q$_{v}$ g/Kg",
+ "qcond": "q$_{c}$ g/Kg",
+ "wvel": "w m/s",
+ "uvel": "u m/s",
+ "vvel": "v m/s",
+ }
+
+ cmaps = {
+ "press": ["PRGn", colors.CenteredNorm(vcenter=1015)],
+ "temp": ["RdBu_r", colors.CenteredNorm(vcenter=300)],
+ "qvap": ["BrBG", colors.CenteredNorm(vcenter=0.01)],
+ "qcond": ["BrBG", colors.CenteredNorm(vcenter=0.001)],
+ "wvel": ["coolwarm", colors.CenteredNorm(vcenter=0.0, halfrange=3.0)],
+ "uvel": ["coolwarm", colors.CenteredNorm(vcenter=0.0, halfrange=3.0)],
+ "vvel": ["coolwarm", colors.CenteredNorm(vcenter=0.0, halfrange=3.0)],
+ }
+
+ xxh, zzh = np.meshgrid(
+ gbxs["xhalf"], gbxs["zhalf"], indexing="ij"
+ ) # dims [xdims, zdims]
+ t2plts = np.linspace(time.mins[0], time.mins[-1], 5)
+
+ for key in labels.keys():
+ try:
+ data4d = thermo[key]
+ except KeyError:
+ data4d = winds[key]
+
+ plot_timeseries_domain_slices(
+ key,
+ labels[key],
+ cmaps[key][0],
+ cmaps[key][1],
+ time,
+ t2plts,
+ xxh,
+ zzh,
+ gbxs["yfull"],
+ data4d,
+ savedir,
+ )
+
+
+def plot_timeseries_domain_slices(
+ key, label, cmap, norm, time, t2plts, xxh, zzh, yfull, data4d, savedir
+):
+ """plot 2-D cross-sections along y axis of 3-D data 'data3d' for several timeslices
+ given times and meshgrid for centres in x and z, 'xxh' and 'zzh'."""
+
+ fig = plt.figure(figsize=(16, 9))
+ ncols = len(t2plts)
+ nrows = data4d.shape[1]
+ gs = GridSpec(
+ nrows=nrows + 1, ncols=ncols, figure=fig, height_ratios=[1] + [8] * nrows
+ )
+ cax = fig.add_subplot(gs[0, :])
+
+ cax.set_title(label)
+ pcm = plot_domain_slices(
+ fig, gs, nrows, ncols, time, t2plts, xxh, zzh, yfull, data4d, cmap, norm
+ )
+ plt.colorbar(pcm, cax=cax, orientation="horizontal")
+
+ fig.tight_layout()
+
+ if savedir != "":
+ savename = savedir + "/timeseries_" + key + ".png"
+ fig.savefig(savename, dpi=400, bbox_inches="tight", facecolor="w", format="png")
+ print("Figure .png saved as: " + savename)
+ plt.show()
+
+
+def plot_domain_slices(
+ fig, gs, nrows, ncols, time, t2plts, xxh, zzh, yfull, data4d, cmap, norm
+):
+ """plot 2-D cross-sections along y axis of 4-D data 'data4d' at a series of time slices
+ given meshgrid for centres in x and z, 'xxh' and 'zzh'."""
+
+ for m in range(ncols):
+ t = np.argmin(abs(time.mins - t2plts[m]))
+ data3d = data4d[t, :, :, :]
+ for n in range(nrows):
+ ax = fig.add_subplot(gs[n + 1, m])
+ ax.set_title(
+ "t={:.0f}min,\ny={:.2f}km".format(time.mins[t], yfull[n] / 1000)
+ )
+ pcm = ax_colormap(ax, xxh, zzh, data3d[n, :, :], cmap, norm)
+
+ return pcm
+
def ax_colormap(ax, xxh, zzh, data2d, cmap, norm):
- ''' plot pcolormesh for 2-D data 'data2d' given meshgrid for
- centres in x and z, 'xxh' and 'zzh'. '''
+ """plot pcolormesh for 2-D data 'data2d' given meshgrid for
+ centres in x and z, 'xxh' and 'zzh'."""
- ax.set_aspect("equal")
- pcm = ax.pcolormesh(xxh[:,:]/1000, zzh[:,:]/1000, data2d, cmap=cmap, norm=norm)
- ax.set_xlabel("x /km")
- ax.set_ylabel("z /km")
+ ax.set_aspect("equal")
+ pcm = ax.pcolormesh(
+ xxh[:, :] / 1000, zzh[:, :] / 1000, data2d, cmap=cmap, norm=norm
+ )
+ ax.set_xlabel("x /km")
+ ax.set_ylabel("z /km")
- return pcm
+ return pcm
diff --git a/examples/yac/yac_3d/yac_data_reader.py b/examples/yac/yac_3d/yac_data_reader.py
index 67fc8d152..8811c73e9 100755
--- a/examples/yac/yac_3d/yac_data_reader.py
+++ b/examples/yac/yac_3d/yac_data_reader.py
@@ -1,49 +1,77 @@
-from yac import *
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
+----- CLEO -----
+File: yac_data_reader.py
+Project: yac_3d
+Created Date: Tuesday 7th May 2024
+Author: Wilton Loch (WL)
+Additional Contributors:
+-----
+Last Modified: Tuesday 7th May 2024
+Modified By: CB
+-----
+License: BSD 3-Clause "New" or "Revised" License
+https://opensource.org/licenses/BSD-3-Clause
+-----
+File Description:
+"""
+
+from yac import YAC, def_calendar, Calendar, Reg2dGrid, Location, Field, TimeUnit
import numpy as np
# --- Subroutines for binary data reading ---
+
class variable_metadata:
def __init__(self, b0, bsize, nvar, vtype, units, scale_factor):
- self.b0 = b0
- self.bsize = bsize
- self.nvar = nvar
- self.vtype = vtype
- self.units = units
+ self.b0 = b0
+ self.bsize = bsize
+ self.nvar = nvar
+ self.vtype = vtype
+ self.units = units
self.scale_factor = scale_factor
+
def thermodynamicvar_from_binary(filename):
- file = open(filename, 'rb')
+ file = open(filename, "rb")
print("Opened file", filename)
variable_metadata = read_metadata(file)
return vector_from_binary(file, variable_metadata[0])
+
def vector_from_binary(file, variable_metadata):
file.seek(variable_metadata.b0)
- data = np.array(np.frombuffer(file.read(8 * variable_metadata.nvar), dtype = np.float64), copy=True)
+ data = np.array(
+ np.frombuffer(file.read(8 * variable_metadata.nvar), dtype=np.float64),
+ copy=True,
+ )
return data
+
def read_variable_metadata(file, offset):
- b0 = int.from_bytes(file.read(4), "little")
+ b0 = int.from_bytes(file.read(4), "little")
bsize = int.from_bytes(file.read(4), "little")
- nvar = int.from_bytes(file.read(4), "little")
+ nvar = int.from_bytes(file.read(4), "little")
vtype = file.read(1).decode("UTF-8")
units = file.read(1).decode("UTF-8")
- scale_factor = np.frombuffer(file.read(8), dtype = np.float64)[0]
+ scale_factor = np.frombuffer(file.read(8), dtype=np.float64)[0]
return variable_metadata(b0, bsize, nvar, vtype, units, scale_factor)
+
def read_metadata(file):
- d0byte = int.from_bytes(file.read(4), "little")
- charbytes = int.from_bytes(file.read(4), "little")
- nvars = int.from_bytes(file.read(4), "little")
+ # d0byte = int.from_bytes(file.read(4), "little")
+ charbytes = int.from_bytes(file.read(4), "little")
+ nvars = int.from_bytes(file.read(4), "little")
mbytes_pervar = int.from_bytes(file.read(4), "little")
- global_meta_string = file.read(charbytes).decode("UTF-8")
+ # global_meta_string = file.read(charbytes).decode("UTF-8")
variable_metadata = []
offset = 4 + charbytes
@@ -54,6 +82,7 @@ def read_metadata(file):
return variable_metadata
+
# --- Start of YAC definitions ---
yac = YAC()
@@ -64,18 +93,18 @@ def read_metadata(file):
def_calendar(Calendar.PROLEPTIC_GREGORIAN)
# --- Grid definition ---
-lon = np.linspace(0,2*np.pi, 27)[:-1]
-lat = np.linspace(-0.5*np.pi,0.5*np.pi, 33)[1:-1]
-grid = Reg2dGrid(f"yac_reader_grid", lon, lat)
+lon = np.linspace(0, 2 * np.pi, 27)[:-1]
+lat = np.linspace(-0.5 * np.pi, 0.5 * np.pi, 33)[1:-1]
+grid = Reg2dGrid("yac_reader_grid", lon, lat)
# --- Point definitions ---
-cell_centers_lon = (lon + np.pi/27)[:-1]
-cell_centers_lat = (lat + np.pi/66)[:-1]
+cell_centers_lon = (lon + np.pi / 27)[:-1]
+cell_centers_lat = (lat + np.pi / 66)[:-1]
edge_centers_lat = []
edge_centers_lon = []
for lat_index in range(0, len(lat) * 2 - 1):
- if (lat_index % 2 == 0):
+ if lat_index % 2 == 0:
edge_centers_lon.extend(cell_centers_lon)
edge_centers_lat.extend([lat[lat_index // 2]] * len(cell_centers_lon))
else:
@@ -83,28 +112,40 @@ def read_metadata(file):
edge_centers_lat.extend([cell_centers_lat[(lat_index - 1) // 2]] * len(lon))
cell_centers = grid.def_points(Location.CELL, cell_centers_lon, cell_centers_lat)
-edge_centers = grid.def_points_unstruct(Location.EDGE, edge_centers_lon, edge_centers_lat)
+edge_centers = grid.def_points_unstruct(
+ Location.EDGE, edge_centers_lon, edge_centers_lat
+)
# --- Field definitions ---
-press = Field.create("pressure", component, cell_centers, 1, "PT1M", TimeUnit.ISO_FORMAT)
-temp = Field.create("temperature", component, cell_centers, 1, "PT1M", TimeUnit.ISO_FORMAT)
-qvap = Field.create("qvap", component, cell_centers, 1, "PT1M", TimeUnit.ISO_FORMAT)
-qcond = Field.create("qcond", component, cell_centers, 1, "PT1M", TimeUnit.ISO_FORMAT)
-vvel = Field.create("vvel", component, cell_centers, 1, "PT1M", TimeUnit.ISO_FORMAT)
-hor_wind_velocities = Field.create("hor_wind_velocities", component, edge_centers, 1, "PT1M", TimeUnit.ISO_FORMAT)
+press = Field.create(
+ "pressure", component, cell_centers, 1, "PT1M", TimeUnit.ISO_FORMAT
+)
+temp = Field.create(
+ "temperature", component, cell_centers, 1, "PT1M", TimeUnit.ISO_FORMAT
+)
+qvap = Field.create("qvap", component, cell_centers, 1, "PT1M", TimeUnit.ISO_FORMAT)
+qcond = Field.create("qcond", component, cell_centers, 1, "PT1M", TimeUnit.ISO_FORMAT)
+vvel = Field.create("vvel", component, cell_centers, 1, "PT1M", TimeUnit.ISO_FORMAT)
+hor_wind_velocities = Field.create(
+ "hor_wind_velocities", component, edge_centers, 1, "PT1M", TimeUnit.ISO_FORMAT
+)
# --- End of YAC definitions ---
yac.enddef()
np.set_printoptions(threshold=np.inf)
# Read binary data from files
-press_values = thermodynamicvar_from_binary("../build/share/yac1_dimlessthermo_press.dat")
-temp_values = thermodynamicvar_from_binary("../build/share/yac1_dimlessthermo_temp.dat")
-qvap_values = thermodynamicvar_from_binary("../build/share/yac1_dimlessthermo_qvap.dat")
-qcond_values = thermodynamicvar_from_binary("../build/share/yac1_dimlessthermo_qcond.dat")
-uvel = thermodynamicvar_from_binary("../build/share/yac1_dimlessthermo_uvel.dat")
-wvel = thermodynamicvar_from_binary("../build/share/yac1_dimlessthermo_wvel.dat")
-vvel_values = thermodynamicvar_from_binary("../build/share/yac1_dimlessthermo_vvel.dat")
+press_values = thermodynamicvar_from_binary(
+ "../build/share/yac1_dimlessthermo_press.dat"
+)
+temp_values = thermodynamicvar_from_binary("../build/share/yac1_dimlessthermo_temp.dat")
+qvap_values = thermodynamicvar_from_binary("../build/share/yac1_dimlessthermo_qvap.dat")
+qcond_values = thermodynamicvar_from_binary(
+ "../build/share/yac1_dimlessthermo_qcond.dat"
+)
+uvel = thermodynamicvar_from_binary("../build/share/yac1_dimlessthermo_uvel.dat")
+wvel = thermodynamicvar_from_binary("../build/share/yac1_dimlessthermo_wvel.dat")
+vvel_values = thermodynamicvar_from_binary("../build/share/yac1_dimlessthermo_vvel.dat")
# Pack all horizontal wind velocities edge data into one united field for YAC exchange
united_edge_data = []
@@ -113,15 +154,31 @@ def read_metadata(file):
w_timestep_offset = timestep * 2340
for vertical_level in range(3):
for lat_index in range(len(lat) * 2 - 1):
- if (lat_index % 2 == 0):
+ if lat_index % 2 == 0:
vertical_level_offset = vertical_level * 775
- lower_index = u_timestep_offset + vertical_level_offset + (lat_index // 2) * (len(lon) - 1)
- upper_index = u_timestep_offset + vertical_level_offset + (lat_index // 2 + 1) * (len(lon) - 1)
+ lower_index = (
+ u_timestep_offset
+ + vertical_level_offset
+ + (lat_index // 2) * (len(lon) - 1)
+ )
+ upper_index = (
+ u_timestep_offset
+ + vertical_level_offset
+ + (lat_index // 2 + 1) * (len(lon) - 1)
+ )
united_edge_data.extend(uvel[lower_index:upper_index])
else:
vertical_level_offset = vertical_level * 780
- lower_index = w_timestep_offset + vertical_level_offset + ((lat_index - 1) // 2) * len(lon)
- upper_index = w_timestep_offset + vertical_level_offset + ((lat_index + 1) // 2) * len(lon)
+ lower_index = (
+ w_timestep_offset
+ + vertical_level_offset
+ + ((lat_index - 1) // 2) * len(lon)
+ )
+ upper_index = (
+ w_timestep_offset
+ + vertical_level_offset
+ + ((lat_index + 1) // 2) * len(lon)
+ )
united_edge_data.extend(wvel[lower_index:upper_index])
# somewhat generic version for cell and horizontal edge coupling
@@ -151,6 +208,8 @@ def read_metadata(file):
temp.put(temp_values[cell_lower_index:cell_upper_index])
qvap.put(qvap_values[cell_lower_index:cell_upper_index])
qcond.put(qcond_values[cell_lower_index:cell_upper_index])
- hor_wind_velocities.put(np.asarray(united_edge_data[edge_lower_index:edge_upper_index]))
+ hor_wind_velocities.put(
+ np.asarray(united_edge_data[edge_lower_index:edge_upper_index])
+ )
del yac
diff --git a/pySD/cxx2py.py b/pySD/cxx2py.py
index 6fe8e5214..ced05d5ca 100644
--- a/pySD/cxx2py.py
+++ b/pySD/cxx2py.py
@@ -1,4 +1,7 @@
-'''
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
----- CLEO -----
File: cxx2py.py
Project: pySD
@@ -6,118 +9,132 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Wednesday 17th April 2024
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
https://opensource.org/licenses/BSD-3-Clause
-----
-Copyright (c) 2023 MPI-M, Clara Bayley
------
File Description:
-'''
-
-
-import numpy as np
+"""
######## function(s) for converting c++ values into python ones ########
+
def print_dict_statement(filename, dictname, dict):
- ''' print key, value pairs for dictionary created by reading filename '''
+ """print key, value pairs for dictionary created by reading filename"""
+
+ print("\n---- " + dictname + " from ", filename, "-----")
+ for c in dict:
+ print(c, "=", dict[c])
+ print("---------------------------------------------\n")
- print("\n---- "+dictname+" from ", filename, "-----")
- for c in dict:
- print(c, "=", dict[c])
- print("---------------------------------------------\n")
def remove_excess_line(line):
- """ removes white spaces and
- and comments from a line """
+ """removes white spaces and
+ and comments from a line"""
+
+ line = line.strip()
+ line = line.replace(" ", "")
+ if "#" in line:
+ line = line[: line.find("#")]
- line = line.strip()
- line = line.replace(" ", "")
- if "#" in line:
- line = line[:line.find("#")]
+ return line
- return line
def line_with_assignment(line):
- """ find all the lines in a file
- which have form [...] = [...] ;
- ie. lines which assign variables. return
- list of these lines truncatd at ; """
-
- line = line.replace(" ", "")
- if "double" in line or "int" in line:
- ind1 = line.find("=")
- ind2 = line.find(";")
- if ind2 != -1 and ind1 != -1 and line[0] != "/":
- goodline = line[:ind2+1]
- return goodline
-
- else:
- return False
+ """find all the lines in a file
+ which have form [...] = [...] ;
+ ie. lines which assign variables. return
+ list of these lines truncatd at ;"""
+
+ line = line.replace(" ", "")
+ if "double" in line or "int" in line:
+ ind1 = line.find("=")
+ ind2 = line.find(";")
+ if ind2 != -1 and ind1 != -1 and line[0] != "/":
+ goodline = line[: ind2 + 1]
+ return goodline
+
+ else:
+ return False
+
def where_typename_ends(line):
- """finds index where c++ name for
- variable type ends for a const double,
- double, int or const int. """
-
- if line[:3] == "int": x="int"
- elif line[:6] == "double": x="double"
- elif line[:11] == "constdouble": x="constdouble"
- elif line[:8] == "constint": x="constint"
- elif line[:12] == "constexprint": x="constexprint"
- elif line[:15] == "constexprdouble": x="constexprdouble"
- elif line[:20] == "constexprunsignedint": x="constexprunsignedint"
- elif line[:17] == "constexpruint64_t": x="constexpruint64_t"
- else:
- raise Exception ("c++ type for variable not understood")
- return len(x)
+ """finds index where c++ name for
+ variable type ends for a const double,
+ double, int or const int."""
+
+ if line[:3] == "int":
+ x = "int"
+ elif line[:6] == "double":
+ x = "double"
+ elif line[:11] == "constdouble":
+ x = "constdouble"
+ elif line[:8] == "constint":
+ x = "constint"
+ elif line[:12] == "constexprint":
+ x = "constexprint"
+ elif line[:15] == "constexprdouble":
+ x = "constexprdouble"
+ elif line[:20] == "constexprunsignedint":
+ x = "constexprunsignedint"
+ elif line[:17] == "constexpruint64_t":
+ x = "constexpruint64_t"
+ else:
+ raise Exception("c++ type for variable not understood")
+ return len(x)
+
def read_cxxconsts_into_floats(filename):
- """returns dictionary of value: float from
- (const) doubles and (const) ints
- assigned in a c++ file. Also returns
- dictionary of notfloats for values that
- couldn't be converted"""
-
- floats = {}
- notfloats = {}
- with open(filename) as file:
- rlines=[]
- filelines = file.readlines()
- for line in filelines:
- goodline = line_with_assignment(line)
- if goodline:
- rlines.append(goodline)
-
- for line in rlines:
- x = where_typename_ends(line)
- name = line[x:line.find("=")]
- value = line[line.find("=")+1 : line.find(";")]
-
- try:
- floats[name] = float(value)
- except ValueError:
- notfloats[name] = value
-
- return floats
+ """returns dictionary of value: float from
+ (const) doubles and (const) ints
+ assigned in a c++ file. Also returns
+ dictionary of notfloats for values that
+ couldn't be converted"""
+
+ floats = {}
+ notfloats = {}
+ with open(filename) as file:
+ rlines = []
+ filelines = file.readlines()
+ for line in filelines:
+ goodline = line_with_assignment(line)
+ if goodline:
+ rlines.append(goodline)
+
+ for line in rlines:
+ x = where_typename_ends(line)
+ name = line[x : line.find("=")]
+ value = line[line.find("=") + 1 : line.find(";")]
+
+ try:
+ floats[name] = float(value)
+ except ValueError:
+ notfloats[name] = value
+
+ return floats
+
def derive_more_floats(consts):
- '''return mconsts dictionary containing
+ """return mconsts dictionary containing
some derived key,values from values in
- consts dictionary'''
-
- mconsts = {
- "COORD0" : consts["TIME0"]*consts["W0"], # characteristic coordinate grid scale [m]
- "RGAS_DRY" : consts["RGAS_UNIV"]/consts["MR_DRY"], # specific gas constant for dry air [J/Kg/K]
- "RGAS_V" : consts["RGAS_UNIV"]/consts["MR_WATER"], #specific gas constant for water [J/Kg/K]
- "CP0" : consts["CP_DRY"], # characteristic Heat capacity [J/Kg/K]
- "Mr_ratio" : consts["MR_WATER"]/consts["MR_DRY"],
- }
-
- mconsts["RHO0"] = consts["P0"]/(mconsts["CP0"]*consts["TEMP0"]) # characteristic density [Kg/m^3]
- mconsts["MASS0"] = (consts["R0"]**3) * mconsts["RHO0"] # characteristic mass [Kg]
-
- return mconsts
+ consts dictionary"""
+
+ mconsts = {
+ "COORD0": consts["TIME0"]
+ * consts["W0"], # characteristic coordinate grid scale [m]
+ "RGAS_DRY": consts["RGAS_UNIV"]
+ / consts["MR_DRY"], # specific gas constant for dry air [J/Kg/K]
+ "RGAS_V": consts["RGAS_UNIV"]
+ / consts["MR_WATER"], # specific gas constant for water [J/Kg/K]
+ "CP0": consts["CP_DRY"], # characteristic Heat capacity [J/Kg/K]
+ "Mr_ratio": consts["MR_WATER"] / consts["MR_DRY"],
+ }
+
+ mconsts["RHO0"] = consts["P0"] / (
+ mconsts["CP0"] * consts["TEMP0"]
+ ) # characteristic density [Kg/m^3]
+ mconsts["MASS0"] = (consts["R0"] ** 3) * mconsts["RHO0"] # characteristic mass [Kg]
+
+ return mconsts
diff --git a/pySD/editconfigfile.py b/pySD/editconfigfile.py
index 524ae2707..202bac4c4 100644
--- a/pySD/editconfigfile.py
+++ b/pySD/editconfigfile.py
@@ -1,4 +1,4 @@
-'''
+"""
Copyright (c) 2024 MPI-M, Clara Bayley
@@ -16,17 +16,18 @@
https://opensource.org/licenses/BSD-3-Clause
-----
File Description:
-'''
+"""
from ruamel.yaml import YAML
+
def update_param(node, param, new_value):
- '''Function to recursively searches for 'param' key in YAML node
- and updates it's value to with 'new_value' when found '''
+ """Function to recursively searches for 'param' key in YAML node
+ and updates it's value to with 'new_value' when found"""
if isinstance(node, dict):
if param in node:
- node[param] = new_value # update value
+ node[param] = new_value # update value
return True
else:
for key, val in node.items():
@@ -37,26 +38,27 @@ def update_param(node, param, new_value):
for item in node:
is_success = update_param(item, param, new_value)
if is_success:
- return True
+ return True
return False
+
def edit_config_params(filename, params2change):
- ''' rewrites config YAML file with key,value pairs listed in params2change updated to new values
- whilst preserving original YAML file's formatting and comments etc. '''
+ """rewrites config YAML file with key,value pairs listed in params2change updated to new values
+ whilst preserving original YAML file's formatting and comments etc."""
yaml = YAML()
# Load the YAML file
- with open(filename, 'r') as file:
+ with open(filename, "r") as file:
data = yaml.load(file)
# Update the parameters from the YAML file
for param, new_value in params2change.items():
is_success = update_param(data, param, new_value)
if not is_success:
- errmsg = param+" could not be updated to new value: "+str(new_value)
- raise ValueError(errmsg)
+ errmsg = param + " could not be updated to new value: " + str(new_value)
+ raise ValueError(errmsg)
# Overwrite the YAML file
- with open(filename, 'w') as file:
+ with open(filename, "w") as file:
yaml.dump(data, file)
diff --git a/pySD/gbxboundariesbinary_src/create_gbxboundaries.py b/pySD/gbxboundariesbinary_src/create_gbxboundaries.py
index 9bc6f8bc2..d170b5372 100644
--- a/pySD/gbxboundariesbinary_src/create_gbxboundaries.py
+++ b/pySD/gbxboundariesbinary_src/create_gbxboundaries.py
@@ -1,4 +1,7 @@
-'''
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
----- CLEO -----
File: create_gbxboundaries.py
Project: gbxboundariesbinary_src
@@ -6,167 +9,182 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Wednesday 17th April 2024
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
https://opensource.org/licenses/BSD-3-Clause
-----
-Copyright (c) 2023 MPI-M, Clara Bayley
------
File Description:
-'''
-
+"""
import numpy as np
from .. import cxx2py, writebinary
+
def get_COORD0_from_constsfile(constsfile, returnconsts=False):
- ''' create values from constants file required as inputs to create initial
- superdroplet conditions '''
+ """create values from constants file required as inputs to create initial
+ superdroplet conditions"""
+
+ consts = cxx2py.read_cxxconsts_into_floats(constsfile)
+ COORD0 = consts["TIME0"] * consts["W0"]
- consts = cxx2py.read_cxxconsts_into_floats(constsfile)
- COORD0 = consts["TIME0"]*consts["W0"]
+ if returnconsts:
+ return COORD0, consts
+ else:
+ return COORD0
- if returnconsts:
- return COORD0, consts
- else:
- return COORD0
def linearlyspaced_halfcoords(grid):
- ''' returns linearly spaced half coords ie. 1D
- gridbox boundaries given 'grid' list of
- [min coord, max coord, delta coord] for x, y or z'''
+ """returns linearly spaced half coords ie. 1D
+ gridbox boundaries given 'grid' list of
+ [min coord, max coord, delta coord] for x, y or z"""
- min, max, delta = grid
+ min, max, delta = grid
+
+ return np.arange(min, max + delta, delta, dtype=np.double)
- return np.arange(min, max+delta, delta, dtype=np.double)
def get_dimless_halfcoords(grid, coord, COORD0):
- ''' given a list or array, return dimensionless halfcoords.
- For list call "halfcoords_from_coordlims" before
- de-dimensionalising. For array simply return array / COORD0'''
+ """given a list or array, return dimensionless halfcoords.
+ For list call "halfcoords_from_coordlims" before
+ de-dimensionalising. For array simply return array / COORD0"""
+
+ if type(grid) not in [list, np.ndarray]:
+ raise ValueError("input " + coord + " grid is neither list or array ")
- if (type(grid) not in [list, np.ndarray]):
- raise ValueError("input "+coord+" grid is neither list or array ")
+ elif isinstance(grid, list):
+ grid = linearlyspaced_halfcoords(grid)
- elif type(grid) == list:
- grid = linearlyspaced_halfcoords(grid)
+ elif isinstance(grid, np.ndarray):
+ grid = np.array(grid, dtype=np.double)
- elif type(grid) == np.ndarray:
- grid = np.array(grid, dtype=np.double)
+ return grid / COORD0
- return grid / COORD0
def check_halfcoords(grid, coord):
- ''' check that x , y or z grid limits are for at least
- 1 cell with strictly monotonically increasing bounds '''
+ """check that x , y or z grid limits are for at least
+ 1 cell with strictly monotonically increasing bounds"""
- if (coord not in ["z", "x", "y"]):
- raise ValueError("coord should be x, y or z")
+ if coord not in ["z", "x", "y"]:
+ raise ValueError("coord should be x, y or z")
+
+ criteria = len(grid) >= 2 and np.all(np.diff(grid) > 0)
+ if criteria is False:
+ errmsg = str(grid) + " does not meet criteria for " + coord + " halfcoords"
+ raise ValueError(errmsg)
- criteria = (len(grid) >= 2 and np.all(np.diff(grid) > 0))
- if criteria != True:
- errmsg = str(grid)+" does not meet criteria for "+coord+" halfcoords"
- raise ValueError(errmsg)
def gridboxboundaries_from_halfcoords(allhalfcoords, ngridboxes):
- ''' returns gbxbounds dictionary. Each key of gbxbounds is a gridbox's
- index and the corresponding value is the gridbox's
- [zmin, zmax, xmin, xmax, ymin, ymax] boundaries'''
-
- zhalfs = allhalfcoords[0]
- xhalfs = allhalfcoords[1]
- yhalfs = allhalfcoords[2]
-
- gbxbounds, ii = {}, 0
- for j in range(len(yhalfs)-1):
- for i in range(len(xhalfs)-1):
- for k in range(len(zhalfs)-1):
- zbounds = [zhalfs[k], zhalfs[k+1]]
- xbounds = [xhalfs[i], xhalfs[i+1]]
- ybounds = [yhalfs[j], yhalfs[j+1]]
- gbxbounds[ii] = zbounds + xbounds + ybounds
- ii+=1
-
- if len(gbxbounds) != ngridboxes:
- raise ValueError("not enough gbxbounds for ngridboxes")
- else:
- print("created boundaries for",ngridboxes,"gridboxes")
-
- return gbxbounds
+ """returns gbxbounds dictionary. Each key of gbxbounds is a gridbox's
+ index and the corresponding value is the gridbox's
+ [zmin, zmax, xmin, xmax, ymin, ymax] boundaries"""
+
+ zhalfs = allhalfcoords[0]
+ xhalfs = allhalfcoords[1]
+ yhalfs = allhalfcoords[2]
+
+ gbxbounds, ii = {}, 0
+ for j in range(len(yhalfs) - 1):
+ for i in range(len(xhalfs) - 1):
+ for k in range(len(zhalfs) - 1):
+ zbounds = [zhalfs[k], zhalfs[k + 1]]
+ xbounds = [xhalfs[i], xhalfs[i + 1]]
+ ybounds = [yhalfs[j], yhalfs[j + 1]]
+ gbxbounds[ii] = zbounds + xbounds + ybounds
+ ii += 1
+
+ if len(gbxbounds) != ngridboxes:
+ raise ValueError("not enough gbxbounds for ngridboxes")
+ else:
+ print("created boundaries for", ngridboxes, "gridboxes")
+
+ return gbxbounds
+
def dimless_gridboxboundaries(zgrid, xgrid, ygrid, COORD0):
- ''' use zgrid, xgrid and ygrid lists or arrays to create half coords
- of gridboxes (ie. their boundaries). Return single list of zhalf,
- then xhalf, then yhalf coords and a list of len(3) which states how
- many of each z, x and y coords there are'''
+ """use zgrid, xgrid and ygrid lists or arrays to create half coords
+ of gridboxes (ie. their boundaries). Return single list of zhalf,
+ then xhalf, then yhalf coords and a list of len(3) which states how
+ many of each z, x and y coords there are"""
- allhalfcoords, ndims = [], [] # z, x and y half coords in 1 continuous list
- for grid, coord in zip([zgrid, xgrid, ygrid], ["z", "x", "y"]):
+ allhalfcoords, ndims = [], [] # z, x and y half coords in 1 continuous list
+ for grid, coord in zip([zgrid, xgrid, ygrid], ["z", "x", "y"]):
+ halfcoords = get_dimless_halfcoords(grid, coord, COORD0)
+ check_halfcoords(halfcoords, coord)
- halfcoords = get_dimless_halfcoords(grid, coord, COORD0)
- check_halfcoords(halfcoords, coord)
+ allhalfcoords.append(halfcoords)
+ ndims.append(len(halfcoords) - 1)
- allhalfcoords.append(halfcoords)
- ndims.append(len(halfcoords)-1)
+ gbxbounds = gridboxboundaries_from_halfcoords(allhalfcoords, np.prod(ndims))
- gbxbounds = gridboxboundaries_from_halfcoords(allhalfcoords, np.prod(ndims))
+ ndims = np.asarray(ndims, dtype=np.uint)
+ gbxindicies = np.array(list(gbxbounds.keys()), dtype=np.uintc).flatten()
+ gbxboundsdata = np.array(list(gbxbounds.values()), dtype=np.double).flatten()
- ndims = np.asarray(ndims, dtype=np.uint)
- gbxindicies = np.array(list(gbxbounds.keys()), dtype=np.uintc).flatten()
- gbxboundsdata = np.array(list(gbxbounds.values()), dtype=np.double).flatten()
+ return ndims, gbxindicies, gbxboundsdata
- return ndims, gbxindicies, gbxboundsdata
def set_arraydtype(arr, dtype):
+ og = type(arr[0])
+ if og != dtype:
+ arr = np.array(arr, dtype=dtype)
- og = type(arr[0])
- if og != dtype:
- arr = np.array(arr, dtype=dtype)
+ warning = (
+ "WARNING! dtype of attributes is being changed!"
+ + " from "
+ + str(og)
+ + " to "
+ + str(dtype)
+ )
+ raise ValueError(warning)
- warning = "WARNING! dtype of attributes is being changed!"+\
- " from "+str(og)+" to "+str(dtype)
- raise ValueError(warning)
+ return list(arr)
- return list(arr)
def ctype_compatible_gridboxboundaries(ndims, idxs, bounds):
- ''' check type of gridbox boundaries data is compatible
- with c type double. If not, change type and raise error '''
+ """check type of gridbox boundaries data is compatible
+ with c type double. If not, change type and raise error"""
+
+ datatypes = [np.uint, np.uintc, np.double]
- datatypes = [np.uint, np.uintc, np.double]
+ ndims = set_arraydtype(ndims, datatypes[0])
+ idxs = set_arraydtype(idxs, datatypes[1])
+ bounds = set_arraydtype(bounds, datatypes[2])
- ndims = set_arraydtype(ndims, datatypes[0])
- idxs = set_arraydtype(idxs, datatypes[1])
- bounds = set_arraydtype(bounds, datatypes[2])
+ datalist = ndims + idxs + bounds
- datalist = ndims + idxs + bounds
+ return datalist, datatypes
- return datalist, datatypes
def write_gridboxboundaries_binary(gridfile, zgrid, xgrid, ygrid, constsfile):
- ''' zgrid, xgrid and ygrid can either be list of
- [min coord, max coord, delta] or they can be arrays of
- their half coordinates (ie. gridbox boundaries). If the former,
- first create half coordinates. In both cases, de-dimensionalise
- and write the boundaries to a binary file, "filename" '''
-
- COORD0 = get_COORD0_from_constsfile(constsfile)
-
- ndims, gbxindicies, gbxboundsdata = dimless_gridboxboundaries(zgrid, xgrid,
- ygrid, COORD0)
- metastr = 'Variables in this file are ndims in (z,x,y), then the '+\
- str(np.prod(ndims))+' gridbox indicies followed by the'+\
- ' [zmin, zmax, xmin, xmax, ymin, ymax]'+\
- ' coordinates for each gridbox\'s boundaries'
-
- ndata = [len(dt) for dt in [ndims, gbxindicies, gbxboundsdata]]
- data, datatypes = ctype_compatible_gridboxboundaries(ndims, gbxindicies,
- gbxboundsdata)
- scale_factors = np.array([1.0, 1.0, COORD0], dtype=np.double)
- units = [b' ', b' ', b"m"]
-
- writebinary.writebinary(gridfile, data, ndata, datatypes,
- units, scale_factors, metastr)
+ """zgrid, xgrid and ygrid can either be list of
+ [min coord, max coord, delta] or they can be arrays of
+ their half coordinates (ie. gridbox boundaries). If the former,
+ first create half coordinates. In both cases, de-dimensionalise
+ and write the boundaries to a binary file, "filename" """
+
+ COORD0 = get_COORD0_from_constsfile(constsfile)
+
+ ndims, gbxindicies, gbxboundsdata = dimless_gridboxboundaries(
+ zgrid, xgrid, ygrid, COORD0
+ )
+ metastr = (
+ "Variables in this file are ndims in (z,x,y), then the "
+ + str(np.prod(ndims))
+ + " gridbox indicies followed by the"
+ + " [zmin, zmax, xmin, xmax, ymin, ymax]"
+ + " coordinates for each gridbox's boundaries"
+ )
+
+ ndata = [len(dt) for dt in [ndims, gbxindicies, gbxboundsdata]]
+ data, datatypes = ctype_compatible_gridboxboundaries(
+ ndims, gbxindicies, gbxboundsdata
+ )
+ scale_factors = np.array([1.0, 1.0, COORD0], dtype=np.double)
+ units = [b" ", b" ", b"m"]
+
+ writebinary.writebinary(
+ gridfile, data, ndata, datatypes, units, scale_factors, metastr
+ )
diff --git a/pySD/gbxboundariesbinary_src/read_gbxboundaries.py b/pySD/gbxboundariesbinary_src/read_gbxboundaries.py
index ec8769437..57b22c5c6 100644
--- a/pySD/gbxboundariesbinary_src/read_gbxboundaries.py
+++ b/pySD/gbxboundariesbinary_src/read_gbxboundaries.py
@@ -1,4 +1,4 @@
-'''
+"""
Copyright (c) 2024 MPI-M, Clara Bayley
@@ -16,7 +16,7 @@
https://opensource.org/licenses/BSD-3-Clause
-----
File Description:
-'''
+"""
import numpy as np
import matplotlib.pyplot as plt
@@ -24,128 +24,135 @@
from .create_gbxboundaries import get_COORD0_from_constsfile
from ..readbinary import readbinary
-def get_gridboxboundaries(gridfile, COORD0=False,
- constsfile="", isprint=True):
- ''' get gridbox boundaries from binary file and
- re-dimensionalise usign COORD0 const from constsfile '''
+
+def get_gridboxboundaries(gridfile, COORD0=False, constsfile="", isprint=True):
+ """get gridbox boundaries from binary file and
+ re-dimensionalise usign COORD0 const from constsfile"""
if not COORD0:
COORD0 = get_COORD0_from_constsfile(constsfile)
- gbxbounds = read_dimless_gbxboundaries_binary(gridfile, COORD0,
- isprint=isprint)
+ gbxbounds = read_dimless_gbxboundaries_binary(gridfile, COORD0, isprint=isprint)
zhalf, xhalf, yhalf = halfcoords_from_gbxbounds(gbxbounds, isprint=isprint)
return zhalf, xhalf, yhalf
+
def get_domainvol_from_gridfile(gridfile, COORD0=False, constsfile=""):
- ''' get total domain volume from binary file '''
+ """get total domain volume from binary file"""
- zhalf, xhalf, yhalf = get_gridboxboundaries(gridfile, COORD0=COORD0,
- constsfile=constsfile)
+ zhalf, xhalf, yhalf = get_gridboxboundaries(
+ gridfile, COORD0=COORD0, constsfile=constsfile
+ )
return calc_domainvol(zhalf, xhalf, yhalf)
-def get_gbxvols_from_gridfile(gridfile, COORD0=False,
- constsfile="", isprint=True):
- ''' get total domain volume from binary file '''
+
+def get_gbxvols_from_gridfile(gridfile, COORD0=False, constsfile="", isprint=True):
+ """get total domain volume from binary file"""
if not COORD0:
COORD0 = get_COORD0_from_constsfile(constsfile)
- gbxbounds = read_dimless_gbxboundaries_binary(gridfile, COORD0,
- isprint=isprint)
+ gbxbounds = read_dimless_gbxboundaries_binary(gridfile, COORD0, isprint=isprint)
return calc_gridboxvols(gbxbounds)
+
def fullcell(halfcell):
- ''' return fullcell corods (cell centres)
- given half coords in a direction'''
+ """return fullcell corods (cell centres)
+ given half coords in a direction"""
+
+ return (halfcell[1:] + halfcell[:-1]) / 2
- return (halfcell[1:]+halfcell[:-1])/2
def cellwidth(halfcell):
- ''' return cell spacing (width) given half coords
- in a direction '''
+ """return cell spacing (width) given half coords
+ in a direction"""
- delta = abs(halfcell[1:]-halfcell[:-1])
+ delta = abs(halfcell[1:] - halfcell[:-1])
return delta
-def fullcell_fromhalfcoords(zhalf, xhalf, yhalf):
+def fullcell_fromhalfcoords(zhalf, xhalf, yhalf):
zfull = fullcell(zhalf)
xfull = fullcell(xhalf)
yfull = fullcell(yhalf)
return zfull, xfull, yfull
+
def fullcoords_forallgridboxes(gbxbounds, ndims):
- ''' returns (x,y,z) centres of gridboxes in domain '''
+ """returns (x,y,z) centres of gridboxes in domain"""
zhalf, xhalf, yhalf = halfcoords_from_gbxbounds(gbxbounds, isprint=False)
zfull, xfull, yfull = fullcell_fromhalfcoords(zhalf, xhalf, yhalf)
- zfullcoords = np.tile(zfull, int(ndims[1]*ndims[2])) # zfull of every gridbox in order of gbxindex
+ zfullcoords = np.tile(
+ zfull, int(ndims[1] * ndims[2])
+ ) # zfull of every gridbox in order of gbxindex
xfullcoords = np.tile(np.repeat(xfull, ndims[0]), int(ndims[2]))
- yfullcoords = np.repeat(yfull, ndims[0]*ndims[1])
+ yfullcoords = np.repeat(yfull, ndims[0] * ndims[1])
return zfullcoords, xfullcoords, yfullcoords
+
def coords_forgridboxfaces(gbxbounds, ndims, face):
- ''' returns (x,y,z) coordinates of gridboxes faces
- in a particular direction '''
+ """returns (x,y,z) coordinates of gridboxes faces
+ in a particular direction"""
ndims = [int(n) for n in ndims]
zhalf, xhalf, yhalf = halfcoords_from_gbxbounds(gbxbounds, isprint=False)
zfull, xfull, yfull = fullcell_fromhalfcoords(zhalf, xhalf, yhalf)
if face == "z":
- nz, nx, ny = ndims[0]+1, ndims[1], ndims[2]
- zfaces = np.tile(zhalf, nx*ny) # zfull of every gridbox in order of gbxindex
+ nz, nx, ny = ndims[0] + 1, ndims[1], ndims[2]
+ zfaces = np.tile(zhalf, nx * ny) # zfull of every gridbox in order of gbxindex
xfulls = np.tile(np.repeat(xfull, nz), ny)
- yfulls = np.repeat(yfull, nz*nx)
+ yfulls = np.repeat(yfull, nz * nx)
return zfaces, xfulls, yfulls
elif face == "x":
- nz, nx, ny = ndims[0], ndims[1]+1, ndims[2]
- zfulls = np.tile(zfull, nx*ny) # zfull of every gridbox in order of gbxindex
+ nz, nx, ny = ndims[0], ndims[1] + 1, ndims[2]
+ zfulls = np.tile(zfull, nx * ny) # zfull of every gridbox in order of gbxindex
xfaces = np.tile(np.repeat(xhalf, nz), ny)
- yfulls = np.repeat(yfull, nz*nx)
+ yfulls = np.repeat(yfull, nz * nx)
return zfulls, xfaces, yfulls
elif face == "y":
- nz, nx, ny = ndims[0], ndims[1], ndims[2]+1
- zfulls = np.tile(zfull, nx*ny) # zfull of every gridbox in order of gbxindex
+ nz, nx, ny = ndims[0], ndims[1], ndims[2] + 1
+ zfulls = np.tile(zfull, nx * ny) # zfull of every gridbox in order of gbxindex
xfulls = np.tile(np.repeat(xfull, nz), ny)
- yfaces = np.repeat(yhalf, nz*nx)
+ yfaces = np.repeat(yhalf, nz * nx)
return zfulls, xfulls, yfaces
-def read_dimless_gbxboundaries_binary(filename, COORD0=False,
- return_ndims=False,
- isprint=True):
- ''' return dictionary for gbx indicies to gbx boundaries by
+
+def read_dimless_gbxboundaries_binary(
+ filename, COORD0=False, return_ndims=False, isprint=True
+):
+ """return dictionary for gbx indicies to gbx boundaries by
reading binary file. Return dimensionless version if COORD0
- not give (=False). '''
+ not give (=False)."""
data, ndata_pervar = readbinary(filename, isprint=isprint)
datatypes = [np.uint, np.uintc, np.double]
- ll = [0,0] # indexs for division of data list between each variable
+ ll = [0, 0] # indexs for division of data list between each variable
for n in range(1, len(ndata_pervar)):
- ll[n-1] = np.sum(ndata_pervar[:n])
+ ll[n - 1] = np.sum(ndata_pervar[:n])
- ndims = np.asarray(data[:ll[0]], dtype=datatypes[0])
- gbxidxs = np.asarray(data[ll[0]:ll[1]], dtype=datatypes[1])
+ ndims = np.asarray(data[: ll[0]], dtype=datatypes[0])
+ gbxidxs = np.asarray(data[ll[0] : ll[1]], dtype=datatypes[1])
ngridboxes = int(np.prod(ndims))
if len(gbxidxs) != ngridboxes:
err = "number of gridbox indexes not consistent with (z,x,y) dims"
raise ValueError(err)
- boundsdata = np.asarray(data[ll[1]:], dtype=datatypes[2])
- boundsdata = np.reshape(boundsdata, [ngridboxes, len(boundsdata)//ngridboxes])
+ boundsdata = np.asarray(data[ll[1] :], dtype=datatypes[2])
+ boundsdata = np.reshape(boundsdata, [ngridboxes, len(boundsdata) // ngridboxes])
if COORD0:
boundsdata = boundsdata * COORD0
@@ -157,20 +164,21 @@ def read_dimless_gbxboundaries_binary(filename, COORD0=False,
else:
return gbxbounds
+
def halfcoords_from_gbxbounds(gbxbounds, isprint=True):
- ''' returns half coords of gbx boundaries in lists obtained
- from gbxbounds dictionary '''
+ """returns half coords of gbx boundaries in lists obtained
+ from gbxbounds dictionary"""
boundsdata = np.asarray(list(gbxbounds.values()))
- zhalf = np.unique(np.sort(boundsdata[:,0]))
- zhalf = np.append(zhalf, np.amax(boundsdata[:,1]))
+ zhalf = np.unique(np.sort(boundsdata[:, 0]))
+ zhalf = np.append(zhalf, np.amax(boundsdata[:, 1]))
- xhalf = np.unique(np.sort(boundsdata[:,2]))
- xhalf = np.append(xhalf, np.amax(boundsdata[:,3]))
+ xhalf = np.unique(np.sort(boundsdata[:, 2]))
+ xhalf = np.append(xhalf, np.amax(boundsdata[:, 3]))
- yhalf = np.unique(np.sort(boundsdata[:,4]))
- yhalf = np.append(yhalf, np.amax(boundsdata[:,5]))
+ yhalf = np.unique(np.sort(boundsdata[:, 4]))
+ yhalf = np.append(yhalf, np.amax(boundsdata[:, 5]))
if isprint:
print("zhalf: ", zhalf)
@@ -179,12 +187,11 @@ def halfcoords_from_gbxbounds(gbxbounds, isprint=True):
return zhalf, xhalf, yhalf
-def plot_gridboxboundaries(constsfile, gridfile, binpath, savefig):
- plt.rcParams.update({'font.size': 14})
+def plot_gridboxboundaries(constsfile, gridfile, binpath, savefig):
+ plt.rcParams.update({"font.size": 14})
- zhalf, xhalf, yhalf = get_gridboxboundaries(gridfile,
- constsfile=constsfile)
+ zhalf, xhalf, yhalf = get_gridboxboundaries(gridfile, constsfile=constsfile)
halfs = [zhalf, xhalf, yhalf]
fulls, deltas = [], []
@@ -195,27 +202,38 @@ def plot_gridboxboundaries(constsfile, gridfile, binpath, savefig):
fig, axs = plt.subplots(nrows=1, ncols=3, figsize=(10, 5))
for i, crd in enumerate(["z", "x", "y"]):
- xlims = [0.8*np.amin(deltas[i])/1000, np.amax(deltas[i])/1000*1.2]
- ylims = [np.amin(halfs[i])/1000, np.amax(halfs[i])/1000]
- axs[i].scatter(deltas[i]/1000, fulls[i]/1000,
- color="k", label="centres")
- axs[i].hlines(halfs[i]/1000, xlims[0], xlims[1],
- color="grey", alpha=0.8, linewidth=0.8, label="boundaries")
+ xlims = [0.8 * np.amin(deltas[i]) / 1000, np.amax(deltas[i]) / 1000 * 1.2]
+ ylims = [np.amin(halfs[i]) / 1000, np.amax(halfs[i]) / 1000]
+ axs[i].scatter(deltas[i] / 1000, fulls[i] / 1000, color="k", label="centres")
+ axs[i].hlines(
+ halfs[i] / 1000,
+ xlims[0],
+ xlims[1],
+ color="grey",
+ alpha=0.8,
+ linewidth=0.8,
+ label="boundaries",
+ )
axs[i].set_xlim(xlims)
axs[i].set_ylim(ylims)
- axs[i].set_xlabel("gridbox spacing, \u0394 "+crd+" /km")
- axs[i].set_ylabel("gridbox centres, "+crd+"f /km")
+ axs[i].set_xlabel("gridbox spacing, \u0394 " + crd + " /km")
+ axs[i].set_ylabel("gridbox centres, " + crd + "f /km")
axs[i].legend()
fig.tight_layout()
if savefig:
- fig.savefig(binpath+"gridboxboundaries.png", dpi=400,
- bbox_inches="tight", facecolor='w', format="png")
- print("Figure .png saved as: "+binpath+"gridboxboundaries.png")
+ fig.savefig(
+ binpath + "gridboxboundaries.png",
+ dpi=400,
+ bbox_inches="tight",
+ facecolor="w",
+ format="png",
+ )
+ print("Figure .png saved as: " + binpath + "gridboxboundaries.png")
plt.show()
-def calc_domainvol(zhalf, xhalf, yhalf):
+def calc_domainvol(zhalf, xhalf, yhalf):
widths = []
for half in [zhalf, xhalf, yhalf]:
widths.append(np.amax(half) - np.amin(half))
@@ -224,22 +242,21 @@ def calc_domainvol(zhalf, xhalf, yhalf):
return domainvol
-def calc_gridboxvols(gbxbounds):
+def calc_gridboxvols(gbxbounds):
gbxvols = []
for gbxindex, bounds in gbxbounds.items():
zwidth = bounds[1] - bounds[0]
xwidth = bounds[3] - bounds[2]
ywidth = bounds[5] - bounds[4]
- gbxvols.append(zwidth * xwidth * ywidth )
+ gbxvols.append(zwidth * xwidth * ywidth)
return gbxvols
-def domaininfo(gbxbounds, isprint=True):
- zhalf, xhalf, yhalf = halfcoords_from_gbxbounds(gbxbounds,
- isprint=isprint)
+def domaininfo(gbxbounds, isprint=True):
+ zhalf, xhalf, yhalf = halfcoords_from_gbxbounds(gbxbounds, isprint=isprint)
domainvol = calc_domainvol(zhalf, xhalf, yhalf)
gridboxvols = calc_gridboxvols(gbxbounds)
@@ -247,8 +264,8 @@ def domaininfo(gbxbounds, isprint=True):
return domainvol, gridboxvols, ngridboxes
-def grid_dimensions(gbxbounds):
+def grid_dimensions(gbxbounds):
zhalf, xhalf, yhalf = halfcoords_from_gbxbounds(gbxbounds, isprint=False)
if len(zhalf) == 2:
@@ -263,38 +280,52 @@ def grid_dimensions(gbxbounds):
extents, spacings = [], []
for half in [zhalf, xhalf, yhalf]:
extents.append([np.amin(half), np.amax(half)])
- spacings.append(abs(half[1:]-half[:-1]))
+ spacings.append(abs(half[1:] - half[:-1]))
return extents, spacings, griddims
+
def print_domain_info(constsfile, gridfile):
- ''' prints information about domain reda from gridfile and constants file '''
+ """prints information about domain reda from gridfile and constants file"""
- isprint=True
+ isprint = True
COORD0 = get_COORD0_from_constsfile(constsfile)
- gbxbounds = read_dimless_gbxboundaries_binary(gridfile, COORD0,
- isprint=isprint)
+ gbxbounds = read_dimless_gbxboundaries_binary(gridfile, COORD0, isprint=isprint)
- domainvol, gridboxvols, ngridboxes = domaininfo(gbxbounds,
- isprint=isprint)
+ domainvol, gridboxvols, ngridboxes = domaininfo(gbxbounds, isprint=isprint)
xtns, spacings, griddims = grid_dimensions(gbxbounds)
ztot = abs(xtns[0][0] - xtns[0][1])
xtot = abs(xtns[1][0] - xtns[1][1])
ytot = abs(xtns[2][0] - xtns[2][1])
- inforstr = "\n------ DOMAIN / GRIDBOXES INFO ------\n"+\
- "------------- "+str(griddims)+"-D MODEL -------------\n"+\
- "domain dimensions: ({:3g}x{:3g}x{:3g})m^3\n".format(ztot, xtot, ytot)+\
- "domain no. gridboxes: "+str(len(spacings[0])) +\
- "x"+str(len(spacings[1]))+"x"+str(len(spacings[2]))+"\n"+\
- "domain z limits: ({:3g},{:3g})m\n".format(np.amin(xtns[0]),np.amax(xtns[0]))+\
- "domain x limits: ({:3g}, {:3g})m\n".format(np.amin(xtns[1]),np.amax(xtns[1]))+\
- "domain y limits: ({:3g}, {:3g})m\n".format(np.amin(xtns[2]), np.amax(xtns[2]))+\
- "mean gridbox z spacing: {:3g} m\n".format(np.mean(spacings[0]))+\
- "mean gridbox x spacing: {:3g} m\n".format(np.mean(spacings[1]))+\
- "mean gridbox y spacing: {:3g} m\n".format(np.mean(spacings[2]))+\
- "mean gridbox volume: {:3g}".format(np.mean(gridboxvols))+" m^3\n"+\
- "total domain volume: {:3g} m^3\n".format(domainvol)+\
- "total no. gridboxes: "+str(ngridboxes)+\
- "\n------------------------------------\n"
+ inforstr = (
+ "\n------ DOMAIN / GRIDBOXES INFO ------\n"
+ + "------------- "
+ + str(griddims)
+ + "-D MODEL -------------\n"
+ + "domain dimensions: ({:3g}x{:3g}x{:3g})m^3\n".format(ztot, xtot, ytot)
+ + "domain no. gridboxes: "
+ + str(len(spacings[0]))
+ + "x"
+ + str(len(spacings[1]))
+ + "x"
+ + str(len(spacings[2]))
+ + "\n"
+ + "domain z limits: ({:3g},{:3g})m\n".format(np.amin(xtns[0]), np.amax(xtns[0]))
+ + "domain x limits: ({:3g}, {:3g})m\n".format(
+ np.amin(xtns[1]), np.amax(xtns[1])
+ )
+ + "domain y limits: ({:3g}, {:3g})m\n".format(
+ np.amin(xtns[2]), np.amax(xtns[2])
+ )
+ + "mean gridbox z spacing: {:3g} m\n".format(np.mean(spacings[0]))
+ + "mean gridbox x spacing: {:3g} m\n".format(np.mean(spacings[1]))
+ + "mean gridbox y spacing: {:3g} m\n".format(np.mean(spacings[2]))
+ + "mean gridbox volume: {:3g}".format(np.mean(gridboxvols))
+ + " m^3\n"
+ + "total domain volume: {:3g} m^3\n".format(domainvol)
+ + "total no. gridboxes: "
+ + str(ngridboxes)
+ + "\n------------------------------------\n"
+ )
print(inforstr)
diff --git a/pySD/initsuperdropsbinary_src/__init__.py b/pySD/initsuperdropsbinary_src/__init__.py
index bda73f385..9b87b6265 100644
--- a/pySD/initsuperdropsbinary_src/__init__.py
+++ b/pySD/initsuperdropsbinary_src/__init__.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: __init__.py
Project: initsuperdropsbinary_src
@@ -15,13 +15,15 @@
Copyright (c) 2023 MPI-M, Clara Bayley
-----
File Description:
-'''
+"""
-__all__ = ["create_initsuperdrops",
- "read_initsuperdrops",
- "attrsgen",
- "probdists",
- "crdgens",
- "rgens",
- "dryrgens"]
+__all__ = [
+ "create_initsuperdrops",
+ "read_initsuperdrops",
+ "attrsgen",
+ "probdists",
+ "crdgens",
+ "rgens",
+ "dryrgens",
+]
diff --git a/pySD/initsuperdropsbinary_src/attrsgen.py b/pySD/initsuperdropsbinary_src/attrsgen.py
index 95345426e..4b8987cf2 100644
--- a/pySD/initsuperdropsbinary_src/attrsgen.py
+++ b/pySD/initsuperdropsbinary_src/attrsgen.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: attrsgen.py
Project: initsuperdropsbinary_src
@@ -17,121 +17,134 @@
File Description:
attrsgen generates multiple superdroplet
attributes given individual generators
-'''
+"""
import numpy as np
from ..gbxboundariesbinary_src import read_gbxboundaries as rgrid
+
class AttrsGenerator:
- ''' class for functions to generate attributes of
+ """class for functions to generate attributes of
superdroplets given the generators for independent
attributes e.g. for radius and coord3 in substantation
- of class'''
-
- def __init__(self, radiigen, dryradiigen, xiprobdist,
- coord3gen, coord1gen, coord2gen):
+ of class"""
- self.radiigen = radiigen # generates radius (solute + water)
- self.dryradiigen = dryradiigen # generates dry radius (-> solute mass)
- self.xiprobdist = xiprobdist # droplet size distribution (-> multiplicity)
+ def __init__(
+ self, radiigen, dryradiigen, xiprobdist, coord3gen, coord1gen, coord2gen
+ ):
+ self.radiigen = radiigen # generates radius (solute + water)
+ self.dryradiigen = dryradiigen # generates dry radius (-> solute mass)
+ self.xiprobdist = xiprobdist # droplet size distribution (-> multiplicity)
- self.coord3gen = coord3gen # generates spatial coordinate
+ self.coord3gen = coord3gen # generates spatial coordinate
self.coord1gen = coord1gen
self.coord2gen = coord2gen
- self.ncoordsgen = sum(x is not None for x in
- [coord3gen, coord2gen, coord1gen])
+ self.ncoordsgen = sum(x is not None for x in [coord3gen, coord2gen, coord1gen])
def mass_solutes(self, radii, RHO_SOL):
- ''' return the mass [Kg] of the solute in superdroplets given their
- dry radii [m] and solute density [Kg m^3]'''
+ """return the mass [Kg] of the solute in superdroplets given their
+ dry radii [m] and solute density [Kg m^3]"""
- dryradii = self.dryradiigen(radii) # [m]
+ dryradii = self.dryradiigen(radii) # [m]
dryradii = np.where(radii < dryradii, radii, dryradii)
- msols = 4.0/3.0 * np.pi * (dryradii**3) * RHO_SOL
+ msols = 4.0 / 3.0 * np.pi * (dryradii**3) * RHO_SOL
return msols # [Kg]
def multiplicities(self, radii, NUMCONC, samplevol):
- ''' Calculate the multiplicity of the dry radius of each
+ """Calculate the multiplicity of the dry radius of each
superdroplet given it's probability such that the total number
concentration [m^-3] of real droplets in the volume, vol, [m^3]
is about 'numconc'. Raise an error if any of the calculated
- mulitiplicities are zero '''
+ mulitiplicities are zero"""
- prob = self.xiprobdist(radii) # normalised prob distrib
+ prob = self.xiprobdist(radii) # normalised prob distrib
xi = np.rint(prob * NUMCONC * samplevol)
if any(xi == 0):
- num = len(xi[xi==0])
- errmsg = "ERROR, "+str(num)+" out of "+str(len(xi))+" SDs"+\
- " created with multiplicity = 0. Consider increasing numconc"+\
- " or changing range of radii sampled."
- raise ValueError(errmsg)
+ num = len(xi[xi == 0])
+ errmsg = (
+ "ERROR, "
+ + str(num)
+ + " out of "
+ + str(len(xi))
+ + " SDs"
+ + " created with multiplicity = 0. Consider increasing numconc"
+ + " or changing range of radii sampled."
+ )
+ raise ValueError(errmsg)
return np.array(xi, dtype=np.uint)
def check_coordsgen_matches_modeldimension(self, nspacedims):
-
if nspacedims != self.ncoordsgen:
- errmsg = str(self.ncoordsgen)+" coord generators specified "+\
- "but nspacedims = "+str(nspacedims)
+ errmsg = (
+ str(self.ncoordsgen)
+ + " coord generators specified "
+ + "but nspacedims = "
+ + str(nspacedims)
+ )
raise ValueError(errmsg)
def check_totalnumconc(self, multiplicities, NUMCONC, samplevol):
- '''check number concentration of real droplets calculated from
+ """check number concentration of real droplets calculated from
multiplicities is same as input value for number conc. Also check
total number of real droplets is within 10% of the expected
- value given the input number conc and sample volume '''
+ value given the input number conc and sample volume"""
nreals = np.rint(NUMCONC * samplevol)
calcnreals = np.rint(np.sum(multiplicities))
- calcnumconc = np.rint(calcnreals/samplevol)
+ calcnumconc = np.rint(calcnreals / samplevol)
if np.rint(NUMCONC) != calcnumconc:
- errmsg = "total real droplet concentration"+\
- " {:0g} != numconc, {:0g}".format(calcnumconc, NUMCONC)
- raise ValueError(errmsg)
+ errmsg = (
+ "total real droplet concentration"
+ + " {:0g} != numconc, {:0g}".format(calcnumconc, NUMCONC)
+ )
+ raise ValueError(errmsg)
- if abs(nreals - calcnreals) > 0.001*nreals:
- errmsg = "total no. real droplets, {:0g},".format(calcnreals)+\
- " not consistent with sample volume {:.3g} m^3".format(samplevol)
- raise ValueError(errmsg)
+ if abs(nreals - calcnreals) > 0.001 * nreals:
+ errmsg = "total no. real droplets, {:0g},".format(
+ calcnreals
+ ) + " not consistent with sample volume {:.3g} m^3".format(samplevol)
+ raise ValueError(errmsg)
- def print_totalconc(self, multiplicities, radii,
- mass_solutes, RHO_SOL, samplevol):
- ''' print statement of total num conc and mass conc '''
+ def print_totalconc(self, multiplicities, radii, mass_solutes, RHO_SOL, samplevol):
+ """print statement of total num conc and mass conc"""
def totmass(radius, msol, RHO_SOL):
- ''' total mass of droplets represented by a superdroplet
- droplet totmass = mass of water + solute '''
+ """total mass of droplets represented by a superdroplet
+ droplet totmass = mass of water + solute"""
- RHO_L = 998.203 # density of liquid water [kg/m^3]
- massconst = 4.0 / 3.0 * np.pi * radius * radius * radius * RHO_L
+ RHO_L = 998.203 # density of liquid water [kg/m^3]
+ massconst = 4.0 / 3.0 * np.pi * radius * radius * radius * RHO_L
density_factor = 1.0 - RHO_L / RHO_SOL
totmass = msol * density_factor + massconst
- return totmass * 1000 # [g]
+ return totmass * 1000 # [g]
- numconc = np.sum(multiplicities) / samplevol / 1e6 # [cm^-3]
+ numconc = np.sum(multiplicities) / samplevol / 1e6 # [cm^-3]
totmass = np.sum(totmass(radii, mass_solutes, RHO_SOL) * multiplicities)
- massconc = totmass /samplevol # [g/m^3]
+ massconc = totmass / samplevol # [g/m^3]
- msg = "--- total droplet concentration = "+\
- "{:1g}cm^-3 => {:.1g}g/m^3".format(numconc, massconc) +\
- ", in {:.3g}m^3 volume --- ".format(samplevol)
+ msg = (
+ "--- total droplet concentration = "
+ + "{:1g}cm^-3 => {:.1g}g/m^3".format(numconc, massconc)
+ + ", in {:.3g}m^3 volume --- ".format(samplevol)
+ )
print(msg)
def generate_attributes(self, nsupers, RHO_SOL, NUMCONC, gridboxbounds):
- ''' generate superdroplets (SDs) attributes that have dimensions
- by calling the appropraite generating functions'''
+ """generate superdroplets (SDs) attributes that have dimensions
+ by calling the appropraite generating functions"""
- gbxvol = rgrid.calc_domainvol(gridboxbounds[0:2],
- gridboxbounds[2:4],
- gridboxbounds[4:]) # [m^3]
- radii = self.radiigen(nsupers) # [m]
+ gbxvol = rgrid.calc_domainvol(
+ gridboxbounds[0:2], gridboxbounds[2:4], gridboxbounds[4:]
+ ) # [m^3]
+ radii = self.radiigen(nsupers) # [m]
mass_solutes = self.mass_solutes(radii, RHO_SOL) # [Kg]
@@ -139,30 +152,37 @@ def generate_attributes(self, nsupers, RHO_SOL, NUMCONC, gridboxbounds):
if nsupers > 0:
self.check_totalnumconc(multiplicities, NUMCONC, gbxvol)
- self.print_totalconc(multiplicities, radii, mass_solutes,
- RHO_SOL, gbxvol)
+ self.print_totalconc(multiplicities, radii, mass_solutes, RHO_SOL, gbxvol)
- return multiplicities, radii, mass_solutes # units [], [m], [Kg], [m]
+ return multiplicities, radii, mass_solutes # units [], [m], [Kg], [m]
def generate_coords(self, nsupers, nspacedims, gridboxbounds):
- ''' generate superdroplets (SDs) attributes that have dimensions
- by calling the appropraite generating functions'''
+ """generate superdroplets (SDs) attributes that have dimensions
+ by calling the appropraite generating functions"""
self.check_coordsgen_matches_modeldimension(nspacedims)
coord3, coord1, coord2 = np.array([]), np.array([]), np.array([])
if self.coord3gen:
- coord3range = [gridboxbounds[0], gridboxbounds[1]] # [min,max] coord3 to sample within
+ coord3range = [
+ gridboxbounds[0],
+ gridboxbounds[1],
+ ] # [min,max] coord3 to sample within
coord3 = self.coord3gen(nsupers, coord3range)
if self.coord1gen:
- coord1range = [gridboxbounds[2], gridboxbounds[3]] # [min,max] coord1 to sample within
+ coord1range = [
+ gridboxbounds[2],
+ gridboxbounds[3],
+ ] # [min,max] coord1 to sample within
coord1 = self.coord1gen(nsupers, coord1range)
if self.coord2gen:
- coord2range = [gridboxbounds[4], gridboxbounds[5]] # [min,max] coord2 to sample within
+ coord2range = [
+ gridboxbounds[4],
+ gridboxbounds[5],
+ ] # [min,max] coord2 to sample within
coord2 = self.coord2gen(nsupers, coord2range)
-
- return coord3, coord1, coord2 # units [m], [m], [m]
+ return coord3, coord1, coord2 # units [m], [m], [m]
diff --git a/pySD/initsuperdropsbinary_src/crdgens.py b/pySD/initsuperdropsbinary_src/crdgens.py
index afd23025b..02bf10ec7 100644
--- a/pySD/initsuperdropsbinary_src/crdgens.py
+++ b/pySD/initsuperdropsbinary_src/crdgens.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: crdgens.py
Project: initsuperdropsbinary_src
@@ -18,88 +18,87 @@
various ways of generating spatial
coordinates for superdroplet initial
conditions
-'''
+"""
import numpy as np
from ..gbxboundariesbinary_src import read_gbxboundaries as rgrid
+
def nsupers_at_domain_base(gridfile, constsfile, nsupers, zlim):
- ''' create dict for sd initialisation where nsupers
- only occur in gridboxes with upper bound <= zlim '''
+ """create dict for sd initialisation where nsupers
+ only occur in gridboxes with upper bound <= zlim"""
COORD0 = rgrid.get_COORD0_from_constsfile(constsfile)
- gbxbounds, ndims = rgrid.read_dimless_gbxboundaries_binary(gridfile,
- COORD0=COORD0,
- return_ndims=True,
- isprint=False)
+ gbxbounds, ndims = rgrid.read_dimless_gbxboundaries_binary(
+ gridfile, COORD0=COORD0, return_ndims=True, isprint=False
+ )
nsupersdict = {}
for ii in gbxbounds.keys():
gbx_zupper = gbxbounds[ii][1] # z upper bound of gridbox
- if (gbx_zupper <= zlim):
+ if gbx_zupper <= zlim:
nsupersdict[ii] = nsupers
else:
nsupersdict[ii] = 0
return nsupersdict
+
def nsupers_at_domain_top(gridfile, constsfile, nsupers, zlim):
- ''' create dict for sd initialisation where nsupers
- only occur in gridboxes with lower bound >= zlim '''
+ """create dict for sd initialisation where nsupers
+ only occur in gridboxes with lower bound >= zlim"""
COORD0 = rgrid.get_COORD0_from_constsfile(constsfile)
- gbxbounds, ndims = rgrid.read_dimless_gbxboundaries_binary(gridfile,
- COORD0=COORD0,
- return_ndims=True,
- isprint=False)
+ gbxbounds, ndims = rgrid.read_dimless_gbxboundaries_binary(
+ gridfile, COORD0=COORD0, return_ndims=True, isprint=False
+ )
nsupersdict = {}
for ii in gbxbounds.keys():
gbx_zlower = gbxbounds[ii][0] # z lower bound of gridbox
- if (gbx_zlower >= zlim):
+ if gbx_zlower >= zlim:
nsupersdict[ii] = nsupers
else:
nsupersdict[ii] = 0
return nsupersdict
+
class MonoCoordGen:
- ''' method to generate superdroplets with
- coord all equal to coord0 '''
+ """method to generate superdroplets with
+ coord all equal to coord0"""
def __init__(self, coord0):
-
self.coord0 = coord0
def __call__(self, nsupers, coordrange):
- ''' Returns coord for nsupers all
- with the value of coord0 '''
+ """Returns coord for nsupers all
+ with the value of coord0"""
- if (self.coord0 >= coordrange[0] and
- self.coord0 < coordrange[1]):
+ if self.coord0 >= coordrange[0] and self.coord0 < coordrange[1]:
attrs = np.full(nsupers, self.coord0)
else:
attrs = np.array([])
return attrs
+
class SampleCoordGen:
- ''' method to generate 'nsupers'
+ """method to generate 'nsupers'
no. of superdroplets' coord [m]
- by sampling in range bewteen coordspan '''
+ by sampling in range bewteen coordspan"""
def __init__(self, random):
-
self.random = random
def __call__(self, nsupers, coordrange):
- ''' Returns coord3 for nsupers
- sampled from coord3span [m]'''
+ """Returns coord3 for nsupers
+ sampled from coord3span [m]"""
if not self.random:
- coord = np.linspace(coordrange[0], coordrange[1],
- nsupers, endpoint=False)
+ coord = np.linspace(coordrange[0], coordrange[1], nsupers, endpoint=False)
else:
- coord = np.random.uniform(low=coordrange[0], high=coordrange[1],
- size=nsupers)
+ coord = np.random.uniform(
+ low=coordrange[0], high=coordrange[1], size=nsupers
+ )
return coord # units [m]
diff --git a/pySD/initsuperdropsbinary_src/create_initsuperdrops.py b/pySD/initsuperdropsbinary_src/create_initsuperdrops.py
index 6c4967e96..da0e01d7e 100644
--- a/pySD/initsuperdropsbinary_src/create_initsuperdrops.py
+++ b/pySD/initsuperdropsbinary_src/create_initsuperdrops.py
@@ -1,4 +1,7 @@
-'''
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
----- CLEO -----
File: create_initsuperdrops.py
Project: initsuperdropsbinary_src
@@ -6,25 +9,26 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Wednesday 17th April 2024
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
https://opensource.org/licenses/BSD-3-Clause
-----
-Copyright (c) 2023 MPI-M, Clara Bayley
------
File Description:
-'''
-
+"""
import numpy as np
from os.path import isfile
from .. import cxx2py, readconfigfile, writebinary
-from ..gbxboundariesbinary_src.read_gbxboundaries import read_dimless_gbxboundaries_binary
+from ..gbxboundariesbinary_src.read_gbxboundaries import (
+ read_dimless_gbxboundaries_binary,
+)
+
class ManyAttrs:
- '''store for lists of each attribute for all superdroplets '''
+ """store for lists of each attribute for all superdroplets"""
+
def __init__(self):
self.sdgbxindex = []
self.xi = []
@@ -34,8 +38,7 @@ def __init__(self):
self.coord1 = []
self.coord2 = []
- def set_attrlists(self, a, b, c,
- d, e, f, g):
+ def set_attrlists(self, a, b, c, d, e, f, g):
self.sdgbxindex = a
self.xi = b
self.radius = c
@@ -45,8 +48,8 @@ def set_attrlists(self, a, b, c,
self.coord2 = g
def extend_attrlists(self, mia):
- ''' use an instance of ManyAttrs (mia) to
- extend lists in this instance '''
+ """use an instance of ManyAttrs (mia) to
+ extend lists in this instance"""
self.sdgbxindex.extend(mia.sdgbxindex)
self.xi.extend(mia.xi)
self.radius.extend(mia.radius)
@@ -55,69 +58,78 @@ def extend_attrlists(self, mia):
self.coord1.extend(mia.coord1)
self.coord2.extend(mia.coord2)
+
def initSDsinputsdict(configfile, constsfile):
- ''' create values from constants file & config file
- required as inputs to create initial
- superdroplet conditions '''
-
- consts = cxx2py.read_cxxconsts_into_floats(constsfile)
- mconsts = cxx2py.derive_more_floats(consts)
- config = readconfigfile.read_configparams_into_floats(configfile)
-
- inputs = {
- # for creating SD attribute distirbutions
- "nspacedims": int(config["nspacedims"]),
- "RHO_L": consts["RHO_L"], # liquid density [Kg/m^3]
- "RHO_SOL": consts["RHO_SOL"], # solute density [Kg/m^3]
-
- # for de-dimensionalising attributes
- "R0":consts["R0"], # droplet radius lengthscale [m]
- "RHO0": mconsts["RHO0"], # characteristic density scale [Kg/m^3]
- "MASS0": mconsts["MASS0"], # characteristic mass scale [Kg]
- "COORD0": mconsts["COORD0"], # z coordinate lengthscale [m]
- }
-
- return inputs
-
-def is_sdgbxindex_correct(gridboxbounds, coord3, coord1, coord2,
- gbxindex, sdgbxindex):
- ''' rasises error if superdroplet coords [m] lie outside gridbox bounds [m]
- or if gridbox index not equal to superdroplet's. '''
-
- errmsg = None
- for i, coord in enumerate([coord3, coord1, coord2]):
- if (coord < gridboxbounds[2*i]).any():
- errmsg = "superdroplet coord"+str(i+1)+" below lower"+\
- " limit of gridbox it's associated with"
- elif (coord >= gridboxbounds[2*i+1]).any():
- errmsg = "superdroplet coord"+str(i+1)+" above upper"+\
- " limit of gridbox it's associated with"
- if errmsg:
- raise ValueError(errmsg)
-
- elif (sdgbxindex != gbxindex).any():
- errmsg = "superdroplet gridbox index not the same as"+\
- " gridbox it should be associated with"
- raise ValueError(errmsg)
-
-def dimless_superdropsattrs(nsupers, initattrsgen, inputs, gbxindex,
- gridboxbounds, NUMCONC):
- ''' use superdroplet attribute generator "initattrsgen"
+ """create values from constants file & config file
+ required as inputs to create initial
+ superdroplet conditions"""
+
+ consts = cxx2py.read_cxxconsts_into_floats(constsfile)
+ mconsts = cxx2py.derive_more_floats(consts)
+ config = readconfigfile.read_configparams_into_floats(configfile)
+
+ inputs = {
+ # for creating SD attribute distirbutions
+ "nspacedims": int(config["nspacedims"]),
+ "RHO_L": consts["RHO_L"], # liquid density [Kg/m^3]
+ "RHO_SOL": consts["RHO_SOL"], # solute density [Kg/m^3]
+ # for de-dimensionalising attributes
+ "R0": consts["R0"], # droplet radius lengthscale [m]
+ "RHO0": mconsts["RHO0"], # characteristic density scale [Kg/m^3]
+ "MASS0": mconsts["MASS0"], # characteristic mass scale [Kg]
+ "COORD0": mconsts["COORD0"], # z coordinate lengthscale [m]
+ }
+
+ return inputs
+
+
+def is_sdgbxindex_correct(gridboxbounds, coord3, coord1, coord2, gbxindex, sdgbxindex):
+ """rasises error if superdroplet coords [m] lie outside gridbox bounds [m]
+ or if gridbox index not equal to superdroplet's."""
+
+ errmsg = None
+ for i, coord in enumerate([coord3, coord1, coord2]):
+ if (coord < gridboxbounds[2 * i]).any():
+ errmsg = (
+ "superdroplet coord"
+ + str(i + 1)
+ + " below lower"
+ + " limit of gridbox it's associated with"
+ )
+ elif (coord >= gridboxbounds[2 * i + 1]).any():
+ errmsg = (
+ "superdroplet coord"
+ + str(i + 1)
+ + " above upper"
+ + " limit of gridbox it's associated with"
+ )
+ if errmsg:
+ raise ValueError(errmsg)
+
+ elif (sdgbxindex != gbxindex).any():
+ errmsg = (
+ "superdroplet gridbox index not the same as"
+ + " gridbox it should be associated with"
+ )
+ raise ValueError(errmsg)
+
+
+def dimless_superdropsattrs(
+ nsupers, initattrsgen, inputs, gbxindex, gridboxbounds, NUMCONC
+):
+ """use superdroplet attribute generator "initattrsgen"
and settings from config and consts files to
- make dimensionless superdroplet attributes'''
+ make dimensionless superdroplet attributes"""
# generate attributes
- sdgbxindex = [gbxindex]*nsupers
- xi, radius, msol = initattrsgen.generate_attributes(nsupers,
- inputs["RHO_SOL"],
- NUMCONC,
- gridboxbounds)
- coord3, coord1, coord2 = initattrsgen.generate_coords(nsupers,
- inputs["nspacedims"],
- gridboxbounds)
- is_sdgbxindex_correct(gridboxbounds,
- coord3, coord1, coord2,
- gbxindex, sdgbxindex)
+ sdgbxindex = [gbxindex] * nsupers
+ xi, radius, msol = initattrsgen.generate_attributes(
+ nsupers, inputs["RHO_SOL"], NUMCONC, gridboxbounds
+ )
+ coord3, coord1, coord2 = initattrsgen.generate_coords(
+ nsupers, inputs["nspacedims"], gridboxbounds
+ )
+ is_sdgbxindex_correct(gridboxbounds, coord3, coord1, coord2, gbxindex, sdgbxindex)
# de-dimsionalise attributes
radius = radius / inputs["R0"]
@@ -127,156 +139,183 @@ def dimless_superdropsattrs(nsupers, initattrsgen, inputs, gbxindex,
coord2 = coord2 / inputs["COORD0"]
attrs4gbx = ManyAttrs()
- attrs4gbx.set_attrlists(sdgbxindex, xi, radius, msol,
- coord3, coord1, coord2)
+ attrs4gbx.set_attrlists(sdgbxindex, xi, radius, msol, coord3, coord1, coord2)
return attrs4gbx
-def create_allsuperdropattrs(nsupersdict, initattrsgen,
- gbxbounds, inputs, NUMCONC):
- ''' returns lists for attributes of all SDs in domain called attrs'''
- attrs = ManyAttrs() # lists of attrs for SDs in domain
+def create_allsuperdropattrs(nsupersdict, initattrsgen, gbxbounds, inputs, NUMCONC):
+ """returns lists for attributes of all SDs in domain called attrs"""
+
+ attrs = ManyAttrs() # lists of attrs for SDs in domain
- for gbxindex, gridboxbounds in gbxbounds.items():
+ for gbxindex, gridboxbounds in gbxbounds.items():
+ nsupers = nsupersdict[gbxindex]
+ attrs4gbx = dimless_superdropsattrs(
+ nsupers, initattrsgen, inputs, gbxindex, gridboxbounds, NUMCONC
+ ) # lists of attrs for SDs in gridbox
- nsupers = nsupersdict[gbxindex]
- attrs4gbx = dimless_superdropsattrs(nsupers, initattrsgen, inputs,
- gbxindex, gridboxbounds, NUMCONC) # lists of attrs for SDs in gridbox
+ attrs.extend_attrlists(attrs4gbx)
- attrs.extend_attrlists(attrs4gbx)
+ return attrs
- return attrs
def set_arraydtype(arr, dtype):
+ og = type(arr[0])
+ if og != dtype:
+ arr = np.array(arr, dtype=dtype)
- og = type(arr[0])
- if og != dtype:
- arr = np.array(arr, dtype=dtype)
+ warning = (
+ "WARNING! dtype of attributes is being changed!"
+ + " from "
+ + str(og)
+ + " to "
+ + str(dtype)
+ )
+ raise ValueError(warning)
- warning = "WARNING! dtype of attributes is being changed!"+\
- " from "+str(og)+" to "+str(dtype)
- raise ValueError(warning)
+ return arr
- return arr
def ctype_compatible_attrs(attrs):
- ''' make list from arrays of SD attributes that are
- compatible with c type expected by SDM e.g. unsigned
- long ints for xi, doubles for radius and msol'''
-
- datatypes = [np.uintc, np.uint, np.double, np.double]
- datatypes += [np.double]*3 # coords datatype
+ """make list from arrays of SD attributes that are
+ compatible with c type expected by SDM e.g. unsigned
+ long ints for xi, doubles for radius and msol"""
- attrs.sdgbxindex = list(set_arraydtype(attrs.sdgbxindex, datatypes[0]))
- attrs.xi = list(set_arraydtype(attrs.xi, datatypes[1]))
- attrs.radius = list(set_arraydtype(attrs.radius, datatypes[2]))
- attrs.msol = list(set_arraydtype(attrs.msol, datatypes[3]))
+ datatypes = [np.uintc, np.uint, np.double, np.double]
+ datatypes += [np.double] * 3 # coords datatype
- datalist = attrs.sdgbxindex + attrs.xi + attrs.radius + attrs.msol
+ attrs.sdgbxindex = list(set_arraydtype(attrs.sdgbxindex, datatypes[0]))
+ attrs.xi = list(set_arraydtype(attrs.xi, datatypes[1]))
+ attrs.radius = list(set_arraydtype(attrs.radius, datatypes[2]))
+ attrs.msol = list(set_arraydtype(attrs.msol, datatypes[3]))
- if any(attrs.coord3):
- # make coord3 compatible if there is data for it (>= 1-D model)
- attrs.coord3 = list(set_arraydtype(attrs.coord3, datatypes[4]))
- datalist += attrs.coord3
+ datalist = attrs.sdgbxindex + attrs.xi + attrs.radius + attrs.msol
- if any(attrs.coord1):
- # make coord1 compatible if there is data for it and coord3 (>= 2-D model)
- attrs.coord1 = list(set_arraydtype(attrs.coord1, datatypes[4]))
- datalist += attrs.coord1
+ if any(attrs.coord3):
+ # make coord3 compatible if there is data for it (>= 1-D model)
+ attrs.coord3 = list(set_arraydtype(attrs.coord3, datatypes[4]))
+ datalist += attrs.coord3
- if any(attrs.coord2):
- # make coord2 compatible if there is data for it and coord3 and coord2 (>= 3-D model)
- attrs.coord2 = list(set_arraydtype(attrs.coord2, datatypes[4]))
- datalist += attrs.coord2
+ if any(attrs.coord1):
+ # make coord1 compatible if there is data for it and coord3 (>= 2-D model)
+ attrs.coord1 = list(set_arraydtype(attrs.coord1, datatypes[4]))
+ datalist += attrs.coord1
- return datalist, datatypes
+ if any(attrs.coord2):
+ # make coord2 compatible if there is data for it and coord3 and coord2 (>= 3-D model)
+ attrs.coord2 = list(set_arraydtype(attrs.coord2, datatypes[4]))
+ datalist += attrs.coord2
-def check_datashape(data, ndata, nspacedims):
- ''' make sure each superdroplet attribute in data has length stated
- in ndata and that this length is compatible with the nummber of
- attributes and superdroplets expected given ndata'''
-
- err=''
- if any([n != ndata[0] for n in ndata[:4+nspacedims]]):
+ return datalist, datatypes
- err += "\n------ ERROR! -----\n"+\
- "not all variables in data are same length, ndata = "+\
- str(ndata[:4+nspacedims])+"\n---------------------\n"
- if len(data) != np.sum(ndata):
- err += "inconsistent dimensions of data: "+str(np.shape(data))+", and"+\
- " data per attribute: "+str(ndata)+". data should be 1D with"+\
- " shape: num_attributes * nsupers. data should be list of"+\
- " [nsupers]*num_attributes."
+def check_datashape(data, ndata, nspacedims):
+ """make sure each superdroplet attribute in data has length stated
+ in ndata and that this length is compatible with the nummber of
+ attributes and superdroplets expected given ndata"""
+
+ err = ""
+ if any([n != ndata[0] for n in ndata[: 4 + nspacedims]]):
+ err += (
+ "\n------ ERROR! -----\n"
+ + "not all variables in data are same length, ndata = "
+ + str(ndata[: 4 + nspacedims])
+ + "\n---------------------\n"
+ )
+
+ if len(data) != np.sum(ndata):
+ err += (
+ "inconsistent dimensions of data: "
+ + str(np.shape(data))
+ + ", and"
+ + " data per attribute: "
+ + str(ndata)
+ + ". data should be 1D with"
+ + " shape: num_attributes * nsupers. data should be list of"
+ + " [nsupers]*num_attributes."
+ )
+
+ if err:
+ raise ValueError(err)
- if err:
- raise ValueError(err)
def nsupers_pergridboxdict(nsupers, gbxbounds):
+ if isinstance(nsupers, int):
+ nsupersdict = {}
+ for key in gbxbounds.keys():
+ nsupersdict[key] = nsupers
+ return nsupersdict
+
+ elif isinstance(nsupers, dict):
+ if nsupers.keys() != gbxbounds.keys():
+ errmsg = "keys for nsupers dict don't match gridbox indexes"
+ raise ValueError(errmsg)
+ else:
+ return nsupers
- if type(nsupers) == int:
- nsupersdict = {}
- for key in gbxbounds.keys():
- nsupersdict[key] = nsupers
- return nsupersdict
-
- elif type(nsupers) == dict:
- if nsupers.keys() != gbxbounds.keys():
- errmsg = "keys for nsupers dict don't match gridbox indexes"
- raise ValueError(errmsg)
else:
- return nsupers
-
- else:
- errmsg = "nsupers must be either dict or int"
- raise ValueError(errmsg)
-
-
-def write_initsuperdrops_binary(initsupersfile, initattrsgen, configfile,
- constsfile, gridfile, nsupers, NUMCONC):
- ''' de-dimensionalise attributes in initattrsgen and then write to
- to a binary file, "initsupersfile", with some metadata '''
-
- if not isfile(gridfile):
- errmsg = "gridfile not found, but must be"+\
- " created before initsupersfile can be"
- raise ValueError(errmsg)
-
- inputs = initSDsinputsdict(configfile, constsfile)
- gbxbounds = read_dimless_gbxboundaries_binary(gridfile,
- COORD0=inputs["COORD0"],
- isprint=False)
- nsupersdict = nsupers_pergridboxdict(nsupers, gbxbounds)
-
- attrs = create_allsuperdropattrs(nsupersdict, initattrsgen,
- gbxbounds, inputs, NUMCONC)
-
- ndata = [len(dt) for dt in [attrs.sdgbxindex, attrs.xi,
- attrs.radius, attrs.msol, attrs.coord3,
- attrs.coord1, attrs.coord2]]
-
- data, datatypes = ctype_compatible_attrs(attrs)
- check_datashape(data, ndata, inputs["nspacedims"])
-
- units = [b' ', b' ', b'm', b'g']
- units += [b'm']*3 # coords units
-
- scale_factors = [1.0, 1.0, inputs["R0"], inputs["MASS0"]]
- scale_factors += [inputs["COORD0"]]*3 # coords scale factors
- scale_factors = np.asarray(scale_factors, dtype=np.double)
-
- metastr = 'Variables in this file are Superdroplet attributes:'
- if initattrsgen.coord3gen:
- if initattrsgen.coord1gen:
- if initattrsgen.coord2gen:
- metastr += ' [sdgbxindex, xi, radius, msol, coord3, coord1, coord2]'
- else:
- metastr += ' [sdgbxindex, xi, radius, msol, coord3, coord1]'
+ errmsg = "nsupers must be either dict or int"
+ raise ValueError(errmsg)
+
+
+def write_initsuperdrops_binary(
+ initsupersfile, initattrsgen, configfile, constsfile, gridfile, nsupers, NUMCONC
+):
+ """de-dimensionalise attributes in initattrsgen and then write to
+ to a binary file, "initsupersfile", with some metadata"""
+
+ if not isfile(gridfile):
+ errmsg = (
+ "gridfile not found, but must be" + " created before initsupersfile can be"
+ )
+ raise ValueError(errmsg)
+
+ inputs = initSDsinputsdict(configfile, constsfile)
+ gbxbounds = read_dimless_gbxboundaries_binary(
+ gridfile, COORD0=inputs["COORD0"], isprint=False
+ )
+ nsupersdict = nsupers_pergridboxdict(nsupers, gbxbounds)
+
+ attrs = create_allsuperdropattrs(
+ nsupersdict, initattrsgen, gbxbounds, inputs, NUMCONC
+ )
+
+ ndata = [
+ len(dt)
+ for dt in [
+ attrs.sdgbxindex,
+ attrs.xi,
+ attrs.radius,
+ attrs.msol,
+ attrs.coord3,
+ attrs.coord1,
+ attrs.coord2,
+ ]
+ ]
+
+ data, datatypes = ctype_compatible_attrs(attrs)
+ check_datashape(data, ndata, inputs["nspacedims"])
+
+ units = [b" ", b" ", b"m", b"g"]
+ units += [b"m"] * 3 # coords units
+
+ scale_factors = [1.0, 1.0, inputs["R0"], inputs["MASS0"]]
+ scale_factors += [inputs["COORD0"]] * 3 # coords scale factors
+ scale_factors = np.asarray(scale_factors, dtype=np.double)
+
+ metastr = "Variables in this file are Superdroplet attributes:"
+ if initattrsgen.coord3gen:
+ if initattrsgen.coord1gen:
+ if initattrsgen.coord2gen:
+ metastr += " [sdgbxindex, xi, radius, msol, coord3, coord1, coord2]"
+ else:
+ metastr += " [sdgbxindex, xi, radius, msol, coord3, coord1]"
+ else:
+ metastr += " [sdgbxindex, xi, radius, msol, coord3]"
else:
- metastr += ' [sdgbxindex, xi, radius, msol, coord3]'
- else:
- metastr += ' [sdgbxindex, xi, radius, msol]'
+ metastr += " [sdgbxindex, xi, radius, msol]"
- writebinary.writebinary(initsupersfile, data, ndata, datatypes,
- units, scale_factors, metastr)
+ writebinary.writebinary(
+ initsupersfile, data, ndata, datatypes, units, scale_factors, metastr
+ )
diff --git a/pySD/initsuperdropsbinary_src/dryrgens.py b/pySD/initsuperdropsbinary_src/dryrgens.py
index 0ad99e7d9..86ca1e1cc 100644
--- a/pySD/initsuperdropsbinary_src/dryrgens.py
+++ b/pySD/initsuperdropsbinary_src/dryrgens.py
@@ -1,4 +1,7 @@
-'''
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
----- CLEO -----
File: dryrgens.py
Project: initsuperdropsbinary_src
@@ -6,30 +9,26 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Wednesday 27th December 2023
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
https://opensource.org/licenses/BSD-3-Clause
-----
-Copyright (c) 2023 MPI-M, Clara Bayley
------
File Description:
various ways of generating dryradii of
superdroplets for their initial conditions
-'''
+"""
-import numpy as np
class ScaledRadiiGen:
- '''method to generate superdroplet dryradii that
- are the radii divided by a scale factor 'sf' '''
-
- def __init__(self, scale_factor):
+ """method to generate superdroplet dryradii that
+ are the radii divided by a scale factor 'sf'"""
- self.sf = scale_factor
+ def __init__(self, scale_factor):
+ self.sf = scale_factor
- def __call__(self, radii):
- ''' Returns dryradii for nsupers [m]'''
+ def __call__(self, radii):
+ """Returns dryradii for nsupers [m]"""
- return radii / self.sf # units [m]
+ return radii / self.sf # units [m]
diff --git a/pySD/initsuperdropsbinary_src/probdists.py b/pySD/initsuperdropsbinary_src/probdists.py
index 875465e44..2da7c044e 100644
--- a/pySD/initsuperdropsbinary_src/probdists.py
+++ b/pySD/initsuperdropsbinary_src/probdists.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: probdists.py
Project: initsuperdropsbinary_src
@@ -18,186 +18,191 @@
Class calls' return normalised probability
of radii for various probability distributions
assuming bins are evenly spaced in log10(r)
-'''
+"""
import numpy as np
from scipy import special
+
class CombinedRadiiProbDistribs:
- ''' probability of radius from the sum of several
- probability distributions '''
+ """probability of radius from the sum of several
+ probability distributions"""
+
+ def __init__(self, probdistribs, scalefacs):
+ self.probdistribs = probdistribs
+ self.scalefacs = scalefacs
- def __init__(self, probdistribs, scalefacs):
- self.probdistribs = probdistribs
- self.scalefacs = scalefacs
+ if len(scalefacs) != len(probdistribs):
+ errmsg = "relative height of each probability distribution must be given"
+ raise ValueError(errmsg)
- if len(scalefacs) != len(probdistribs):
- errmsg = "relative height of each probability distribution must be given"
- raise ValueError(errmsg)
+ def __call__(self, radii):
+ """returns distribution for radii given by the
+ sum of the distributions in probdistribs list"""
- def __call__(self, radii):
- ''' returns distribution for radii given by the
- sum of the distributions in probdistribs list '''
+ probs = np.zeros(radii.shape)
+ for distrib, sf in zip(self.probdistribs, self.scalefacs):
+ probs += sf * distrib(radii)
- probs = np.zeros(radii.shape)
- for distrib, sf in zip(self.probdistribs, self.scalefacs):
- probs += sf * distrib(radii)
+ return probs / np.sum(probs) # normalise so sum(prob) = 1
- return probs / np.sum(probs) # normalise so sum(prob) = 1
class DiracDelta:
- ''' probability of radius nonzero if it is
- closest value in sample of radii to r0 '''
+ """probability of radius nonzero if it is
+ closest value in sample of radii to r0"""
- def __init__(self, r0):
+ def __init__(self, r0):
+ self.r0 = r0
- self.r0 = r0
+ def __call__(self, radii):
+ """Returns probability of radius in radii sample for
+ discrete version of dirac delta function centred on
+ value of r in radii closest to r0. For each radius in radii,
+ probability of that radius = 0 if it's not the closest value
+ in radii to r0. If it is the closest, the probability is maximal
+ (ie. prob = 1 and is then re-normalised such that sum of the
+ probalilities over the sample = 1)"""
- def __call__(self, radii):
- ''' Returns probability of radius in radii sample for
- discrete version of dirac delta function centred on
- value of r in radii closest to r0. For each radius in radii,
- probability of that radius = 0 if it's not the closest value
- in radii to r0. If it is the closest, the probability is maximal
- (ie. prob = 1 and is then re-normalised such that sum of the
- probalilities over the sample = 1) '''
+ if radii.any():
+ diff = np.abs(radii - self.r0)
- if radii.any():
- diff = np.abs(radii - self.r0)
+ probs = np.where(diff == np.min(diff), 1, 0)
- probs = np.where(diff == np.min(diff), 1, 0)
+ return probs / np.sum(probs) # normalise so sum(prob) = 1
+ else:
+ return np.array([])
- return probs / np.sum(probs) #normalise so sum(prob) = 1
- else:
- return np.array([])
class VolExponential:
- ''' probability of radius given by exponential in
- volume distribution as defined by Shima et al. (2009) '''
+ """probability of radius given by exponential in
+ volume distribution as defined by Shima et al. (2009)"""
- def __init__(self, radius0, rspan):
+ def __init__(self, radius0, rspan):
+ self.radius0 = radius0 # peak of volume exponential distribution [m]
+ self.rspan = rspan
- self.radius0 = radius0 # peak of volume exponential distribution [m]
- self.rspan = rspan
+ def __call__(self, radii):
+ """Returns probability of eaach radius in radii according to
+ distribution where probability of volume is exponential and bins
+ for radii are evently spaced in ln(r).
+ typical parameter values:
+ radius0 = 30.531e-6 # [m]
+ numconc = 2**(23) # [m^-3]"""
- def __call__(self, radii):
- ''' Returns probability of eaach radius in radii according to
- distribution where probability of volume is exponential and bins
- for radii are evently spaced in ln(r).
- typical parameter values:
- radius0 = 30.531e-6 # [m]
- numconc = 2**(23) # [m^-3] '''
+ rwdth = np.log(
+ self.rspan[-1] / self.rspan[0]
+ ) # assume equally spaced bins in ln(r)
- rwdth = np.log(self.rspan[-1]/self.rspan[0]) # assume equally spaced bins in ln(r)
+ dn_dV = np.exp(-((radii / self.radius0) ** 3)) # prob density P(volume)
- dn_dV = np.exp(-(radii/self.radius0)**3) # prob density P(volume)
+ probs = rwdth * radii**3 * dn_dV # prob density * ln(r) bin width
- probs = rwdth * radii**3 * dn_dV # prob density * ln(r) bin width
+ return probs / np.sum(probs) # normalise so sum(prob) = 1
- return probs / np.sum(probs) # normalise so sum(prob) = 1
class LnNormal:
- ''' probability of radius given by lognormal distribution
- as defined by section 5.2.3 of "An Introduction to clouds from
- the Microscale to Climate" by Lohmann, Luond and Mahrt and radii
- sampled from evenly spaced bins in ln(r).
- typical parameter values:
- geomeans = [0.02e-6, 0.2e-6, 3.5e-6] # [m]
- geosigs = [1.55, 2.3, 2]
- scalefacs = [1e6, 0.3e6, 0.025e6]
- numconc = 1e9 # [m^-3]'''
-
- def __init__(self, geomeans, geosigs, scalefacs):
-
- nmodes = len(geomeans)
- if nmodes != len(geosigs) or nmodes != len(scalefacs):
- errmsg = "parameters for number of lognormal modes is not consistent"
- raise ValueError(errmsg)
- else:
- self.nmodes = nmodes
- self.geomeans = geomeans
- self.geosigs = geosigs
- self.scalefacs = scalefacs
-
- def __call__(self, radii):
- ''' Returns probability of each radius in radii derived
- from superposition of Logarithmic (in e) Normal Distributions '''
-
- probs = np.zeros(radii.shape)
- for n in range(self.nmodes):
- probs += self.lnnormaldist(radii, self.scalefacs[n],
- self.geomeans[n], self.geosigs[n])
-
- return probs / np.sum(probs) # normalise so sum(prob) = 1
-
- def lnnormaldist(self, radii, scalefac, geomean, geosig):
- ''' calculate probability of radii given the paramters of a
- lognormal dsitribution accordin to equation 5.8 of "An
- Introduction to clouds from the Microscale to Climate"
- by Lohmann, Luond and Mahrt '''
-
- sigtilda = np.log(geosig)
- mutilda = np.log(geomean)
-
- norm = scalefac / (np.sqrt(2*np.pi)*sigtilda)
- exponent = -(np.log(radii)-mutilda)**2/(2*sigtilda**2)
-
- dn_dlnr = norm*np.exp(exponent) # eq.5.8 [lohmann intro 2 clouds]
-
- return dn_dlnr
+ """probability of radius given by lognormal distribution
+ as defined by section 5.2.3 of "An Introduction to clouds from
+ the Microscale to Climate" by Lohmann, Luond and Mahrt and radii
+ sampled from evenly spaced bins in ln(r).
+ typical parameter values:
+ geomeans = [0.02e-6, 0.2e-6, 3.5e-6] # [m]
+ geosigs = [1.55, 2.3, 2]
+ scalefacs = [1e6, 0.3e6, 0.025e6]
+ numconc = 1e9 # [m^-3]"""
-class ClouddropsHansenGamma:
- ''' probability of radius according to gamma distribution for
- shallow cumuli cloud droplets from Poertge et al. 2023 '''
+ def __init__(self, geomeans, geosigs, scalefacs):
+ nmodes = len(geomeans)
+ if nmodes != len(geosigs) or nmodes != len(scalefacs):
+ errmsg = "parameters for number of lognormal modes is not consistent"
+ raise ValueError(errmsg)
+ else:
+ self.nmodes = nmodes
+ self.geomeans = geomeans
+ self.geosigs = geosigs
+ self.scalefacs = scalefacs
- def __init__(self, reff, nueff):
- self.reff = reff
- self.nueff = nueff
+ def __call__(self, radii):
+ """Returns probability of each radius in radii derived
+ from superposition of Logarithmic (in e) Normal Distributions"""
- def __call__(self, radii):
- ''' return gamma distribution for cloud droplets
- given radius [m] using parameters from Poertge
- et al. 2023 for shallow cumuli (figure 12).
- typical values:
- reff = 7e-6 #[m]
- nueff = 0.08 # [] '''
+ probs = np.zeros(radii.shape)
+ for n in range(self.nmodes):
+ probs += self.lnnormaldist(
+ radii, self.scalefacs[n], self.geomeans[n], self.geosigs[n]
+ )
- xp = (1-2*self.nueff)/self.nueff
- n0const = (self.reff*self.nueff)**(-xp)
- n0const = n0const / special.gamma(xp)
+ return probs / np.sum(probs) # normalise so sum(prob) = 1
- term1 = radii**((1-3*self.nueff)/self.nueff)
- term2 = np.exp(-radii/(self.reff*self.nueff))
+ def lnnormaldist(self, radii, scalefac, geomean, geosig):
+ """calculate probability of radii given the paramters of a
+ lognormal dsitribution accordin to equation 5.8 of "An
+ Introduction to clouds from the Microscale to Climate"
+ by Lohmann, Luond and Mahrt"""
- probs = n0const * term1 * term2 # dn_dr [prob m^-1]
+ sigtilda = np.log(geosig)
+ mutilda = np.log(geomean)
- return probs / np.sum(probs) # normalise so sum(prob) = 1
+ norm = scalefac / (np.sqrt(2 * np.pi) * sigtilda)
+ exponent = -((np.log(radii) - mutilda) ** 2) / (2 * sigtilda**2)
-class RaindropsGeoffroyGamma:
- ''' probability of radius given gamma distribution for
- shallow cumuli rain droplets from Geoffroy et al. 2014 '''
+ dn_dlnr = norm * np.exp(exponent) # eq.5.8 [lohmann intro 2 clouds]
- def __init__(self, nrain, qrain, dvol):
+ return dn_dlnr
- self.nrain = nrain # raindrop concentration [ndrops/m^3]
- self.qrain = qrain # rainwater content [g/m^3]
- self.dvol = dvol # volume mean raindrop diameter [m]
- def __call__(self, radii):
- ''' returns probability of each radius according to a
- gamma distribution for rain droplets using parameters
- from Geoffroy et al. 2014 for precipitating shallow
- cumuli RICO (see figure 3 and equations 2,3 and 5).
- typical parameter values:
- nrain = 3 / 0.001 # [ndrops/m^3]
- qrain = 0.9 # [g/m^3]
- dvol = 800e-6 #[m] '''
+class ClouddropsHansenGamma:
+ """probability of radius according to gamma distribution for
+ shallow cumuli cloud droplets from Poertge et al. 2023"""
+
+ def __init__(self, reff, nueff):
+ self.reff = reff
+ self.nueff = nueff
+
+ def __call__(self, radii):
+ """return gamma distribution for cloud droplets
+ given radius [m] using parameters from Poertge
+ et al. 2023 for shallow cumuli (figure 12).
+ typical values:
+ reff = 7e-6 #[m]
+ nueff = 0.08 # []"""
+
+ xp = (1 - 2 * self.nueff) / self.nueff
+ n0const = (self.reff * self.nueff) ** (-xp)
+ n0const = n0const / special.gamma(xp)
- nu = 18/((self.nrain*self.qrain)**0.25) # []
- lamda = (nu*(nu+1)*(nu+2))**(1/3) / self.dvol # [m^-1]
- const = self.nrain * lamda**nu / special.gamma(nu)
+ term1 = radii ** ((1 - 3 * self.nueff) / self.nueff)
+ term2 = np.exp(-radii / (self.reff * self.nueff))
- diam = 2*radii #[m]
- probs = const * diam**(nu-1) * np.exp(-lamda*diam) # dn_dr [prob m^-1]
+ probs = n0const * term1 * term2 # dn_dr [prob m^-1]
- return probs / np.sum(probs) # normalise so sum(prob) = 1
+ return probs / np.sum(probs) # normalise so sum(prob) = 1
+
+
+class RaindropsGeoffroyGamma:
+ """probability of radius given gamma distribution for
+ shallow cumuli rain droplets from Geoffroy et al. 2014"""
+
+ def __init__(self, nrain, qrain, dvol):
+ self.nrain = nrain # raindrop concentration [ndrops/m^3]
+ self.qrain = qrain # rainwater content [g/m^3]
+ self.dvol = dvol # volume mean raindrop diameter [m]
+
+ def __call__(self, radii):
+ """returns probability of each radius according to a
+ gamma distribution for rain droplets using parameters
+ from Geoffroy et al. 2014 for precipitating shallow
+ cumuli RICO (see figure 3 and equations 2,3 and 5).
+ typical parameter values:
+ nrain = 3 / 0.001 # [ndrops/m^3]
+ qrain = 0.9 # [g/m^3]
+ dvol = 800e-6 #[m]"""
+
+ nu = 18 / ((self.nrain * self.qrain) ** 0.25) # []
+ lamda = (nu * (nu + 1) * (nu + 2)) ** (1 / 3) / self.dvol # [m^-1]
+ const = self.nrain * lamda**nu / special.gamma(nu)
+
+ diam = 2 * radii # [m]
+ probs = const * diam ** (nu - 1) * np.exp(-lamda * diam) # dn_dr [prob m^-1]
+
+ return probs / np.sum(probs) # normalise so sum(prob) = 1
diff --git a/pySD/initsuperdropsbinary_src/read_initsuperdrops.py b/pySD/initsuperdropsbinary_src/read_initsuperdrops.py
index 89213c83c..7a53668a2 100644
--- a/pySD/initsuperdropsbinary_src/read_initsuperdrops.py
+++ b/pySD/initsuperdropsbinary_src/read_initsuperdrops.py
@@ -1,4 +1,7 @@
-'''
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
----- CLEO -----
File: read_initsuperdrops.py
Project: initsuperdropsbinary_src
@@ -6,17 +9,14 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Wednesday 10th January 2024
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
https://opensource.org/licenses/BSD-3-Clause
-----
-Copyright (c) 2023 MPI-M, Clara Bayley
------
File Description:
-'''
-
+"""
import numpy as np
import matplotlib.pyplot as plt
@@ -25,19 +25,24 @@
from ..readbinary import readbinary
from ..gbxboundariesbinary_src.read_gbxboundaries import get_gbxvols_from_gridfile
-def plot_initGBxs_distribs(configfile, constsfile, initsupersfile,
- gridfile, binpath, savefig, gbxs2plt):
- ''' plot initial superdroplet distribution from initsupersfile binary
- of every gridbox with index in gbx2plts '''
- plot_initGBxs_attrdistribs(configfile, constsfile, initsupersfile,
- gridfile, binpath, savefig, gbxs2plt)
- plot_initGBxs_dropletmasses(configfile, constsfile, initsupersfile,
- gridfile, binpath, savefig, gbxs2plt)
+def plot_initGBxs_distribs(
+ configfile, constsfile, initsupersfile, gridfile, binpath, savefig, gbxs2plt
+):
+ """plot initial superdroplet distribution from initsupersfile binary
+ of every gridbox with index in gbx2plts"""
+
+ plot_initGBxs_attrdistribs(
+ configfile, constsfile, initsupersfile, gridfile, binpath, savefig, gbxs2plt
+ )
+ plot_initGBxs_dropletmasses(
+ configfile, constsfile, initsupersfile, gridfile, binpath, savefig, gbxs2plt
+ )
+
def get_superdroplet_attributes(configfile, constsfile, initsupersfile):
- ''' get gridbox boundaries from binary file and
- re-dimensionalise usign COORD0 const from constsfile '''
+ """get gridbox boundaries from binary file and
+ re-dimensionalise usign COORD0 const from constsfile"""
inputs = initSDsinputsdict(configfile, constsfile)
@@ -52,124 +57,133 @@ def get_superdroplet_attributes(configfile, constsfile, initsupersfile):
return attrs
+
def read_dimless_superdrops_binary(filename, isprint=True):
- ''' return dimenionsless gbx boundaries by reading binary file'''
+ """return dimenionsless gbx boundaries by reading binary file"""
datatypes = [np.uintc, np.uint, np.double, np.double]
- datatypes += [np.double]*3
+ datatypes += [np.double] * 3
data, ndata_pervar = readbinary(filename, isprint=isprint)
- ll = [0,0,0,0,0,0] # indexs for division of data list between each variable
+ ll = [0, 0, 0, 0, 0, 0] # indexs for division of data list between each variable
for n in range(1, len(ndata_pervar)):
- ll[n-1] = np.sum(ndata_pervar[:n])
+ ll[n - 1] = np.sum(ndata_pervar[:n])
attrs = ManyAttrs()
- attrs.sdgbxindex = np.asarray(data[:ll[0]], dtype=datatypes[0])
- attrs.xi = np.asarray(data[ll[0]:ll[1]], dtype=datatypes[1])
- attrs.radius = np.asarray(data[ll[1]:ll[2]], dtype=datatypes[2])
- attrs.msol = np.asarray(data[ll[2]:ll[3]], dtype=datatypes[3])
- attrs.coord3 = np.asarray(data[ll[3]:ll[4]], dtype=datatypes[4])
- attrs.coord1 = np.asarray(data[ll[4]:ll[5]], dtype=datatypes[5])
- attrs.coord2 = np.asarray(data[ll[5]:], dtype=datatypes[6])
-
- print("attribute shapes: ", attrs.sdgbxindex.shape, attrs.xi.shape,
- attrs.radius.shape, attrs.msol.shape, attrs.coord3.shape,
- attrs.coord1.shape, attrs.coord2.shape)
+ attrs.sdgbxindex = np.asarray(data[: ll[0]], dtype=datatypes[0])
+ attrs.xi = np.asarray(data[ll[0] : ll[1]], dtype=datatypes[1])
+ attrs.radius = np.asarray(data[ll[1] : ll[2]], dtype=datatypes[2])
+ attrs.msol = np.asarray(data[ll[2] : ll[3]], dtype=datatypes[3])
+ attrs.coord3 = np.asarray(data[ll[3] : ll[4]], dtype=datatypes[4])
+ attrs.coord1 = np.asarray(data[ll[4] : ll[5]], dtype=datatypes[5])
+ attrs.coord2 = np.asarray(data[ll[5] :], dtype=datatypes[6])
+
+ print(
+ "attribute shapes: ",
+ attrs.sdgbxindex.shape,
+ attrs.xi.shape,
+ attrs.radius.shape,
+ attrs.msol.shape,
+ attrs.coord3.shape,
+ attrs.coord1.shape,
+ attrs.coord2.shape,
+ )
return attrs
-def totmass(radius, msol, RHO_L , RHO_SOL):
- ''' droplet totmass = mass of water + solute '''
- massconst = 4.0 / 3.0 * np.pi * radius * radius * radius * RHO_L
+
+def totmass(radius, msol, RHO_L, RHO_SOL):
+ """droplet totmass = mass of water + solute"""
+ massconst = 4.0 / 3.0 * np.pi * radius * radius * radius * RHO_L
density_factor = 1.0 - RHO_L / RHO_SOL
totmass = msol * density_factor + massconst
return totmass
+
def print_initSDs_infos(initSDsfile, configfile, constsfile, gridfile):
- gbxvols = np.asarray(get_gbxvols_from_gridfile(gridfile,
- constsfile=constsfile,
- isprint=False))
+ gbxvols = np.asarray(
+ get_gbxvols_from_gridfile(gridfile, constsfile=constsfile, isprint=False)
+ )
- attrs = get_superdroplet_attributes(configfile,
- constsfile,
- initSDsfile)
+ attrs = get_superdroplet_attributes(configfile, constsfile, initSDsfile)
xi = attrs.xi.flatten()
vol = np.sum(gbxvols)
- numconc = np.sum(xi)/vol / 1e6 #[/cm^3]
- massconc = np.sum(attrs.msol.flatten() * xi) / vol * 1000 #[g m^-3]
- dropvol = 4/3 * np.pi * np.sum((attrs.radius.flatten()**3) * xi)
- m_w_conc = dropvol * 1000 / vol * 1000 # mass as if drops had density of water=1000Kg/m^3 [g m^3]
-
- inforstr = "\n------ DOMAIN SUPERDROPLETS INFO ------\n"+\
- "total droplet number conc: {:3g}".format(numconc)+" /cm^3\n"+\
- "total droplet mass: {:3g}".format(massconc)+" g/m^3\n"+\
- " as if water: {:3g}".format(m_w_conc)+" g/m^3"+\
- "\n------------------------------------\n"
+ numconc = np.sum(xi) / vol / 1e6 # [/cm^3]
+ massconc = np.sum(attrs.msol.flatten() * xi) / vol * 1000 # [g m^-3]
+ dropvol = 4 / 3 * np.pi * np.sum((attrs.radius.flatten() ** 3) * xi)
+ m_w_conc = (
+ dropvol * 1000 / vol * 1000
+ ) # mass as if drops had density of water=1000Kg/m^3 [g m^3]
+
+ inforstr = (
+ "\n------ DOMAIN SUPERDROPLETS INFO ------\n"
+ + "total droplet number conc: {:3g}".format(numconc)
+ + " /cm^3\n"
+ + "total droplet mass: {:3g}".format(massconc)
+ + " g/m^3\n"
+ + " as if water: {:3g}".format(m_w_conc)
+ + " g/m^3"
+ + "\n------------------------------------\n"
+ )
print(inforstr)
-def plot_initdistribs(attrs, gbxvols, gbxidxs):
- plt.rcParams.update({'font.size': 14})
+def plot_initdistribs(attrs, gbxvols, gbxidxs):
+ plt.rcParams.update({"font.size": 14})
fig, axs = figure_setup(attrs.coord3, attrs.coord1, attrs.coord2)
# create nbins evenly spaced in log10(r)
nbins = 20
- minr, maxr = np.min(attrs.radius)/10, np.max(attrs.radius)*10
- hedgs = np.linspace(np.log10(minr), np.log10(maxr),
- nbins+1) # edges to lnr bins
+ minr, maxr = np.min(attrs.radius) / 10, np.max(attrs.radius) * 10
+ hedgs = np.linspace(np.log10(minr), np.log10(maxr), nbins + 1) # edges to lnr bins
for idx in gbxidxs:
vol = gbxvols[idx]
- sl = np.s_[attrs.sdgbxindex==idx]
- l0 = plot_radiusdistrib(axs[0], hedgs,
- attrs.radius[sl], attrs.xi[sl])
- l1 = plot_numconcdistrib(axs[1], hedgs, attrs.xi[sl],
- attrs.radius[sl], vol)
- l2 = plot_masssolutedistrib(axs[2], hedgs, attrs.xi[sl],
- attrs.radius[sl], attrs.msol[sl],
- vol)
+ sl = np.s_[attrs.sdgbxindex == idx]
+ l0 = plot_radiusdistrib(axs[0], hedgs, attrs.radius[sl], attrs.xi[sl])
+ l1 = plot_numconcdistrib(axs[1], hedgs, attrs.xi[sl], attrs.radius[sl], vol)
+ l2 = plot_masssolutedistrib(
+ axs[2], hedgs, attrs.xi[sl], attrs.radius[sl], attrs.msol[sl], vol
+ )
ls = plot_coorddistribs(axs, sl, hedgs, attrs)
fig.tight_layout()
return fig, axs, [l0, l1, l2, ls]
-def plot_initGBxs_attrdistribs(configfile, constsfile, initsupersfile,
- gridfile, binpath, savefig, gbxs2plt):
- ''' plot initial superdroplet distribution from initsupersfile binary
- of every gridbox with index in gbx2plts '''
- gbxvols = get_gbxvols_from_gridfile(gridfile,
- constsfile=constsfile,
- isprint=False)
- attrs = get_superdroplet_attributes(configfile,
- constsfile,
- initsupersfile)
+def plot_initGBxs_attrdistribs(
+ configfile, constsfile, initsupersfile, gridfile, binpath, savefig, gbxs2plt
+):
+ """plot initial superdroplet distribution from initsupersfile binary
+ of every gridbox with index in gbx2plts"""
- if type(gbxs2plt) == int:
+ gbxvols = get_gbxvols_from_gridfile(gridfile, constsfile=constsfile, isprint=False)
+ attrs = get_superdroplet_attributes(configfile, constsfile, initsupersfile)
+
+ if isinstance(gbxs2plt, int):
gbxidxs = [gbxs2plt]
- savename = binpath+"initGBx"+str(gbxs2plt)+"_distrib.png"
+ savename = binpath + "initGBx" + str(gbxs2plt) + "_distrib.png"
elif gbxs2plt == "all":
- gbxidxs = np.unique(attrs.sdgbxindex)
- savename = binpath+"initallGBxs_distribs.png"
+ gbxidxs = np.unique(attrs.sdgbxindex)
+ savename = binpath + "initallGBxs_distribs.png"
else:
gbxidxs = gbxs2plt
- savename = binpath+"initGBxs_distribs.png"
+ savename = binpath + "initGBxs_distribs.png"
fig, axs, lines = plot_initdistribs(attrs, gbxvols, gbxidxs)
fig.tight_layout()
if savefig:
- fig.savefig(savename, dpi=400,
- bbox_inches="tight", facecolor='w', format="png")
- print("Figure .png saved as: "+savename)
+ fig.savefig(savename, dpi=400, bbox_inches="tight", facecolor="w", format="png")
+ print("Figure .png saved as: " + savename)
plt.show()
-def figure_setup(coord3, coord1, coord2):
- ncoords = 3-sum(not x.any() for x in [coord3, coord2, coord1])
+def figure_setup(coord3, coord1, coord2):
+ ncoords = 3 - sum(not x.any() for x in [coord3, coord2, coord1])
if ncoords == 0:
fig, axs = plt.subplots(nrows=1, ncols=3, figsize=(14, 4))
@@ -178,48 +192,55 @@ def figure_setup(coord3, coord1, coord2):
else:
fig, axs = plt.subplots(nrows=2, ncols=3, figsize=(14, 8))
- fig.suptitle(str(ncoords)+"-D SDM Initial Superdroplet Conditions")
+ fig.suptitle(str(ncoords) + "-D SDM Initial Superdroplet Conditions")
axs = axs.flatten()
if ncoords == 2:
axs[-1].remove()
-
return fig, axs
+
def log10r_frequency_distribution(radius, hedgs, wghts):
- ''' get distribution of data with weights 'wghts' against
+ """get distribution of data with weights 'wghts' against
log10(r). Uses np.histogram to get frequency of a particular
value of data that falls in each bin (with each bin defined
by it's edges 'hedgs'). Return distirbution alongside the radius
- bin centers and widths in [m]'''
+ bin centers and widths in [m]"""
if type(wghts) != np.ndarray:
wghts = np.full(np.shape(radius), wghts)
- hist, hedgs = np.histogram(np.log10(radius), bins=hedgs,
- weights=wghts, density=None)
+ hist, hedgs = np.histogram(
+ np.log10(radius), bins=hedgs, weights=wghts, density=None
+ )
# convert [m] to [micron]
- hedgs = (10**(hedgs))*1e6
+ hedgs = (10 ** (hedgs)) * 1e6
# radius bin widths [micron]
hwdths = hedgs[1:] - hedgs[:-1]
# radius bin centres [micron]
- hcens = (hedgs[1:]+hedgs[:-1])/2
+ hcens = (hedgs[1:] + hedgs[:-1]) / 2
return hist, hedgs, hwdths, hcens
def plot_radiusdistrib(ax, hedgs, radius, xi):
- ''' get and plotthe superdroplet radius in each log10(r)
- bin and as a scatter on a twinx axis with their multiplicities'''
+ """get and plotthe superdroplet radius in each log10(r)
+ bin and as a scatter on a twinx axis with their multiplicities"""
- l1 = ax.scatter(radius*1e6, xi, zorder=1,
- label="multiplicities")
+ l1 = ax.scatter(radius * 1e6, xi, zorder=1, label="multiplicities")
ax2 = ax.twinx()
hist, hedgs, hwdths, hcens = log10r_frequency_distribution(radius, hedgs, 1)
- l2 = ax2.step(hcens, hist, where='mid', alpha=0.8, zorder=0,
- color="grey", label="number distribution")
+ l2 = ax2.step(
+ hcens,
+ hist,
+ where="mid",
+ alpha=0.8,
+ zorder=0,
+ color="grey",
+ label="number distribution",
+ )
ax.set_xscale("log")
ax.set_xlabel("radius, r, /\u03BCm")
@@ -236,13 +257,12 @@ def plot_radiusdistrib(ax, hedgs, radius, xi):
def plot_numconcdistrib(ax, hedgs, xi, radius, vol):
- ''' get and plot frequency of real droplets in each log10(r) bin '''
+ """get and plot frequency of real droplets in each log10(r) bin"""
wghts = xi / vol / 1e6 # [cm^-3]
- hist, hedgs, hwdths, hcens = log10r_frequency_distribution(
- radius, hedgs, wghts)
+ hist, hedgs, hwdths, hcens = log10r_frequency_distribution(radius, hedgs, wghts)
- line = ax.step(hcens, hist, label="binned distribution", where='mid')
+ line = ax.step(hcens, hist, label="binned distribution", where="mid")
ax.set_xscale("log")
ax.set_xlabel("radius, r, /\u03BCm")
ax.set_ylabel("real droplet number\nconcentration / cm$^{-3}$")
@@ -252,40 +272,40 @@ def plot_numconcdistrib(ax, hedgs, xi, radius, vol):
return line
+
def plot_masssolutedistrib(ax, hedgs, xi, radius, msol, vol):
- ''' get and plot frequency of real droplets in each log10(r) bin '''
+ """get and plot frequency of real droplets in each log10(r) bin"""
- wghts = msol*xi/vol * 1000 / 1e6 # [g cm^-3]
- hist, hedgs, hwdths, hcens = log10r_frequency_distribution(
- radius, hedgs, wghts)
+ wghts = msol * xi / vol * 1000 / 1e6 # [g cm^-3]
+ hist, hedgs, hwdths, hcens = log10r_frequency_distribution(radius, hedgs, wghts)
- line = ax.step(hcens, hist, where='mid')
+ line = ax.step(hcens, hist, where="mid")
ax.set_xscale("log")
ax.set_xlabel("radius, r, /\u03BCm")
ax.set_ylabel("solute mass per unit volume / g cm$^{-3}$")
return line
+
def plot_totmassdistrib(ax, hedgs, xi, radius, msol, vol, RHO_L, RHO_SOL):
- ''' get and plot frequency of real droplets in each log10(r) bin '''
+ """get and plot frequency of real droplets in each log10(r) bin"""
- mass = totmass(radius, msol, RHO_L , RHO_SOL)
- wghts = mass*xi/vol * 1000 / 1e6 # [g cm^-3]
- hist, hedgs, hwdths, hcens = log10r_frequency_distribution(
- radius, hedgs, wghts)
+ mass = totmass(radius, msol, RHO_L, RHO_SOL)
+ wghts = mass * xi / vol * 1000 / 1e6 # [g cm^-3]
+ hist, hedgs, hwdths, hcens = log10r_frequency_distribution(radius, hedgs, wghts)
- line = ax.step(hcens, hist, where='mid')
+ line = ax.step(hcens, hist, where="mid")
ax.set_xscale("log")
ax.set_xlabel("radius, r, /\u03BCm")
ax.set_ylabel("droplet mass per unit volume / g cm$^{-3}$")
return line
-def scatter_totmass_solutemass(ax, radius, msol, RHO_L, RHO_SOL):
- mass = totmass(radius, msol, RHO_L , RHO_SOL)
+def scatter_totmass_solutemass(ax, radius, msol, RHO_L, RHO_SOL):
+ mass = totmass(radius, msol, RHO_L, RHO_SOL)
- line = ax.scatter(mass*1000, msol*1000, marker="x")
+ line = ax.scatter(mass * 1000, msol * 1000, marker="x")
ax.set_xscale("log")
ax.set_yscale("log")
@@ -294,90 +314,101 @@ def scatter_totmass_solutemass(ax, radius, msol, RHO_L, RHO_SOL):
return line
-def plot_coorddistribs(axs, i2plt, hedgs, attrs):
+def plot_coorddistribs(axs, i2plt, hedgs, attrs):
ls = []
if attrs.coord3.any():
- ls.append(plot_coorddist(axs[3], hedgs, attrs.coord3[i2plt],
- attrs.radius[i2plt], 3))
- if attrs.coord1.any():
- ls.append(plot_coorddist(axs[4], hedgs, attrs.coord1[i2plt],
- attrs.radius[i2plt], 1))
- if attrs.coord2.any():
- ls.append(plot_coorddist(axs[5], hedgs,
- attrs.coord2[i2plt],
- attrs.radius[i2plt], 2))
+ ls.append(
+ plot_coorddist(axs[3], hedgs, attrs.coord3[i2plt], attrs.radius[i2plt], 3)
+ )
+ if attrs.coord1.any():
+ ls.append(
+ plot_coorddist(
+ axs[4], hedgs, attrs.coord1[i2plt], attrs.radius[i2plt], 1
+ )
+ )
+ if attrs.coord2.any():
+ ls.append(
+ plot_coorddist(
+ axs[5], hedgs, attrs.coord2[i2plt], attrs.radius[i2plt], 2
+ )
+ )
return ls
-def plot_coorddist(ax, hedgs, coord3, radius, coordnum):
+def plot_coorddist(ax, hedgs, coord3, radius, coordnum):
line = None
if any(coord3):
- line = ax.scatter(radius*1e6, coord3)
+ line = ax.scatter(radius * 1e6, coord3)
ax.set_xscale("log")
ax.set_xlabel("radius, r, /\u03BCm")
- ax.set_ylabel("superdroplet coord"+str(coordnum)+" / m")
+ ax.set_ylabel("superdroplet coord" + str(coordnum) + " / m")
return line
-def plot_initGBxs_dropletmasses(configfile, constsfile, initsupersfile,
- gridfile, binpath, savefig, gbxs2plt):
- ''' plot initial superdroplet mass distributions
+
+def plot_initGBxs_dropletmasses(
+ configfile, constsfile, initsupersfile, gridfile, binpath, savefig, gbxs2plt
+):
+ """plot initial superdroplet mass distributions
from initsupersfile binary of every gridbox with index
- in gbx2plts '''
-
- gbxvols = get_gbxvols_from_gridfile(gridfile,
- constsfile=constsfile,
- isprint=False)
- attrs = get_superdroplet_attributes(configfile,
- constsfile,
- initsupersfile)
+ in gbx2plts"""
+
+ gbxvols = get_gbxvols_from_gridfile(gridfile, constsfile=constsfile, isprint=False)
+ attrs = get_superdroplet_attributes(configfile, constsfile, initsupersfile)
inputs = initSDsinputsdict(configfile, constsfile)
- if type(gbxs2plt) == int:
+ if isinstance(gbxs2plt, int):
gbxidxs = [gbxs2plt]
- savename = binpath+"initGBx"+str(gbxs2plt)+"_dropletmasses.png"
+ savename = binpath + "initGBx" + str(gbxs2plt) + "_dropletmasses.png"
elif gbxs2plt == "all":
- gbxidxs = np.unique(attrs.sdgbxindex)
- savename = binpath+"initallGBxs_dropletmasses.png"
+ gbxidxs = np.unique(attrs.sdgbxindex)
+ savename = binpath + "initallGBxs_dropletmasses.png"
else:
gbxidxs = gbxs2plt
- savename = binpath+"initGBxs_dropletmasses.png"
+ savename = binpath + "initGBxs_dropletmasses.png"
- fig, axs, lines = plot_massdistribs(attrs, gbxvols, gbxidxs,
- inputs["RHO_L"], inputs["RHO_SOL"])
+ fig, axs, lines = plot_massdistribs(
+ attrs, gbxvols, gbxidxs, inputs["RHO_L"], inputs["RHO_SOL"]
+ )
fig.tight_layout()
if savefig:
- fig.savefig(savename, dpi=400,
- bbox_inches="tight", facecolor='w', format="png")
- print("Figure .png saved as: "+savename)
+ fig.savefig(savename, dpi=400, bbox_inches="tight", facecolor="w", format="png")
+ print("Figure .png saved as: " + savename)
plt.show()
-def plot_massdistribs(attrs, gbxvols, gbxidxs, RHO_L, RHO_SOL):
- plt.rcParams.update({'font.size': 14})
+def plot_massdistribs(attrs, gbxvols, gbxidxs, RHO_L, RHO_SOL):
+ plt.rcParams.update({"font.size": 14})
fig, axs = plt.subplots(nrows=1, ncols=3, figsize=(14, 4))
fig.suptitle("SDM Initial Superdroplet Mass Distributions")
# create nbins evenly spaced in log10(r)
nbins = 20
- minr, maxr = np.min(attrs.radius)/10, np.max(attrs.radius)*10
- hedgs = np.linspace(np.log10(minr), np.log10(maxr),
- nbins+1) # edges to lnr bins
+ minr, maxr = np.min(attrs.radius) / 10, np.max(attrs.radius) * 10
+ hedgs = np.linspace(np.log10(minr), np.log10(maxr), nbins + 1) # edges to lnr bins
for idx in gbxidxs:
vol = gbxvols[idx]
- sl = np.s_[attrs.sdgbxindex==idx]
- l0 = plot_totmassdistrib(axs[0], hedgs, attrs.xi[sl],
- attrs.radius[sl], attrs.msol[sl],
- vol, RHO_L, RHO_SOL)
- l1 = plot_masssolutedistrib(axs[1], hedgs, attrs.xi[sl],
- attrs.radius[sl], attrs.msol[sl],
- vol)
- l2 = scatter_totmass_solutemass(axs[2], attrs.radius[sl],
- attrs.msol[sl], RHO_L, RHO_SOL)
+ sl = np.s_[attrs.sdgbxindex == idx]
+ l0 = plot_totmassdistrib(
+ axs[0],
+ hedgs,
+ attrs.xi[sl],
+ attrs.radius[sl],
+ attrs.msol[sl],
+ vol,
+ RHO_L,
+ RHO_SOL,
+ )
+ l1 = plot_masssolutedistrib(
+ axs[1], hedgs, attrs.xi[sl], attrs.radius[sl], attrs.msol[sl], vol
+ )
+ l2 = scatter_totmass_solutemass(
+ axs[2], attrs.radius[sl], attrs.msol[sl], RHO_L, RHO_SOL
+ )
fig.tight_layout()
diff --git a/pySD/initsuperdropsbinary_src/rgens.py b/pySD/initsuperdropsbinary_src/rgens.py
index cabb4fe24..bf88ebf67 100644
--- a/pySD/initsuperdropsbinary_src/rgens.py
+++ b/pySD/initsuperdropsbinary_src/rgens.py
@@ -1,4 +1,7 @@
-'''
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
----- CLEO -----
File: rgens.py
Project: initsuperdropsbinary_src
@@ -6,32 +9,29 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Wednesday 27th December 2023
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
https://opensource.org/licenses/BSD-3-Clause
-----
-Copyright (c) 2023 MPI-M, Clara Bayley
------
File Description:
-various ways of generatring radii of
-superdroplets for their initial conditions
-'''
+various ways of generatring radii of superdroplets for their initial conditions
+"""
import numpy as np
+
class MonoAttrGen:
- ''' method to generate superdroplets with an
- attribute all equal to attr0 '''
+ """method to generate superdroplets with an
+ attribute all equal to attr0"""
def __init__(self, attr0):
-
self.attr0 = attr0
def __call__(self, nsupers):
- ''' Returns attribute for nsupers all
- with the value of attr0 '''
+ """Returns attribute for nsupers all
+ with the value of attr0"""
if type(nsupers) == np.ndarray:
nsupers = np.shape(nsupers)[0]
@@ -40,47 +40,45 @@ def __call__(self, nsupers):
return attrs
+
class SampleLog10RadiiGen:
- '''method to generate superdroplet radii by randomly
+ """method to generate superdroplet radii by randomly
sampling from bins that are linearly spaced in log10(r)
- between rspan[0] and rspan[1] '''
+ between rspan[0] and rspan[1]"""
def __init__(self, rspan):
-
self.rspan = rspan
def __call__(self, nsupers):
- ''' Returns radii for nsupers sampled from rspan [m]'''
+ """Returns radii for nsupers sampled from rspan [m]"""
- return self.generate_radiisample(nsupers) # units [m]
+ return self.generate_radiisample(nsupers) # units [m]
def generate_radiisample(self, nbins):
- ''' Divide rspan [m] into evenly spaced bins in log10(r).
+ """Divide rspan [m] into evenly spaced bins in log10(r).
If edges=True, return values of radii at edges of bins.
Else sample each bin randomly to obtain the radius
- of 'nsupers' no. of superdroplets '''
+ of 'nsupers' no. of superdroplets"""
if nbins:
- log10redgs = np.linspace(np.log10(self.rspan[0]), np.log10(
- self.rspan[1]), nbins+1) # log10(r) bin edges
+ log10redgs = np.linspace(
+ np.log10(self.rspan[0]), np.log10(self.rspan[1]), nbins + 1
+ ) # log10(r) bin edges
- redgs = 10**(log10redgs)
radii = self.randomlysample_log10rbins(nbins, log10redgs)
return radii # [m]
else:
return np.array([])
def randomlysample_log10rbins(self, nbins, log10redgs):
- ''' given the bin edges, randomly sample each bin of
- log10(radius /m) and return the resultant radii [m]'''
+ """given the bin edges, randomly sample each bin of
+ log10(radius /m) and return the resultant radii [m]"""
- log10r_binwidth = (log10redgs[-1] - log10redgs[0])/nbins
+ log10r_binwidth = (log10redgs[-1] - log10redgs[0]) / nbins
- randlog10deltar = np.random.uniform(low=0.0,
- high=log10r_binwidth,
- size=nbins)
+ randlog10deltar = np.random.uniform(low=0.0, high=log10r_binwidth, size=nbins)
randlog10r = log10redgs[:-1] + randlog10deltar
- radii = 10**(randlog10r)
+ radii = 10 ** (randlog10r)
return radii # [m]
diff --git a/pySD/readbinary.py b/pySD/readbinary.py
index 13874bb9f..e4cbcd1e1 100644
--- a/pySD/readbinary.py
+++ b/pySD/readbinary.py
@@ -3,87 +3,100 @@
from .writebinary import DataTypeCodes, MetadataPerVariable
+
def readbinary(filename, isprint=True):
- ''' return list of vectors containing dimenionsless
- data read from binary file'''
+ """return list of vectors containing dimenionsless
+ data read from binary file"""
+
+ print("Reading binary file:\n " + filename)
- print("Reading binary file:\n "+filename)
+ nvars, metabytes, metapervar = read_metadata(filename, isprint=isprint)
- nvars, metabytes, metapervar = read_metadata(filename, isprint=isprint)
+ data, ndata_pervar = read_data(filename, nvars, metabytes, metapervar)
- data, ndata_pervar = read_data(filename, nvars, metabytes, metapervar)
+ return data, ndata_pervar
- return data, ndata_pervar
def read_metadata(filename, isprint=True):
- ''' read global metadata and return the variable specific metadata
- in a 2D array with each row being the metadata for a different variable'''
+ """read global metadata and return the variable specific metadata
+ in a 2D array with each row being the metadata for a different variable"""
+
+ dtc = DataTypeCodes()
- dtc = DataTypeCodes()
+ metabytes, gblmeta_bytes, nvars, mpv_bytes = first4uints(filename, dtc)
- metabytes, gblmeta_bytes, nvars, mpv_bytes = first4uints(filename, dtc)
+ gblmetadata, metapervar = get_metadatapervar(
+ filename, dtc, gblmeta_bytes, nvars, metabytes
+ )
- gblmetadata, metapervar = get_metadatapervar(filename, dtc, gblmeta_bytes,
- nvars, metabytes)
+ if isprint:
+ print("Metadata: \n", "'" + gblmetadata + "'")
- if isprint:
- print("Metadata: \n", "'"+gblmetadata+"'")
+ return metapervar.shape[0], metabytes, metapervar
- return metapervar.shape[0], metabytes, metapervar
def first4uints(filename, dtc):
+ with open(filename, mode="rb") as binaryfile:
+ bytes4uints = dtc.format_size("IIII")
+ uints = struct.unpack(" ti+1
- isgone = np.where(np.isin(sd_ti, list(sds_gone))) # indexes in ragged arrays of SDs that leave during timestep ti -> ti+1
- r3sum.append(np.dot(r_ti[isgone]**3, xi_ti[isgone])) # sum of (real) droplet radii^3 that left domain [microns^3]
- precipvol = 4/3 * np.pi * np.asarray(r3sum) / (1e18) # volume of water that left domain [m^3]
-
- domainy = np.amax(gbxs["yhalf"]) - np.amin(gbxs["yhalf"]) # [m]
- domainx = np.amax(gbxs["xhalf"]) - np.amin(gbxs["xhalf"]) # [m]
- deltat = np.diff(ds["time"].values) / 60 / 60 # [hrs]
- preciprate = precipvol * 1000 / (domainx * domainy) / deltat # [mm/hr]
-
- precipaccum = np.cumsum(preciprate * deltat) # [mm]
- preciprate = np.insert(preciprate, 0, 0) # at t=0, precip rate = 0
- precipaccum = np.insert(precipaccum, 0, 0) # at t=0, accumulated precip = 0
-
- return preciprate, precipaccum # [mm/hr] , [mm]
+ """use last radius of SDs before they leave the domain to
+ estimate the volume of precipitation at each timestep.
+ Values should be approx. equal to sum over gbxs (multiplied
+ by area_gbx/area_domain) of logbook values for precip"""
+
+ ds = get_rawdataset(dataset)
+
+ sdId = ak.unflatten(ds["sdId"].values, ds["raggedcount"].values)
+ radius = ak.unflatten(ds["radius"].values, ds["raggedcount"].values)
+ xi = ak.unflatten(ds["xi"].values, ds["raggedcount"].values)
+
+ r3sum = []
+ for ti in range(ds.time.shape[0] - 1):
+ sd_ti, r_ti, xi_ti = sdId[ti], radius[ti], xi[ti]
+ sds_gone = set(sd_ti) - set(
+ sdId[ti + 1]
+ ) # set of SD indexes that have left domain during timestep ti -> ti+1
+ isgone = np.where(
+ np.isin(sd_ti, list(sds_gone))
+ ) # indexes in ragged arrays of SDs that leave during timestep ti -> ti+1
+ r3sum.append(
+ np.dot(r_ti[isgone] ** 3, xi_ti[isgone])
+ ) # sum of (real) droplet radii^3 that left domain [microns^3]
+ precipvol = (
+ 4 / 3 * np.pi * np.asarray(r3sum) / (1e18)
+ ) # volume of water that left domain [m^3]
+
+ domainy = np.amax(gbxs["yhalf"]) - np.amin(gbxs["yhalf"]) # [m]
+ domainx = np.amax(gbxs["xhalf"]) - np.amin(gbxs["xhalf"]) # [m]
+ deltat = np.diff(ds["time"].values) / 60 / 60 # [hrs]
+ preciprate = precipvol * 1000 / (domainx * domainy) / deltat # [mm/hr]
+
+ precipaccum = np.cumsum(preciprate * deltat) # [mm]
+ preciprate = np.insert(preciprate, 0, 0) # at t=0, precip rate = 0
+ precipaccum = np.insert(precipaccum, 0, 0) # at t=0, accumulated precip = 0
+
+ return preciprate, precipaccum # [mm/hr] , [mm]
diff --git a/pySD/sdmout_src/sdtracing.py b/pySD/sdmout_src/sdtracing.py
index de5394c6c..f72d20876 100644
--- a/pySD/sdmout_src/sdtracing.py
+++ b/pySD/sdmout_src/sdtracing.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: sdtracing.py
Project: sdmout_src
@@ -18,108 +18,121 @@
functions to extract attribute data for
specifc superdroplets based on the sdIds
e.g. for tracing their trajectories
-'''
+"""
import numpy as np
import awkward as ak
import random
+
def attr_for_superdroplet(sddata, Id, attr):
- '''selects attribute from sddata belonging
- to superdroplet with identitiy 'Id'
- at every output time '''
+ """selects attribute from sddata belonging
+ to superdroplet with identitiy 'Id'
+ at every output time"""
+
+ bools = ak.Array(sddata.sdId == Id) # True/False id is found in sdId at each time
+ attr4Id = sddata[attr][bools] # attribute where sdId = Id
- bools = ak.Array(sddata.sdId==Id) # True/False id is found in sdId at each time
- attr4Id = sddata[attr][bools] # attribute where sdId = Id
+ num = ak.num(
+ attr4Id
+ ) # at each time, number of positions where sdId is found (should be 0 or 1)
+ if any(num[num != 1]):
+ errmsg = (
+ "attribute timeseries has times when more"
+ + " than one sdId==Id. num should be list of either 1 or 0"
+ + " (for Id found in sddata at given time or not)"
+ )
+ raise ValueError(errmsg)
- num = ak.num(attr4Id) # at each time, number of positions where sdId is found (should be 0 or 1)
- if any(num[num!=1]):
- errmsg = "attribute timeseries has times when more"+\
- " than one sdId==Id. num should be list of either 1 or 0"+\
- " (for Id found in sddata at given time or not)"
- raise ValueError(errmsg)
+ attr4Id = ak.where(
+ num == 0, ak.Array([[np.nan]]), attr4Id
+ ) # replace empty values with np.nan
- attr4Id = ak.where(num==0, ak.Array([[np.nan]]), attr4Id) # replace empty values with np.nan
+ return ak.flatten(attr4Id, axis=1) # remove excess dimension
- return ak.flatten(attr4Id, axis=1) # remove excess dimension
def attributes_for1superdroplet(sddata, Id, attrs):
- '''selects attributes in 'attrs' from sddata
- belonging to superdroplet with identitiy 'Id'
- at every output time '''
-
- attrs4Id = {}
- for attr in attrs:
- attrs4Id[attr] = attr_for_superdroplet(sddata, Id, attr)
-
- return attrs4Id
-
-def attribute_for_superdroplets_sample(sddata, attr, ndrops2sample=0,
- minid=0, maxid=0, ids=[]):
- ''' returns 2D array with dimensions [time, SD]
- containing attribute data over time for a sample of
- superdroplets. Sample is either for superdroplets with
- specific Ids in 'ids' list, or sample of 'ndrops2sample'
- randomly selected superdrops with Ids in the range
- [minid, maxid] '''
-
- if np.any(ids):
- sample = ids
- else: # ids == []
- population = list(range(minid, maxid, 1))
- if ndrops2sample == 0:
- ndrops2sample = maxid
- sample = random.sample(population, ndrops2sample)
-
- ndrops_attr = []
- for id in sample:
- attr4Id = attr_for_superdroplet(sddata, id, attr)
- ndrops_attr.append(attr4Id)
-
- return np.asarray(ndrops_attr).T
+ """selects attributes in 'attrs' from sddata
+ belonging to superdroplet with identitiy 'Id'
+ at every output time"""
+
+ attrs4Id = {}
+ for attr in attrs:
+ attrs4Id[attr] = attr_for_superdroplet(sddata, Id, attr)
+
+ return attrs4Id
+
+
+def attribute_for_superdroplets_sample(
+ sddata, attr, ndrops2sample=0, minid=0, maxid=0, ids=[]
+):
+ """returns 2D array with dimensions [time, SD]
+ containing attribute data over time for a sample of
+ superdroplets. Sample is either for superdroplets with
+ specific Ids in 'ids' list, or sample of 'ndrops2sample'
+ randomly selected superdrops with Ids in the range
+ [minid, maxid]"""
+
+ if np.any(ids):
+ sample = ids
+ else: # ids == []
+ population = list(range(minid, maxid, 1))
+ if ndrops2sample == 0:
+ ndrops2sample = maxid
+ sample = random.sample(population, ndrops2sample)
+
+ ndrops_attr = []
+ for id in sample:
+ attr4Id = attr_for_superdroplet(sddata, id, attr)
+ ndrops_attr.append(attr4Id)
+
+ return np.asarray(ndrops_attr).T
+
def attr_at_times(attrdata, time, times2sel):
- '''selects attribute (for all superdroplets)
- at times closest to 'times2sel' '''
+ """selects attribute (for all superdroplets)
+ at times closest to 'times2sel'"""
+
+ inds = [] # list containing indexes of times closest to times2sel
+ for t in times2sel:
+ inds.append(np.argmin(abs(time - t)))
- inds = [] # list containing indexes of times closest to times2sel
- for t in times2sel:
- inds.append(np.argmin(abs(time-t)))
+ return attrdata[inds]
- return attrdata[inds]
def attributes_at_times(sddata, time, times2sel, attrs2sel):
- '''selects attributes at given times from
- sddata (for all superdroplets in sddata)'''
+ """selects attributes at given times from
+ sddata (for all superdroplets in sddata)"""
- selected_data = {} # dict containting selected attributes at selected times
+ selected_data = {} # dict containting selected attributes at selected times
- for attr in attrs2sel:
+ for attr in attrs2sel:
+ selattr_data = attr_at_times(sddata[attr], time, times2sel)
+ selected_data[attr] = selattr_data
- selattr_data = attr_at_times(sddata[attr], time, times2sel)
- selected_data[attr] = selattr_data
+ return selected_data
- return selected_data
-def attrs_for_superdroplets_sample(sddata, attrs, ndrops2sample=0,
- minid=0, maxid=0, ids=[]):
- ''' returns dictionary of 2D arrays (with dimensions [time, SD])
- for each attribute in 'attrs' list for a sample of
- superdroplets. Sample is either for superdroplets with
- specific Ids in 'ids' list, or sample of 'ndrops2sample'
- randomly selected superdrops with Ids in the range
- [minid, maxid] '''
+def attrs_for_superdroplets_sample(
+ sddata, attrs, ndrops2sample=0, minid=0, maxid=0, ids=[]
+):
+ """returns dictionary of 2D arrays (with dimensions [time, SD])
+ for each attribute in 'attrs' list for a sample of
+ superdroplets. Sample is either for superdroplets with
+ specific Ids in 'ids' list, or sample of 'ndrops2sample'
+ randomly selected superdrops with Ids in the range
+ [minid, maxid]"""
- if np.any(ids):
- sample = ids
- else: # ids == []
- population = list(range(minid, maxid, 1))
- if ndrops2sample == 0:
- ndrops2sample = maxid
- sample = random.sample(population, ndrops2sample)
+ if np.any(ids):
+ sample = ids
+ else: # ids == []
+ population = list(range(minid, maxid, 1))
+ if ndrops2sample == 0:
+ ndrops2sample = maxid
+ sample = random.sample(population, ndrops2sample)
- data = {}
- for a in attrs:
- data[a] = attribute_for_superdroplets_sample(sddata, a, ids=sample)
+ data = {}
+ for a in attrs:
+ data[a] = attribute_for_superdroplets_sample(sddata, a, ids=sample)
- return data
+ return data
diff --git a/pySD/sdmout_src/supersdata.py b/pySD/sdmout_src/supersdata.py
index 6d2450d19..277257b2e 100644
--- a/pySD/sdmout_src/supersdata.py
+++ b/pySD/sdmout_src/supersdata.py
@@ -1,4 +1,7 @@
-'''
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
----- CLEO -----
File: supersdata.py
Project: sdmout_src
@@ -6,31 +9,27 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Monday 15th April 2024
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
https://opensource.org/licenses/BSD-3-Clause
-----
-Copyright (c) 2023 MPI-M, Clara Bayley
------
File Description:
-python class to handle superdroplet
-attributes data from SDM zarr store
-in ragged array format
-'''
+python class to handle superdroplet attributes data from SDM zarr store in ragged array format
+"""
import numpy as np
import xarray as xr
import awkward as ak
-class SuperdropProperties():
- '''Contains attributes common to all superdroplets and functions
- for calculating derived ones'''
+class SuperdropProperties:
+ """Contains attributes common to all superdroplets and functions
+ for calculating derived ones"""
def __init__(self, consts):
- '''Common attributes shared by superdroplets'''
+ """Common attributes shared by superdroplets"""
# density of liquid in droplets (=density of water at 300K) [Kg/m^3]
self.RHO_L = consts["RHO_L"]
@@ -54,55 +53,55 @@ def print_properties(self):
print("-------------------------------")
def rhoeff(self, r, msol):
- ''' calculates effective density [g m^-3] of
- droplet such that mass_droplet, m = 4/3*pi*r^3 * rhoeff
- taking into account mass of liquid and mass of
- solute assuming solute occupies volume it
- would given its (dry) density, RHO_SOL. '''
+ """calculates effective density [g m^-3] of
+ droplet such that mass_droplet, m = 4/3*pi*r^3 * rhoeff
+ taking into account mass of liquid and mass of
+ solute assuming solute occupies volume it
+ would given its (dry) density, RHO_SOL."""
- msol = msol/1000 # convert from grams to Kg
- r = r/1e6 # convert microns to m
+ msol = msol / 1000 # convert from grams to Kg
+ r = r / 1e6 # convert microns to m
- solfactor = 3*msol/(4.0*np.pi*(r**3))
- rhoeff = self.RHO_L + solfactor*(1-self.RHO_L/self.RHO_SOL)
+ solfactor = 3 * msol / (4.0 * np.pi * (r**3))
+ rhoeff = self.RHO_L + solfactor * (1 - self.RHO_L / self.RHO_SOL)
- return rhoeff * 1000 #[g/m^3]
+ return rhoeff * 1000 # [g/m^3]
def vol(self, r):
- ''' volume of droplet [m^3] '''
+ """volume of droplet [m^3]"""
- r = r/1e6 # convert microns to m
+ r = r / 1e6 # convert microns to m
- return 4.0/3.0 * np.pi * r**3
+ return 4.0 / 3.0 * np.pi * r**3
def mass(self, r, msol):
- '''
+ """
total mass of droplet (water + (dry) areosol) [g],
m = 4/3*pi*rho_l**3 + msol(1-rho_l/rho_sol)
ie. m = 4/3*pi*rhoeff*R**3
- '''
+ """
- msol = msol/1000 # convert from grams to Kg
- r = r/1e6 # convert microns to m
+ msol = msol / 1000 # convert from grams to Kg
+ r = r / 1e6 # convert microns to m
- msoleff = msol*(1-self.RHO_L/self.RHO_SOL) # effect of solute on mass
- m = msoleff + 4/3.0*np.pi*(r**3)*self.RHO_L
+ msoleff = msol * (1 - self.RHO_L / self.RHO_SOL) # effect of solute on mass
+ m = msoleff + 4 / 3.0 * np.pi * (r**3) * self.RHO_L
- return m * 1000 # [g]
+ return m * 1000 # [g]
def m_water(self, r, msol):
- ''' mass of only water in droplet [g]'''
+ """mass of only water in droplet [g]"""
- msol = msol/1000 # convert msol from grams to Kg
- r = r/1e6 # convert microns to m
+ msol = msol / 1000 # convert msol from grams to Kg
+ r = r / 1e6 # convert microns to m
- v_sol = msol/self.RHO_SOL
- v_w = 4/3.0*np.pi*(r**3) - v_sol
+ v_sol = msol / self.RHO_SOL
+ v_w = 4 / 3.0 * np.pi * (r**3) - v_sol
- return self.RHO_L*v_w * 1000 #[g]
+ return self.RHO_L * v_w * 1000 # [g]
-class SupersData(SuperdropProperties):
+class SupersData(SuperdropProperties):
def __init__(self, dataset, consts):
SuperdropProperties.__init__(self, consts)
@@ -127,33 +126,31 @@ def __init__(self, dataset, consts):
self.coord2_units = self.tryunits(ds, "coord2") # probably meters
def tryopen_dataset(self, dataset):
-
- if type(dataset) == str:
+ if isinstance(dataset, str):
print("supers dataset: ", dataset)
return xr.open_dataset(dataset, engine="zarr", consolidated=False)
else:
return dataset
def tryvar(self, ds, raggedcount, var):
- ''' attempts to return variable in form of ragged array
+ """attempts to return variable in form of ragged array
(ak.Array) with dims [time, raggedcount]
for a variable "var" in xarray dataset 'ds'.
- If attempt fails, returns empty array instead '''
+ If attempt fails, returns empty array instead"""
try:
return ak.unflatten(ds[var].values, raggedcount)
- except:
+ except KeyError:
return ak.Array([])
def tryunits(self, ds, var):
- ''' attempts to return the units of a variable
- in xarray dataset 'ds'. If attempt fails, returns null '''
+ """attempts to return the units of a variable
+ in xarray dataset 'ds'. If attempt fails, returns null"""
try:
return ds[var].units
- except:
+ except KeyError:
return ""
def __getitem__(self, key):
-
if key == "sdId":
return self.sdId
elif key == "sdgbxindex":
@@ -171,22 +168,21 @@ def __getitem__(self, key):
elif key == "coord2":
return self.coord2
else:
- err = "no known return provided for "+key+" key"
+ err = "no known return provided for " + key + " key"
raise ValueError(err)
class RainSupers(SuperdropProperties):
-
def __init__(self, sddata, consts, rlim=40):
- ''' return data for (rain)drops with radii > rlim.
- Default minimum raindrops size is rlim=40microns'''
+ """return data for (rain)drops with radii > rlim.
+ Default minimum raindrops size is rlim=40microns"""
- if type(sddata) != SupersData:
+ if not isinstance(sddata, SupersData):
sddata = SupersData(dataset=sddata, consts=consts)
israin = sddata.radius >= rlim # ak array True for raindrops
- self.totnsupers_rain = ak.num(israin[israin == True])
+ self.totnsupers_rain = ak.num(israin[israin is True])
self.sdId = sddata.sdId[israin]
self.sdgbxindex = sddata.sdgbxindex[israin]
self.xi = sddata.xi[israin]
@@ -220,5 +216,5 @@ def __getitem__(self, key):
elif key == "coord2":
return self.coord2
else:
- err = "no known return provided for "+key+" key"
+ err = "no known return provided for " + key + " key"
raise ValueError(err)
diff --git a/pySD/sdmout_src/thermodata.py b/pySD/sdmout_src/thermodata.py
index e42407edd..d3ea042f3 100644
--- a/pySD/sdmout_src/thermodata.py
+++ b/pySD/sdmout_src/thermodata.py
@@ -1,4 +1,7 @@
-'''
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
----- CLEO -----
File: thermodata.py
Project: sdmout_src
@@ -6,145 +9,140 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Monday 8th April 2024
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
https://opensource.org/licenses/BSD-3-Clause
-----
-Copyright (c) 2023 MPI-M, Clara Bayley
------
File Description:
-python class to handle thermodynamics and wind fields
-data from SDM zarr store in cartesian
-domain
-'''
+python class to handle thermodynamics and wind fields data from SDM zarr store in cartesian domain
+"""
import numpy as np
import xarray as xr
from . import thermoeqns
+
def thermotryopen_dataset(dataset):
+ if isinstance(dataset, str):
+ print("thermodynamic fields dataset: ", dataset)
+ return xr.open_dataset(dataset, engine="zarr", consolidated=False)
+ else:
+ return dataset
- if type(dataset) == str:
- print("thermodynamic fields dataset: ", dataset)
- return xr.open_dataset(dataset, engine="zarr", consolidated=False)
- else:
- return dataset
def thermovar4d_fromzarr(ds, reshape, key):
- '''' returns 4D variable with dims
- [time, y, x, z] from zarr dataset "ds" '''
+ """' returns 4D variable with dims
+ [time, y, x, z] from zarr dataset "ds" """
- return np.reshape(ds[key].values, reshape)
+ return np.reshape(ds[key].values, reshape)
-class Thermodata:
- def __init__(self, dataset, ntime, ndims, consts):
+class Thermodata:
+ def __init__(self, dataset, ntime, ndims, consts):
+ ds = thermotryopen_dataset(dataset)
- ds = thermotryopen_dataset(dataset)
+ self.consts = consts
- self.consts = consts
+ reshape = [ntime] + list(ndims)
+ self.press = thermovar4d_fromzarr(ds, reshape, "press")
+ self.temp = thermovar4d_fromzarr(ds, reshape, "temp")
+ self.qvap = thermovar4d_fromzarr(ds, reshape, "qvap")
+ self.qcond = thermovar4d_fromzarr(ds, reshape, "qcond")
+ self.theta = self.potential_temp()
- reshape = [ntime] + list(ndims)
- self.press = thermovar4d_fromzarr(ds, reshape, "press")
- self.temp = thermovar4d_fromzarr(ds, reshape, "temp")
- self.qvap = thermovar4d_fromzarr(ds, reshape, "qvap")
- self.qcond = thermovar4d_fromzarr(ds, reshape, "qcond")
- self.theta = self.potential_temp()
+ self.press_units = ds["press"].units # assumed probably hecto-pascals
+ self.temp_units = ds["temp"].units # assumed probably kelvin
+ self.qvap_units = ds["qvap"].units # assumed probably g/Kg
+ self.qcond_units = ds["qcond"].units # assumed probably g/Kg
+ self.theta_units = ds["temp"].units # assumed probably kelvin
- self.press_units = ds["press"].units # assumed probably hecto-pascals
- self.temp_units = ds["temp"].units # assumed probably kelvin
- self.qvap_units = ds["qvap"].units # assumed probably g/Kg
- self.qcond_units = ds["qcond"].units # assumed probably g/Kg
- self.theta_units = ds["temp"].units # assumed probably kelvin
+ def potential_temp(self):
+ """potential temperature, theta"""
- def potential_temp(self):
- ''' potential temperature, theta '''
+ press = self.press * 100 # convert from hPa to Pa
+ qvap = self.qvap / 1000 # convert g/Kg to Kg/Kg
- press = self.press*100 # convert from hPa to Pa
- qvap = self.qvap/1000 # convert g/Kg to Kg/Kg
+ return thermoeqns.dry_pot_temp(self.temp, press, qvap, self.consts)
- return thermoeqns.dry_pot_temp(self.temp, press, qvap, self.consts)
+ def saturationpressure(self):
+ """saturation pressure in hectoPascals"""
- def saturationpressure(self):
- ''' saturation pressure in hectoPascals '''
+ psat = thermoeqns.saturation_pressure(self.temp) # [Pa]
- psat = thermoeqns.saturation_pressure(self.temp) # [Pa]
+ return psat / 100 # [hPa]
- return psat / 100 # [hPa]
+ def vapourpressure(self):
+ """returns vapour pressure in hectoPascals"""
- def vapourpressure(self):
- '''returns vapour pressure in hectoPascals '''
+ p_pascals = self.press * 100 # convert from hPa to Pa
+ qvap = self.qvap / 1000 # convert g/Kg to Kg/Kg
+ Mr_ratio = self.consts["Mr_ratio"]
+ pvap = thermoeqns.vapour_pressure(p_pascals, qvap, Mr_ratio) # [Pa]
- p_pascals = self.press*100 # convert from hPa to Pa
- qvap = self.qvap/1000 # convert g/Kg to Kg/Kg
- Mr_ratio = self.consts["Mr_ratio"]
- pvap = thermoeqns.vapour_pressure(p_pascals, qvap, Mr_ratio) # [Pa]
+ return pvap / 100 # [hPa]
- return pvap / 100 # [hPa]
+ def relative_humidity(self):
+ """returns relative humidty and supersaturation"""
- def relative_humidity(self):
- ''' returns relative humidty and supersaturation '''
+ p_pascals = self.press * 100 # convert from hPa to Pa
+ qvap = self.qvap / 1000 # convert g/Kg to Kg/Kg
+ Mr_ratio = self.consts["Mr_ratio"]
- p_pascals = self.press*100 # convert from hPa to Pa
- qvap = self.qvap/1000 # convert g/Kg to Kg/Kg
- Mr_ratio = self.consts["Mr_ratio"]
+ return thermoeqns.relative_humidity(p_pascals, self.temp, qvap, Mr_ratio)
- return thermoeqns.relative_humidity(p_pascals, self.temp, qvap, Mr_ratio)
+ def supersaturation(self):
+ """returns relative humidty and supersaturation"""
- def supersaturation(self):
- ''' returns relative humidty and supersaturation '''
+ p_pascals = self.press * 100 # convert from hPa to Pa
+ qvap = self.qvap / 1000 # convert g/Kg to Kg/Kg
+ Mr_ratio = self.consts["Mr_ratio"]
+ return thermoeqns.supersaturation(p_pascals, self.temp, qvap, Mr_ratio)
- p_pascals = self.press*100 # convert from hPa to Pa
- qvap = self.qvap/1000 # convert g/Kg to Kg/Kg
- Mr_ratio = self.consts["Mr_ratio"]
- return thermoeqns.supersaturation(p_pascals, self.temp, qvap, Mr_ratio)
+ def __getitem__(self, key):
+ if key == "press":
+ return self.press
+ elif key == "temp":
+ return self.temp
+ elif key == "qvap":
+ return self.qvap
+ elif key == "qcond":
+ return self.qcond
+ elif key == "theta":
+ return self.theta
+ elif key == "relh":
+ return self.relative_humidity()
+ elif key == "supersat":
+ return self.supersaturation()
+ else:
+ err = "no known return provided for " + key + " key"
+ raise ValueError(err)
- def __getitem__(self, key):
- if key == "press":
- return self.press
- elif key == "temp":
- return self.temp
- elif key == "qvap":
- return self.qvap
- elif key == "qcond":
- return self.qcond
- elif key == "theta":
- return self.theta
- elif key == "relh":
- return self.relative_humidity()
- elif key == "supersat":
- return self.supersaturation()
- else:
- err = "no known return provided for "+key+" key"
- raise ValueError(err)
class Winddata:
-
- def __init__(self, dataset, ntime, ndims, consts):
-
- ds = thermotryopen_dataset(dataset)
-
- self.consts = consts
-
- reshape = [ntime] + list(ndims)
- self.wvel = thermovar4d_fromzarr(ds, reshape, "wvel")
- self.uvel = thermovar4d_fromzarr(ds, reshape, "uvel")
- self.vvel = thermovar4d_fromzarr(ds, reshape, "vvel")
-
- self.wvel_units = ds["wvel"].units # probably hecto pascals
- self.uvel_units = ds["uvel"].units # probably hecto pascals
- self.vvel_units = ds["vvel"].units # probably hecto pascals
-
- def __getitem__(self, key):
- if key == "wvel":
- return self.wvel
- elif key == "uvel":
- return self.uvel
- elif key == "vvel":
- return self.vvel
- else:
- err = "no known return provided for "+key+" key"
- raise ValueError(err)
+ def __init__(self, dataset, ntime, ndims, consts):
+ ds = thermotryopen_dataset(dataset)
+
+ self.consts = consts
+
+ reshape = [ntime] + list(ndims)
+ self.wvel = thermovar4d_fromzarr(ds, reshape, "wvel")
+ self.uvel = thermovar4d_fromzarr(ds, reshape, "uvel")
+ self.vvel = thermovar4d_fromzarr(ds, reshape, "vvel")
+
+ self.wvel_units = ds["wvel"].units # probably hecto pascals
+ self.uvel_units = ds["uvel"].units # probably hecto pascals
+ self.vvel_units = ds["vvel"].units # probably hecto pascals
+
+ def __getitem__(self, key):
+ if key == "wvel":
+ return self.wvel
+ elif key == "uvel":
+ return self.uvel
+ elif key == "vvel":
+ return self.vvel
+ else:
+ err = "no known return provided for " + key + " key"
+ raise ValueError(err)
diff --git a/pySD/sdmout_src/thermoeqns.py b/pySD/sdmout_src/thermoeqns.py
index ce21c06b9..70358b677 100644
--- a/pySD/sdmout_src/thermoeqns.py
+++ b/pySD/sdmout_src/thermoeqns.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: thermoeqns.py
Project: sdmout_src
@@ -17,112 +17,116 @@
File Description:
equations in python for calculating thermodynamic
variables e.g. potential temperature
-'''
+"""
import numpy as np
-def vapour_pressure(press, qv, Mr_ratio):
- pv = qv*press/(Mr_ratio + qv)
+def vapour_pressure(press, qv, Mr_ratio):
+ pv = qv * press / (Mr_ratio + qv)
return pv
-def relative_humidity(p, temp, qv, Mr_ratio):
+def relative_humidity(p, temp, qv, Mr_ratio):
pv = vapour_pressure(p, qv, Mr_ratio)
psat = saturation_pressure(temp)
- relh = pv/psat
+ relh = pv / psat
return relh
-def supersaturation(p, temp, qv, Mr_ratio):
+def supersaturation(p, temp, qv, Mr_ratio):
pv = vapour_pressure(p, qv, Mr_ratio)
psat = saturation_pressure(temp)
- qsat = Mr_ratio * psat/(p-pv)
- supersat = qv/qsat - 1
+ qsat = Mr_ratio * psat / (p - pv)
+ supersat = qv / qsat - 1
return supersat
+
def saturation_pressure(TEMP):
- ''' Calculate the equilibrium vapor pressure
- of water over liquid water ie. the
- saturation pressure (psat) given TEMP /K.
- Equation taken from Bjorn Steven's
- "make_tetens" python function from his module
- "moist_thermodynamics.saturation_vapour_pressures"
- available on gitlab. Original paper
- "Murray, F. W. On the Computation of Saturation
- Vapor Pressure. Journal of Applied Meteorology
- and Climatology 6, 203–204 (1967)." '''
+ """Calculate the equilibrium vapor pressure
+ of water over liquid water ie. the
+ saturation pressure (psat) given TEMP /K.
+ Equation taken from Bjorn Steven's
+ "make_tetens" python function from his module
+ "moist_thermodynamics.saturation_vapour_pressures"
+ available on gitlab. Original paper
+ "Murray, F. W. On the Computation of Saturation
+ Vapor Pressure. Journal of Applied Meteorology
+ and Climatology 6, 203–204 (1967)." """
- A = 17.4146 # constants from Bjorn Gitlab originally from paper
- B = 33.639 # ditto
- TREF = 273.16 # Triple point temperature [K] of water
- PREF = 611.655 # Triple point pressure [Pa] of water
+ A = 17.4146 # constants from Bjorn Gitlab originally from paper
+ B = 33.639 # ditto
+ TREF = 273.16 # Triple point temperature [K] of water
+ PREF = 611.655 # Triple point pressure [Pa] of water
- if np.any(TEMP <= 0):
- err = 'Temperature must be larger than 0K.'
- raise ValueError(err)
+ if np.any(TEMP <= 0):
+ err = "Temperature must be larger than 0K."
+ raise ValueError(err)
- expothis = A * (TEMP - TREF) / (TEMP - B)
+ expothis = A * (TEMP - TREF) / (TEMP - B)
- return PREF * np.exp(expothis) # dimensionless psat /Pa
+ return PREF * np.exp(expothis) # dimensionless psat /Pa
def saturation_pressure_murphy_koop(TEMP):
- ''' Calculate the equilibrium vapor pressure
- of water over liquid water ie. the
- saturation pressure (psat [Pa]). Equation taken from
- typhon.physics.thermodynamics.e_eq_water_mk.'''
+ """Calculate the equilibrium vapor pressure
+ of water over liquid water ie. the
+ saturation pressure (psat [Pa]). Equation taken from
+ typhon.physics.thermodynamics.e_eq_water_mk."""
- if np.any(TEMP <= 0):
- err = 'Temperature must be larger than 0K.'
- raise ValueError(err)
+ if np.any(TEMP <= 0):
+ err = "Temperature must be larger than 0K."
+ raise ValueError(err)
- lnpsat = (54.842763 # ln(psat) [Pa]
+ lnpsat = (
+ 54.842763 # ln(psat) [Pa]
- 6763.22 / TEMP
- 4.21 * np.log(TEMP)
+ 0.000367 * TEMP
+ np.tanh(0.0415 * (TEMP - 218.8))
- * (53.878 - 1331.22 / TEMP - 9.44523 * np.log(TEMP) + 0.014025 * TEMP))
+ * (53.878 - 1331.22 / TEMP - 9.44523 * np.log(TEMP) + 0.014025 * TEMP)
+ )
+
+ return np.exp(lnpsat) # psat [Pa]
- return np.exp(lnpsat) # psat [Pa]
def dry_pot_temp(Temp, P, qv, consts):
- ''' calculate potential Temperature [K]
+ """calculate potential Temperature [K]
assuming moist (unsaturated) air with
- vapour content qv '''
+ vapour content qv"""
Cpdry = consts["CP_DRY"]
Cpv = consts["CP_V"]
Rgasdry = consts["RGAS_DRY"]
Rgasv = consts["RGAS_V"]
- Cp = Cpdry * (1+qv*Cpv/Cpdry)/(1+qv)
- Rgas = Rgasdry *(1+qv*Rgasv/Rgasdry)/(1+qv)
+ Cp = Cpdry * (1 + qv * Cpv / Cpdry) / (1 + qv)
+ Rgas = Rgasdry * (1 + qv * Rgasv / Rgasdry) / (1 + qv)
- Theta = Temp*(P[0]/P)**(Rgas/Cp)
+ Theta = Temp * (P[0] / P) ** (Rgas / Cp)
return Theta
-def dry_adiabat(p, temp, consts):
- gamma = (consts["RGAS_DRY"]/consts["CP_DRY"])
- dry_adia = temp[0]*(p/p[0])**gamma # dry adiabatic temp
+def dry_adiabat(p, temp, consts):
+ gamma = consts["RGAS_DRY"] / consts["CP_DRY"]
+ dry_adia = temp[0] * (p / p[0]) ** gamma # dry adiabatic temp
- dry_adia_theta = dry_adia*(p[0]/p)**gamma # dry adiabatic theta (=const)
+ dry_adia_theta = dry_adia * (p[0] / p) ** gamma # dry adiabatic theta (=const)
return dry_adia, dry_adia_theta
def specific_moist_static_energy(Z, Temp, qv, consts):
- ''' calculate the specific mass moist static
- energy, mse, J/Kg. (ie. mse per unit mass). '''
+ """calculate the specific mass moist static
+ energy, mse, J/Kg. (ie. mse per unit mass)."""
- GRAVG = consts["G"] # [m/s^2]
- Latent_v = consts["LATENT_V"] # [J/Kg]
- Cp_dry = consts["CP_DRY"] # [J/Kg/K]
+ GRAVG = consts["G"] # [m/s^2]
+ Latent_v = consts["LATENT_V"] # [J/Kg]
+ Cp_dry = consts["CP_DRY"] # [J/Kg/K]
- return GRAVG*Z + Latent_v*qv + Cp_dry*Temp
+ return GRAVG * Z + Latent_v * qv + Cp_dry * Temp
diff --git a/pySD/sdmout_src/timedata.py b/pySD/sdmout_src/timedata.py
index 6a8f0858a..f422c1ffc 100644
--- a/pySD/sdmout_src/timedata.py
+++ b/pySD/sdmout_src/timedata.py
@@ -1,59 +1,56 @@
-'''
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
----- CLEO -----
File: timedata.py
Project: sdmout_src
-Created Date: Tuesday 24th October 2023
+Created Date: Tuesday 7th May 2024
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Monday 20th November 2023
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
https://opensource.org/licenses/BSD-3-Clause
-----
-Copyright (c) 2023 MPI-M, Clara Bayley
------
File Description:
-python class to get time data
-from SDM zarr store and return
-in various formats
-'''
+python class to get time data from SDM zarr store and return in various formats
+"""
import xarray as xr
-class Time:
-
- def __init__(self, dataset):
-
- timeds = self.tryopen_time(dataset) # time from dataset
- if timeds.units != 's':
- raise ValueError("units of time in dataset must be seconds")
-
- self.secs = timeds.values
- self.mins = self.secs / 60
- self.hrs = self.secs / 60 / 60
-
- def tryopen_time(self, dataset):
- ''' returns time variable (with metadata) from xarray dataset '''
-
- if type(dataset) == str:
- print("time from dataset: ", dataset)
- timeds = xr.open_dataset(dataset, engine="zarr",
- consolidated=False)["time"]
- else:
- timeds = dataset["time"]
-
- return timeds
-
- def __getitem__(self, key):
- if key == "secs":
- return self.secs
- elif key == "mins":
- return self.mins
- elif key == "hrs":
- return self.hrs
- else:
- err = "no known return provided for "+key+" key"
- raise ValueError(err)
+class Time:
+ def __init__(self, dataset):
+ timeds = self.tryopen_time(dataset) # time from dataset
+
+ if timeds.units != "s":
+ raise ValueError("units of time in dataset must be seconds")
+
+ self.secs = timeds.values
+ self.mins = self.secs / 60
+ self.hrs = self.secs / 60 / 60
+
+ def tryopen_time(self, dataset):
+ """returns time variable (with metadata) from xarray dataset"""
+
+ if isinstance(dataset, str):
+ print("time from dataset: ", dataset)
+ timeds = xr.open_dataset(dataset, engine="zarr", consolidated=False)["time"]
+ else:
+ timeds = dataset["time"]
+
+ return timeds
+
+ def __getitem__(self, key):
+ if key == "secs":
+ return self.secs
+ elif key == "mins":
+ return self.mins
+ elif key == "hrs":
+ return self.hrs
+ else:
+ err = "no known return provided for " + key + " key"
+ raise ValueError(err)
diff --git a/pySD/thermobinary_src/create_thermodynamics.py b/pySD/thermobinary_src/create_thermodynamics.py
index 0f1245284..238eac7f7 100644
--- a/pySD/thermobinary_src/create_thermodynamics.py
+++ b/pySD/thermobinary_src/create_thermodynamics.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: create_thermodynamics.py
Project: thermobinary_src
@@ -15,204 +15,246 @@
Copyright (c) 2023 MPI-M, Clara Bayley
-----
File Description:
-'''
+"""
import numpy as np
from os.path import isfile
from .. import cxx2py, readconfigfile, writebinary
-from ..gbxboundariesbinary_src.read_gbxboundaries import read_dimless_gbxboundaries_binary
+from ..gbxboundariesbinary_src.read_gbxboundaries import (
+ read_dimless_gbxboundaries_binary,
+)
-def thermoinputsdict(configfile, constsfile):
- ''' create values from constants file & config file
- required as inputs to create initial
- superdroplet conditions '''
-
- consts = cxx2py.read_cxxconsts_into_floats(constsfile)
- mconsts = cxx2py.derive_more_floats(consts)
- config = readconfigfile.read_configparams_into_floats(configfile)
-
- inputs = {
- # for creating thermodynamic profiles
- "G": consts["G"],
- "CP_DRY": consts["CP_DRY"],
- "RHO_DRY": consts["RHO_DRY"], # dry air density [Kg/m^3]
- "RGAS_DRY": mconsts["RGAS_DRY"],
- "RGAS_V": mconsts["RGAS_V"],
- "Mr_ratio": mconsts["Mr_ratio"],
- "COUPLTSTEP": config["COUPLTSTEP"],
- "T_END": config["T_END"],
-
- # for de-dimensionalising attributes
- "W0": consts["W0"],
- "P0": consts["P0"],
- "TEMP0": consts["TEMP0"],
- "RHO0": mconsts["RHO0"], # characteristic density scale [Kg/m^3]
- "CP0": mconsts["CP0"],
- "COORD0": mconsts["COORD0"], # z coordinate lengthscale [m]
-
- # for reading dimless thermodynamics
- "nspacedims": config["nspacedims"]
- }
-
- inputs["ntime"] = int(np.ceil(inputs["T_END"]/inputs["COUPLTSTEP"]))+1
-
- return inputs
-
-class DimlessThermodynamics:
-
- def __init__(self, inputs=False, configfile="", constsfile=""):
-
- if not inputs:
- inputs = thermoinputsdict(configfile, constsfile)
-
- # scale_factors to de-dimensionalise data
- self.PRESS0 = inputs["P0"]
- self.TEMP0 = inputs["TEMP0"]
- self.qvap0 = 1.0
- self.qcond0 = 1.0
- self.VEL0 = inputs["W0"]
- def makedimless(self, THERMO):
-
- thermodata = {
- "press": THERMO["PRESS"] / self.PRESS0,
- "temp": THERMO["TEMP"] / self.TEMP0,
- "qvap": THERMO["qvap"],
- "qcond": THERMO["qcond"],
- "wvel": THERMO["WVEL"] / self.VEL0,
- "uvel": THERMO["UVEL"] / self.VEL0,
- "vvel": THERMO["VVEL"] / self.VEL0
- }
+def thermoinputsdict(configfile, constsfile):
+ """create values from constants file & config file
+ required as inputs to create initial
+ superdroplet conditions"""
+
+ consts = cxx2py.read_cxxconsts_into_floats(constsfile)
+ mconsts = cxx2py.derive_more_floats(consts)
+ config = readconfigfile.read_configparams_into_floats(configfile)
+
+ inputs = {
+ # for creating thermodynamic profiles
+ "G": consts["G"],
+ "CP_DRY": consts["CP_DRY"],
+ "RHO_DRY": consts["RHO_DRY"], # dry air density [Kg/m^3]
+ "RGAS_DRY": mconsts["RGAS_DRY"],
+ "RGAS_V": mconsts["RGAS_V"],
+ "Mr_ratio": mconsts["Mr_ratio"],
+ "COUPLTSTEP": config["COUPLTSTEP"],
+ "T_END": config["T_END"],
+ # for de-dimensionalising attributes
+ "W0": consts["W0"],
+ "P0": consts["P0"],
+ "TEMP0": consts["TEMP0"],
+ "RHO0": mconsts["RHO0"], # characteristic density scale [Kg/m^3]
+ "CP0": mconsts["CP0"],
+ "COORD0": mconsts["COORD0"], # z coordinate lengthscale [m]
+ # for reading dimless thermodynamics
+ "nspacedims": config["nspacedims"],
+ }
- sfs = [self.PRESS0, self.TEMP0, 1.0, 1.0]
- sfs += [self.VEL0]*3
+ inputs["ntime"] = int(np.ceil(inputs["T_END"] / inputs["COUPLTSTEP"])) + 1
- return thermodata, sfs
+ return inputs
- def redimensionalise(self, thermo):
- THERMODATA = {
- "press": thermo["press"] * self.PRESS0,
- "temp":thermo["temp"] * self.TEMP0,
- "qvap":thermo["qvap"],
- "qcond": thermo["qcond"]
- }
- if "wvel" in thermo.keys():
- THERMODATA["wvel"] = thermo["wvel"] * self.VEL0
- if "uvel" in thermo.keys():
- THERMODATA["uvel"] = thermo["uvel"] * self.VEL0
- if "vvel" in thermo.keys():
- THERMODATA["vvel"] = thermo["vvel"] * self.VEL0
+class DimlessThermodynamics:
+ def __init__(self, inputs=False, configfile="", constsfile=""):
+ if not inputs:
+ inputs = thermoinputsdict(configfile, constsfile)
+
+ # scale_factors to de-dimensionalise data
+ self.PRESS0 = inputs["P0"]
+ self.TEMP0 = inputs["TEMP0"]
+ self.qvap0 = 1.0
+ self.qcond0 = 1.0
+ self.VEL0 = inputs["W0"]
+
+ def makedimless(self, THERMO):
+ thermodata = {
+ "press": THERMO["PRESS"] / self.PRESS0,
+ "temp": THERMO["TEMP"] / self.TEMP0,
+ "qvap": THERMO["qvap"],
+ "qcond": THERMO["qcond"],
+ "wvel": THERMO["WVEL"] / self.VEL0,
+ "uvel": THERMO["UVEL"] / self.VEL0,
+ "vvel": THERMO["VVEL"] / self.VEL0,
+ }
+
+ sfs = [self.PRESS0, self.TEMP0, 1.0, 1.0]
+ sfs += [self.VEL0] * 3
+
+ return thermodata, sfs
+
+ def redimensionalise(self, thermo):
+ THERMODATA = {
+ "press": thermo["press"] * self.PRESS0,
+ "temp": thermo["temp"] * self.TEMP0,
+ "qvap": thermo["qvap"],
+ "qcond": thermo["qcond"],
+ }
+ if "wvel" in thermo.keys():
+ THERMODATA["wvel"] = thermo["wvel"] * self.VEL0
+ if "uvel" in thermo.keys():
+ THERMODATA["uvel"] = thermo["uvel"] * self.VEL0
+ if "vvel" in thermo.keys():
+ THERMODATA["vvel"] = thermo["vvel"] * self.VEL0
+
+ return THERMODATA
- return THERMODATA
def set_arraydtype(arr, dtype):
+ if any(arr):
+ og = type(arr[0])
+ if og != dtype:
+ arr = np.array(arr, dtype=dtype)
- if any(arr):
- og = type(arr[0])
- if og != dtype:
- arr = np.array(arr, dtype=dtype)
+ warning = (
+ "WARNING! dtype of attributes is being changed!"
+ + " from "
+ + str(og)
+ + " to "
+ + str(dtype)
+ )
+ raise ValueError(warning)
- warning = "WARNING! dtype of attributes is being changed!"+\
- " from "+str(og)+" to "+str(dtype)
- raise ValueError(warning)
+ return list(arr)
- return list(arr)
def ctype_compatible_thermodynamics(thermodata):
- ''' check type of gridbox boundaries data is compatible
- with c type double. If not, change type and raise error '''
-
- datatypes = [np.double]*7
+ """check type of gridbox boundaries data is compatible
+ with c type double. If not, change type and raise error"""
- for k, key in enumerate(thermodata.keys()):
+ datatypes = [np.double] * 7
- thermodata[key] = set_arraydtype(thermodata[key], datatypes[k])
+ for k, key in enumerate(thermodata.keys()):
+ thermodata[key] = set_arraydtype(thermodata[key], datatypes[k])
- return thermodata, datatypes
+ return thermodata, datatypes
def check_datashape(thermodata, ndata, ndims, ntime):
- ''' make sure each superdroplet attribute in data has length stated
- in ndata and that this length is compatible with the nummber of
- attributes and superdroplets expected given ndata'''
-
- # expected lengths of data defined on gridbox centres or faces
- cen = int(ntime*np.prod(ndims))
- zface = int(ntime * ndims[2] * ndims[1] * (ndims[0]+1))
- xface = int(ntime * ndims[2] * (ndims[1]+1) * ndims[0])
- yface = int(ntime * (ndims[2]+1) * ndims[1] * ndims[0])
-
- vars = ["press", "temp", "qvap", "qcond"]
- for var in vars:
- lenvar = len(thermodata[var])
- if lenvar != cen:
- err = "\n------ ERROR! -----\n"+\
- str(lenvar)+" "+var+" in thermodynamics data is not the"+\
- " expected length: ntimesteps*ngridboxes = "+str(cen)+\
- "\n---------------------\n"
- raise ValueError(err)
-
- vars = ["wvel", "uvel", "vvel"]
- for var, face in zip(vars, [zface, xface, yface]):
- lenvar = len(thermodata[var])
- if np.logical_and(lenvar, lenvar != face):
- err = "\n------ ERROR! -----\n"+\
- str(lenvar)+" "+var+" in thermodynamics data is not the"+\
- " expected length: ntimesteps*nfaces = "+str(face)+\
- "\n---------------------\n"
- raise ValueError(err)
-
- lens = [len(d) for d in thermodata.values()]
- if lens != ndata:
- err = "inconsistent dimensions of thermodynamic data: "+\
- str(lens)+" compared with given lengths: "+str(ndata)
- raise ValueError(err)
-
-def write_thermodynamics_binary(thermofile, thermogen, configfile,
- constsfile, gridfile):
- ''' write binarys for thermodynamic data over time on C staggered
- grid. So that pressure, temperature, qvap and qcond are defined at
- centres of gridboxes, whereas wind velocities are defined at faces'''
-
- if not isfile(gridfile):
- errmsg = "gridfile not found, but must be"+\
- " created before initsupersfile can be"
- raise ValueError(errmsg)
-
- inputs = thermoinputsdict(configfile, constsfile)
- gbxbounds, ndims = read_dimless_gbxboundaries_binary(gridfile,
- COORD0=inputs["COORD0"],
- return_ndims=True,
- isprint=False)
- thermodata = thermogen.generate_thermo(gbxbounds, ndims, inputs["ntime"])
-
- dth = DimlessThermodynamics(inputs=inputs)
- thermodata, scale_factors = dth.makedimless(thermodata)
-
- ndata = [len(dt) for dt in thermodata.values()]
-
- thermodata, datatypes = ctype_compatible_thermodynamics(thermodata)
- check_datashape(thermodata, ndata, ndims, inputs["ntime"])
-
- units = [b'P', b'K', b' ', b' ']
- units += [b'm']*3 # velocity units
- scale_factors = np.asarray(scale_factors, dtype=np.double)
-
- idot = [i for i, ltr in enumerate(thermofile) if ltr == "."][-1]
- filestem, filetype = thermofile[:idot], thermofile[idot:]
- varat = ["centres"]*4 + ["z-faces", "x-faces", "y-faces"]
- for v, var in enumerate(thermodata.keys()):
- if thermodata[var] != []:
- metastr = 'This file is flattened array of '+var+' variable'+\
- ' for '+str(inputs["ntime"])+' timesteps defined at'+\
- ' grid '+varat[v]+' for grid with dims: '+str(ndims)+\
- ' (ie. file contains '+str(ndata[v])+' datapoints'+\
- ' for '+var+' defined at gridbox '+varat[v]+\
- ' over '+str(inputs["ntime"])+' time steps)'
- filename = filestem+"_"+var+filetype
- writebinary.writebinary(filename, thermodata[var],
- [ndata[v]], [datatypes[v]],
- [units[v]], [scale_factors[v]],
- metastr)
+ """make sure each superdroplet attribute in data has length stated
+ in ndata and that this length is compatible with the nummber of
+ attributes and superdroplets expected given ndata"""
+
+ # expected lengths of data defined on gridbox centres or faces
+ cen = int(ntime * np.prod(ndims))
+ zface = int(ntime * ndims[2] * ndims[1] * (ndims[0] + 1))
+ xface = int(ntime * ndims[2] * (ndims[1] + 1) * ndims[0])
+ yface = int(ntime * (ndims[2] + 1) * ndims[1] * ndims[0])
+
+ vars = ["press", "temp", "qvap", "qcond"]
+ for var in vars:
+ lenvar = len(thermodata[var])
+ if lenvar != cen:
+ err = (
+ "\n------ ERROR! -----\n"
+ + str(lenvar)
+ + " "
+ + var
+ + " in thermodynamics data is not the"
+ + " expected length: ntimesteps*ngridboxes = "
+ + str(cen)
+ + "\n---------------------\n"
+ )
+ raise ValueError(err)
+
+ vars = ["wvel", "uvel", "vvel"]
+ for var, face in zip(vars, [zface, xface, yface]):
+ lenvar = len(thermodata[var])
+ if np.logical_and(lenvar, lenvar != face):
+ err = (
+ "\n------ ERROR! -----\n"
+ + str(lenvar)
+ + " "
+ + var
+ + " in thermodynamics data is not the"
+ + " expected length: ntimesteps*nfaces = "
+ + str(face)
+ + "\n---------------------\n"
+ )
+ raise ValueError(err)
+
+ lens = [len(d) for d in thermodata.values()]
+ if lens != ndata:
+ err = (
+ "inconsistent dimensions of thermodynamic data: "
+ + str(lens)
+ + " compared with given lengths: "
+ + str(ndata)
+ )
+ raise ValueError(err)
+
+
+def write_thermodynamics_binary(
+ thermofile, thermogen, configfile, constsfile, gridfile
+):
+ """write binarys for thermodynamic data over time on C staggered
+ grid. So that pressure, temperature, qvap and qcond are defined at
+ centres of gridboxes, whereas wind velocities are defined at faces"""
+
+ if not isfile(gridfile):
+ errmsg = (
+ "gridfile not found, but must be" + " created before initsupersfile can be"
+ )
+ raise ValueError(errmsg)
+
+ inputs = thermoinputsdict(configfile, constsfile)
+ gbxbounds, ndims = read_dimless_gbxboundaries_binary(
+ gridfile, COORD0=inputs["COORD0"], return_ndims=True, isprint=False
+ )
+ thermodata = thermogen.generate_thermo(gbxbounds, ndims, inputs["ntime"])
+
+ dth = DimlessThermodynamics(inputs=inputs)
+ thermodata, scale_factors = dth.makedimless(thermodata)
+
+ ndata = [len(dt) for dt in thermodata.values()]
+
+ thermodata, datatypes = ctype_compatible_thermodynamics(thermodata)
+ check_datashape(thermodata, ndata, ndims, inputs["ntime"])
+
+ units = [b"P", b"K", b" ", b" "]
+ units += [b"m"] * 3 # velocity units
+ scale_factors = np.asarray(scale_factors, dtype=np.double)
+
+ idot = [i for i, ltr in enumerate(thermofile) if ltr == "."][-1]
+ filestem, filetype = thermofile[:idot], thermofile[idot:]
+ varat = ["centres"] * 4 + ["z-faces", "x-faces", "y-faces"]
+ for v, var in enumerate(thermodata.keys()):
+ if thermodata[var] != []:
+ metastr = (
+ "This file is flattened array of "
+ + var
+ + " variable"
+ + " for "
+ + str(inputs["ntime"])
+ + " timesteps defined at"
+ + " grid "
+ + varat[v]
+ + " for grid with dims: "
+ + str(ndims)
+ + " (ie. file contains "
+ + str(ndata[v])
+ + " datapoints"
+ + " for "
+ + var
+ + " defined at gridbox "
+ + varat[v]
+ + " over "
+ + str(inputs["ntime"])
+ + " time steps)"
+ )
+ filename = filestem + "_" + var + filetype
+ writebinary.writebinary(
+ filename,
+ thermodata[var],
+ [ndata[v]],
+ [datatypes[v]],
+ [units[v]],
+ [scale_factors[v]],
+ metastr,
+ )
diff --git a/pySD/thermobinary_src/read_thermodynamics.py b/pySD/thermobinary_src/read_thermodynamics.py
index 1fff218af..e678693ca 100644
--- a/pySD/thermobinary_src/read_thermodynamics.py
+++ b/pySD/thermobinary_src/read_thermodynamics.py
@@ -1,4 +1,7 @@
-'''
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
----- CLEO -----
File: read_thermodynamics.py
Project: thermobinary_src
@@ -6,17 +9,14 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Wednesday 10th January 2024
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
https://opensource.org/licenses/BSD-3-Clause
-----
-Copyright (c) 2023 MPI-M, Clara Bayley
------
File Description:
-'''
-
+"""
import numpy as np
import matplotlib.pyplot as plt
@@ -27,399 +27,443 @@
from ..readbinary import readbinary
from ..gbxboundariesbinary_src import read_gbxboundaries as rgrid
+
def relative_humidity(press, temp, qvap, Mr_ratio):
+ pv = qvap * press / (Mr_ratio + qvap) # vapour pressure
+ psat = saturation_press(temp)
+ relh = pv / psat
- pv = qvap*press/(Mr_ratio + qvap) # vapour pressure
- psat = saturation_press(temp)
- relh = pv/psat
+ qsat = Mr_ratio * psat / (press - pv)
+ supersat = qvap / qsat - 1
- qsat = Mr_ratio * psat/(press-pv)
- supersat = qvap/qsat - 1
+ return relh, supersat
- return relh, supersat
def potential_temperature(press, temp, press_ref, RGAS, CP):
+ theta = temp * (press_ref / press) ** (RGAS / CP)
- theta = temp * (press_ref / press) ** (RGAS / CP)
+ return theta
- return theta
class ThermoOnGrid:
+ def __init__(self, thermodata, inputs, ndims):
+ dth = DimlessThermodynamics(inputs=inputs)
+ thermodata = dth.redimensionalise(thermodata)
+
+ ntime = inputs["ntime"]
+ ndims = [int(n) for n in ndims]
+ cen = [inputs["ntime"]] + list(np.flip(ndims))
+ zface = [ntime, ndims[2], ndims[1], ndims[0] + 1]
+ xface = [ntime, ndims[2], ndims[1] + 1, ndims[0]]
+ yface = [ntime, ndims[2] + 1, ndims[1], ndims[0]]
+
+ self.vars = ["press", "temp", "qvap", "qcond"]
+ self.press = np.reshape(thermodata["press"], cen)
+ self.temp = np.reshape(thermodata["temp"], cen)
+ self.qvap = np.reshape(thermodata["qvap"], cen)
+ self.qcond = np.reshape(thermodata["qcond"], cen)
+
+ if "wvel" in thermodata.keys():
+ self.wvel = np.reshape(thermodata["wvel"], zface)
+ self.wvel_cens = (self.wvel[:, :, :, 1:] + self.wvel[:, :, :, :-1]) / 2
+ self.vars += ["wvel", "wvel_cens"]
+ if "uvel" in thermodata.keys():
+ self.uvel = np.reshape(thermodata["uvel"], xface)
+ self.uvel_cens = (self.uvel[:, :, 1:, :] + self.uvel[:, :, :-1, :]) / 2
+ self.vars += ["uvel", "uvel_cens"]
+ if "vvel" in thermodata.keys():
+ self.vvel = np.reshape(thermodata["vvel"], yface)
+ self.vvel_cens = (
+ self.vvel[:, 1:, :, :] + self.vvel[:, :-1, :, :]
+ ) / 2
+ self.vars += ["vvel", "vvel_cens"]
+
+ def __getitem__(self, key):
+ if key not in self.vars:
+ err = "no variable " + key + " in ThermoOnGrid"
+ raise ValueError(err)
+ elif key == "press":
+ return self.press
+ elif key == "temp":
+ return self.temp
+ elif key == "qvap":
+ return self.qvap
+ elif key == "qcond":
+ return self.qcond
+
+ elif key == "wvel":
+ return self.wvel
+ elif key == "uvel":
+ return self.uvel
+ elif key == "vvel":
+ return self.vvel
+
+ elif key == "wvel_cens":
+ return self.wvel_cens
+ elif key == "uvel_cens":
+ return self.uvel_cens
+ elif key == "vvel_cens":
+ return self.vvel_cens
+
+ def xymean(self, var):
+ """mean over x and y"""
+
+ return np.mean(var, axis=(1, 2)) # d ims [time, z]
+
+ def ytmean(self, var):
+ """mean over time and y"""
+
+ return np.mean(var, axis=(0, 1)) # dims [x, z]
+
+ def xytmean(self, var):
+ """mean over time, x and y"""
+
+ return np.mean(var, axis=(0, 1, 2)) # dims [z]
+
+
+def thermovar_from_binary(var, thermofile, shape, ntime, ndims, dtype, isprint=True):
+ idot = [i for i, ltr in enumerate(thermofile) if ltr == "."][-1]
+ filestem, filetype = thermofile[:idot], thermofile[idot:]
+ filename = filestem + "_" + var + filetype
+ data, ndata = readbinary(filename, isprint=isprint)
+
+ if ndata != int(np.prod(shape)):
+ err = (
+ str(ndata)
+ + " is incorrect data length for "
+ + var
+ + " defined for "
+ + str(ntime)
+ + " timesteps"
+ + " on grid with dims = "
+ + str(ndims)
+ )
+ raise ValueError(err)
+ else:
+ data = np.reshape(np.asarray(data, dtype=dtype), shape)
+
+ return data
+
+
+def read_dimless_thermodynamics_binary(thermofile, ndims, ntime, nspacedims):
+ # expected lengths of data defined on gridbox centres or faces
+ cen = [ntime, int(np.prod(ndims))]
+ zface = [ntime, int(ndims[2] * ndims[1] * (ndims[0] + 1))]
+ xface = [ntime, int(ndims[2] * (ndims[1] + 1) * ndims[0])]
+ yface = [ntime, int((ndims[2] + 1) * ndims[1] * ndims[0])]
+
+ thermodata = {}
+
+ vars = ["press", "temp", "qvap", "qcond"]
+ datatypes = [np.double] * 4
+ for v, var in enumerate(vars):
+ thermodata[var] = thermovar_from_binary(
+ var, thermofile, cen, ntime, ndims, datatypes[v], isprint=False
+ )
+
+ datatypes = [np.double] * 3
+ if nspacedims >= 1:
+ thermodata["wvel"] = thermovar_from_binary(
+ "wvel", thermofile, zface, ntime, ndims, datatypes[0], isprint=False
+ )
+ if nspacedims >= 2:
+ thermodata["uvel"] = thermovar_from_binary(
+ "uvel", thermofile, xface, ntime, ndims, datatypes[1], isprint=False
+ )
+ if nspacedims >= 3:
+ thermodata["vvel"] = thermovar_from_binary(
+ "vvel", thermofile, yface, ntime, ndims, datatypes[2], isprint=False
+ )
+
+ return thermodata
+
+
+def get_thermodynamics_from_thermofile(
+ thermofile, ndims, inputs=False, constsfile="", configfile=""
+):
+ if not inputs:
+ inputs = thermoinputsdict(configfile, constsfile)
+
+ thermodata = read_dimless_thermodynamics_binary(
+ thermofile, ndims, inputs["ntime"], inputs["nspacedims"]
+ ) # dimensionless data [time, gridboxes]
+ thermodata = ThermoOnGrid(thermodata, inputs, ndims)
+
+ return thermodata # data with units in 4D arrays with dims [time, y, x, z]
+
+
+def plot_thermodynamics(constsfile, configfile, gridfile, thermofile, binpath, savefig):
+ plt.rcParams.update({"font.size": 14})
- def __init__(self, thermodata, inputs, ndims):
-
- dth = DimlessThermodynamics(inputs=inputs)
- thermodata = dth.redimensionalise(thermodata)
-
- ntime = inputs["ntime"]
- ndims = [int(n) for n in ndims]
- cen = [inputs["ntime"]] + list(np.flip(ndims))
- zface = [ntime, ndims[2], ndims[1], ndims[0]+1]
- xface = [ntime, ndims[2], ndims[1]+1, ndims[0]]
- yface = [ntime, ndims[2]+1, ndims[1], ndims[0]]
-
- self.vars = ["press", "temp", "qvap", "qcond"]
- self.press = np.reshape(thermodata["press"], cen)
- self.temp = np.reshape(thermodata["temp"], cen)
- self.qvap= np.reshape(thermodata["qvap"], cen)
- self.qcond = np.reshape(thermodata["qcond"], cen)
-
- if "wvel" in thermodata.keys():
- self.wvel = np.reshape(thermodata["wvel"], zface)
- self.wvel_cens = (self.wvel[:,:,:,1:] + self.wvel[:,:,:,:-1])/2
- self.vars += ["wvel", "wvel_cens"]
- if "uvel" in thermodata.keys():
- self.uvel = np.reshape(thermodata["uvel"], xface)
- self.uvel_cens = (self.uvel[:,:,1:,:] + self.uvel[:,:,:-1,:])/2
- self.vars += ["uvel", "uvel_cens"]
- if "vvel" in thermodata.keys():
- self.vvel = np.reshape(thermodata["vvel"], yface)
- self.vvel_cens = (self.vvel[:,1:,:,:] + self.vvel[:,:-1,:,:])/2
- self.vars += ["vvel", "vvel_cens"]
-
- def __getitem__(self, key):
- if key not in self.vars:
- err = "no variable "+key+" in ThermoOnGrid"
- raise ValueError(err)
- elif key == "press":
- return self.press
- elif key == "temp":
- return self.temp
- elif key == "qvap":
- return self.qvap
- elif key == "qcond":
- return self.qcond
-
- elif key == "wvel":
- return self.wvel
- elif key == "uvel":
- return self.uvel
- elif key == "vvel":
- return self.vvel
-
- elif key == "wvel_cens":
- return self.wvel_cens
- elif key == "uvel_cens":
- return self.uvel_cens
- elif key == "vvel_cens":
- return self.vvel_cens
-
- def xymean(self, var):
- '''mean over x and y'''
-
- return np.mean(var, axis=(1,2)) #d ims [time, z]
-
- def ytmean(self, var):
- '''mean over time and y'''
-
- return np.mean(var, axis=(0,1)) # dims [x, z]
-
- def xytmean(self, var):
- '''mean over time, x and y'''
-
- return np.mean(var, axis=(0,1,2)) # dims [z]
-
-def thermovar_from_binary(var, thermofile, shape,
- ntime, ndims, dtype,
- isprint=True):
-
- idot = [i for i, ltr in enumerate(thermofile) if ltr == "."][-1]
- filestem, filetype = thermofile[:idot], thermofile[idot:]
- filename = filestem+"_"+var+filetype
- data, ndata = readbinary(filename, isprint=isprint)
-
- if ndata != int(np.prod(shape)):
- err = str(ndata)+" is incorrect data length for "+var+\
- " defined for "+str(ntime)+" timesteps"+\
- " on grid with dims = "+str(ndims)
- raise ValueError(err)
- else:
- data = np.reshape(np.asarray(data, dtype=dtype), shape)
-
- return data
-
-def read_dimless_thermodynamics_binary(thermofile, ndims,
- ntime, nspacedims):
-
- # expected lengths of data defined on gridbox centres or faces
- cen = [ntime, int(np.prod(ndims))]
- zface = [ntime, int(ndims[2] * ndims[1] * (ndims[0]+1))]
- xface = [ntime, int(ndims[2] * (ndims[1]+1) * ndims[0])]
- yface = [ntime, int((ndims[2]+1) * ndims[1] * ndims[0])]
-
- thermodata = {}
-
- vars = ["press", "temp", "qvap", "qcond"]
- datatypes = [np.double]*4
- for v, var in enumerate(vars):
- thermodata[var] = thermovar_from_binary(var, thermofile, cen,
- ntime, ndims, datatypes[v],
- isprint=False)
-
- datatypes = [np.double]*3
- if nspacedims >= 1:
- thermodata["wvel"] = thermovar_from_binary("wvel", thermofile, zface,
- ntime, ndims, datatypes[0],
- isprint=False)
- if nspacedims >= 2:
- thermodata["uvel"] = thermovar_from_binary("uvel", thermofile, xface,
- ntime, ndims, datatypes[1],
- isprint=False)
- if nspacedims >= 3:
- thermodata["vvel"] = thermovar_from_binary("vvel", thermofile, yface,
- ntime, ndims, datatypes[2],
- isprint=False)
-
- return thermodata
-
-def get_thermodynamics_from_thermofile(thermofile, ndims, inputs=False,
- constsfile="", configfile=""):
-
- if not inputs:
inputs = thermoinputsdict(configfile, constsfile)
-
- thermodata = read_dimless_thermodynamics_binary(thermofile, ndims,
- inputs["ntime"],
- inputs["nspacedims"]) # dimensionless data [time, gridboxes]
- thermodata = ThermoOnGrid(thermodata, inputs, ndims)
-
- return thermodata # data with units in 4D arrays with dims [time, y, x, z]
-
-def plot_thermodynamics(constsfile, configfile, gridfile,
- thermofile, binpath, savefig):
-
- plt.rcParams.update({'font.size': 14})
-
- inputs = thermoinputsdict(configfile, constsfile)
- gbxbounds, ndims = rgrid.read_dimless_gbxboundaries_binary(gridfile,
- COORD0=inputs["COORD0"],
- return_ndims=True,
- isprint=False)
- xyzhalf = rgrid.halfcoords_from_gbxbounds(gbxbounds, isprint=False) #[m]
- zhalf, xhalf, yhalf = [half/1000 for half in xyzhalf] #convery [m] to [km]
- zfull, xfull, yfull = rgrid.fullcell_fromhalfcoords(zhalf, xhalf, yhalf) #[m]
-
- thermodata = get_thermodynamics_from_thermofile(thermofile, ndims,
- inputs=inputs)
-
- plot_1dprofiles(zfull, thermodata, inputs["Mr_ratio"],
- inputs["RGAS_DRY"], inputs["CP_DRY"],
- binpath, savefig)
+ gbxbounds, ndims = rgrid.read_dimless_gbxboundaries_binary(
+ gridfile, COORD0=inputs["COORD0"], return_ndims=True, isprint=False
+ )
+ xyzhalf = rgrid.halfcoords_from_gbxbounds(gbxbounds, isprint=False) # [m]
+ zhalf, xhalf, yhalf = [half / 1000 for half in xyzhalf] # convery [m] to [km]
+ zfull, xfull, yfull = rgrid.fullcell_fromhalfcoords(zhalf, xhalf, yhalf) # [m]
+
+ thermodata = get_thermodynamics_from_thermofile(thermofile, ndims, inputs=inputs)
+
+ plot_1dprofiles(
+ zfull,
+ thermodata,
+ inputs["Mr_ratio"],
+ inputs["RGAS_DRY"],
+ inputs["CP_DRY"],
+ binpath,
+ savefig,
+ )
if inputs["nspacedims"] > 1:
- xxh, zzh = np.meshgrid(xhalf, zhalf, indexing="ij") # dims [xdims, zdims]
- xxf, zzf = np.meshgrid(xfull, zfull, indexing="ij") # dims [xdims, zdims]
- plot_2dcolormaps(zzh, xxh, zzf, xxf, thermodata, inputs, binpath, savefig)
- plot_2dwindfield(zzh, xxh, zzf, xxf, thermodata["wvel_cens"],
- thermodata["uvel_cens"], binpath, savefig)
+ xxh, zzh = np.meshgrid(xhalf, zhalf, indexing="ij") # dims [xdims, zdims]
+ xxf, zzf = np.meshgrid(xfull, zfull, indexing="ij") # dims [xdims, zdims]
+ plot_2dcolormaps(zzh, xxh, zzf, xxf, thermodata, inputs, binpath, savefig)
+ plot_2dwindfield(
+ zzh,
+ xxh,
+ zzf,
+ xxf,
+ thermodata["wvel_cens"],
+ thermodata["uvel_cens"],
+ binpath,
+ savefig,
+ )
+
def try1dplot(ax, nplots, data, zfull, label):
+ try:
+ ax.plot(data, zfull, marker="x") # (fails for 0D model)
+ except ValueError as e:
+ print("Plotting failed with ValueError:", e, " using ax.scatter instead.")
+ ax.scatter(data, zfull, marker="x")
- try:
- ax.plot(data, zfull, marker="x") # (fails for 0D model)
- except:
- ax.scatter(data, zfull, marker="x")
+ ax.set_xlabel(label)
- ax.set_xlabel(label)
+ return nplots + 1
- return nplots + 1
-def plot_1dthermodynamics(axs, n, zfull ,thermodata,
- Mr_ratio, RGAS_DRY, CP_DRY):
+def plot_1dthermodynamics(axs, n, zfull, thermodata, Mr_ratio, RGAS_DRY, CP_DRY):
+ vars = ["press", "temp", "qvap", "qcond"]
+ units = [" /Pa", " /K", "", ""]
- vars = ["press", "temp", "qvap", "qcond"]
- units = [" /Pa", " /K", "", ""]
+ for var, unit in zip(vars, units):
+ if var in thermodata.vars:
+ profalltime = thermodata.xymean(thermodata[var]) # 1d profile at all times
+ label = var + unit
+ n = try1dplot(axs[n], n, profalltime.T, zfull[None, :].T, label)
- for var, unit in zip(vars, units):
- if var in thermodata.vars:
- profalltime = thermodata.xymean(thermodata[var]) # 1d profile at all times
- label=var+unit
- n = try1dplot(axs[n], n, profalltime.T, zfull[None,:].T, label)
+ pressxy = thermodata.xymean(thermodata.press)
+ tempxy = thermodata.xymean(thermodata.temp)
+ qvapxy = thermodata.xymean(thermodata.qvap)
+ supersat = relative_humidity(pressxy, tempxy, qvapxy, Mr_ratio)[1]
+ label = "supersaturation"
+ n = try1dplot(axs[n], n, supersat.T, zfull[None, :].T, label)
- pressxy = thermodata.xymean(thermodata.press)
- tempxy = thermodata.xymean(thermodata.temp)
- qvapxy = thermodata.xymean(thermodata.qvap)
- supersat = relative_humidity(pressxy, tempxy, qvapxy, Mr_ratio)[1]
- label = "supersaturation"
- n = try1dplot(axs[n], n, supersat.T, zfull[None,:].T, label)
+ press_ref = pressxy[0, 0]
+ theta = potential_temperature(pressxy, tempxy, press_ref, RGAS_DRY, CP_DRY)
+ label = "\u03F4 /K"
+ n = try1dplot(axs[n], n, theta.T, zfull[None, :].T, label)
- press_ref = pressxy[0, 0]
- theta = potential_temperature(pressxy, tempxy, press_ref,
- RGAS_DRY, CP_DRY)
- label="\u03F4 /K"
- n = try1dplot(axs[n], n, theta.T, zfull[None,:].T, label)
+ return n
- return n
def plot_1dwindprofiles(axs, n, zfull, thermodata):
+ vars = ["wvel_cens", "uvel_cens", "vvel_cens"]
+ units = [" /ms$^{-1}$"] * 3
- vars = ["wvel_cens", "uvel_cens", "vvel_cens"]
- units = [" /ms$^{-1}$"]*3
-
- for var, unit in zip(vars, units):
- if var in thermodata.vars:
- profalltime = thermodata.xymean(thermodata[var]) # 1d profile at all times
- label=var+unit
- n = try1dplot(axs[n], n, profalltime.T, zfull[None,:].T, label)
+ for var, unit in zip(vars, units):
+ if var in thermodata.vars:
+ profalltime = thermodata.xymean(thermodata[var]) # 1d profile at all times
+ label = var + unit
+ n = try1dplot(axs[n], n, profalltime.T, zfull[None, :].T, label)
- return n
+ return n
-def plot_1dprofiles(zfull, thermodata, Mr_ratio, RGAS_DRY, CP_DRY,
- binpath, savefig):
+def plot_1dprofiles(zfull, thermodata, Mr_ratio, RGAS_DRY, CP_DRY, binpath, savefig):
fig, axs = plt.subplots(nrows=3, ncols=3, figsize=(16, 8))
axs = axs.flatten()
- nplots = plot_1dthermodynamics(axs, 0, zfull ,thermodata,
- Mr_ratio, RGAS_DRY, CP_DRY)
- nplots = plot_1dwindprofiles(axs, nplots, zfull ,thermodata)
+ nplots = plot_1dthermodynamics(
+ axs, 0, zfull, thermodata, Mr_ratio, RGAS_DRY, CP_DRY
+ )
+ nplots = plot_1dwindprofiles(axs, nplots, zfull, thermodata)
for a in range(nplots, len(axs), 1):
- axs[a].remove() # delete unused axes
+ axs[a].remove() # delete unused axes
for ax in [axs[0], axs[3], axs[6]]:
- ax.set_ylabel("z /km")
+ ax.set_ylabel("z /km")
fig.tight_layout()
if savefig:
savename = "/thermo1dalltimeprofiles.png"
- fig.savefig(binpath+savename, dpi=400,
- bbox_inches="tight", facecolor='w', format="png")
- print("Figure .png saved as: "+binpath+savename)
+ fig.savefig(
+ binpath + savename,
+ dpi=400,
+ bbox_inches="tight",
+ facecolor="w",
+ format="png",
+ )
+ print("Figure .png saved as: " + binpath + savename)
plt.show()
+
def plot_2dmean(ax, meanfunc, xxh, zzh, var, label, unit, norm, cmap):
+ mean2d = meanfunc(var) # avg over time and y axes
+ if np.nanmin(mean2d) != np.nanmax(mean2d):
+ pcm = ax.pcolormesh(xxh[:, :], zzh[:, :], mean2d, cmap=cmap, norm=norm)
+ cb = plt.colorbar(pcm, ax=ax, location="top", label=label + unit)
+ return mean2d, pcm, cb
+ else:
+ txt = label + " = {:.2f}".format(np.nanmin(mean2d)) + unit
+ ax.text(0.5, 0.5, txt, ha="center")
+ return np.nanmin(mean2d), False, False
- mean2d = meanfunc(var) #avg over time and y axes
- if np.nanmin(mean2d) != np.nanmax(mean2d):
- pcm = ax.pcolormesh(xxh[:,:], zzh[:,:], mean2d, cmap=cmap, norm=norm)
- cb = plt.colorbar(pcm, ax=ax, location="top", label=label+unit)
- return mean2d, pcm, cb
- else:
- txt=label+" = {:.2f}".format(np.nanmin(mean2d))+unit
- ax.text(0.5, 0.5, txt, ha='center')
- return np.nanmin(mean2d), False, False
def plot_2dcontour(ax, xxf, zzf, mean2d, contour, cb, cbticks):
+ cb.ax.set_xticks(cbticks)
+ cb.ax.set_xticklabels(["{:.3g}".format(c) for c in cbticks])
+
+ try:
+ ax.contour(
+ xxf, zzf, mean2d, levels=[contour], linestyles=["--"], colors=["grey"]
+ )
+ except ValueError as e:
+ print("Plotting failed with ValueError:", e, "WARNING: not plotting contour")
+
+ cb.ax.plot([contour] * 2, [0, 1], color="grey", linewidth=0.95)
+
+
+def relh_supersat_theta_colomaps(
+ axs, zzh, xxh, zzf, xxf, thermodata, Mr_ratio, RGAS_DRY, CP_DRY
+):
+ relh, supersat = relative_humidity(
+ thermodata.press, thermodata.temp, thermodata.qvap, Mr_ratio
+ )
+ relh = relh * 100 # convert relative humidity to %
+
+ press_ref = thermodata.xymean(thermodata.press)[0, 0]
+ theta = potential_temperature(
+ thermodata.press, thermodata.temp, press_ref, RGAS_DRY, CP_DRY
+ )
+
+ vars = [relh, supersat, theta]
+ labels = ["% relative humidity", "supersaturation", "\u03F4"]
+ units = ["", "", " /K"]
+ cmaps = ["Blues", "PuOr", "RdBu_r"]
+ norms = [
+ colors.CenteredNorm(vcenter=np.mean(relh)),
+ colors.TwoSlopeNorm(vcenter=0.0),
+ colors.CenteredNorm(vcenter=np.nanmin(theta)),
+ ]
+ contours = [1.0, 0.0, None]
+
+ n = 0
+ for var, label, unit, norm, cmap, cont in zip(
+ vars, labels, units, norms, cmaps, contours
+ ):
+ mean2d, pcm, cb = plot_2dmean(
+ axs[n], thermodata.ytmean, xxh, zzh, var, label, unit, norm, cmap
+ )
+
+ if pcm is not False:
+ if n == 1:
+ cbticks = [pcm.norm.vmin, pcm.norm.vcenter, pcm.norm.vmax]
+ else:
+ cbticks = np.linspace(pcm.norm.vmin, pcm.norm.vmax, 5)
+ plot_2dcontour(axs[n], xxf, zzf, mean2d, cont, cb, cbticks)
+
+ n += 1
+
+
+def plot_2dcolormaps(zzh, xxh, zzf, xxf, thermodata, inputs, binpath, savefig):
+ vars = ["press", "temp", "qvap", "qcond"]
+ units = [" /Pa", " /K", "", ""]
+ cmaps = ["PRGn", "RdBu_r", "BrBG", "BrBG"]
+ cmapcens = [
+ np.nanmin(thermodata.press),
+ np.nanmin(thermodata.temp),
+ np.mean(thermodata.qvap),
+ 0.0,
+ ]
+
+ fig, axs = plt.subplots(nrows=2, ncols=4, figsize=(13, 8))
+ axs = axs.flatten()
+ axs[-1].remove()
+
+ n = 0
+ for var, unit, cmapcen, cmap in zip(vars, units, cmapcens, cmaps):
+ norm = colors.CenteredNorm(vcenter=cmapcen)
+ plot_2dmean(
+ axs[n], thermodata.ytmean, xxh, zzh, thermodata[var], var, unit, norm, cmap
+ )
+ n += 1
+
+ relh_supersat_theta_colomaps(
+ [axs[4], axs[5], axs[6]],
+ zzh,
+ xxh,
+ zzf,
+ xxf,
+ thermodata,
+ inputs["Mr_ratio"],
+ inputs["RGAS_DRY"],
+ inputs["CP_DRY"],
+ )
+
+ for ax in axs:
+ ax.set_aspect("equal")
+
+ axs[0].set_ylabel("z /km")
+ axs[3].set_ylabel("z /km")
+ for ax in axs[3:]:
+ ax.set_xlabel("x /km")
+
+ fig.tight_layout()
+ if savefig:
+ savename = "/thermo2dmeanprofiles.png"
+ fig.savefig(
+ binpath + savename,
+ dpi=400,
+ bbox_inches="tight",
+ facecolor="w",
+ format="png",
+ )
+ print("Figure .png saved as: " + binpath + savename)
+ plt.show()
+
+
+def plot_2dwindfield(zzh, xxh, zzf, xxf, wvel_cens, uvel_cens, binpath, savefig):
+ wcen = np.mean(wvel_cens, axis=(0, 1)) # avg over y and time axes
+ ucen = np.mean(uvel_cens, axis=(0, 1))
+ norm = np.sqrt(wcen**2 + ucen**2)
- cb.ax.set_xticks(cbticks)
- cb.ax.set_xticklabels(["{:.3g}".format(c) for c in cbticks])
-
- try:
- ax.contour(xxf, zzf, mean2d, levels=[contour],
- linestyles=["--"], colors=["grey"])
- except:
- print("WARNING: contour not plotted")
-
- cb.ax.plot([contour]*2, [0, 1], color='grey', linewidth=0.95)
-
-def relh_supersat_theta_colomaps(axs, zzh, xxh, zzf, xxf, thermodata,
- Mr_ratio, RGAS_DRY, CP_DRY):
-
- relh, supersat = relative_humidity(thermodata.press,
- thermodata.temp,
- thermodata.qvap,
- Mr_ratio)
- relh = relh * 100 # convert relative humidity to %
-
- press_ref = thermodata.xymean(thermodata.press)[0, 0]
- theta = potential_temperature(thermodata.press, thermodata.temp,
- press_ref, RGAS_DRY, CP_DRY)
-
- vars = [relh, supersat, theta]
- labels = ["% relative humidity", "supersaturation", "\u03F4"]
- units = ["", "", " /K"]
- cmaps = ["Blues", "PuOr", "RdBu_r"]
- norms = [colors.CenteredNorm(vcenter=np.mean(relh)),
- colors.TwoSlopeNorm(vcenter=0.0),
- colors.CenteredNorm(vcenter=np.nanmin(theta))]
- contours = [1.0, 0.0, None]
-
- n = 0
- for var, label, unit, norm, cmap, cont in zip(vars, labels,
- units, norms,
- cmaps, contours):
- mean2d, pcm, cb = plot_2dmean(axs[n], thermodata.ytmean, xxh, zzh,
- var, label, unit, norm, cmap)
-
- if pcm != False:
- if n == 1:
- cbticks = [pcm.norm.vmin, pcm.norm.vcenter, pcm.norm.vmax]
- else:
- cbticks = np.linspace(pcm.norm.vmin, pcm.norm.vmax, 5)
- plot_2dcontour(axs[n], xxf, zzf, mean2d, cont, cb, cbticks)
-
- n += 1
-
-def plot_2dcolormaps(zzh, xxh, zzf, xxf,
- thermodata, inputs, binpath, savefig):
-
- vars = ["press", "temp", "qvap", "qcond"]
- units = [" /Pa", " /K", "", ""]
- cmaps = ["PRGn", "RdBu_r", "BrBG", "BrBG"]
- cmapcens = [np.nanmin(thermodata.press),
- np.nanmin(thermodata.temp),
- np.mean(thermodata.qvap),
- 0.0]
-
- fig, axs = plt.subplots(nrows=2, ncols=4, figsize=(13,8))
- axs = axs.flatten()
- axs[-1].remove()
-
- n = 0
- for var, unit, cmapcen, cmap in zip(vars, units, cmapcens, cmaps):
- norm=colors.CenteredNorm(vcenter=cmapcen)
- plot_2dmean(axs[n], thermodata.ytmean, xxh, zzh, thermodata[var],
- var, unit, norm, cmap)
- n += 1
-
- relh_supersat_theta_colomaps([axs[4], axs[5], axs[6]],
- zzh, xxh, zzf, xxf, thermodata,
- inputs["Mr_ratio"], inputs["RGAS_DRY"],
- inputs["CP_DRY"])
-
- for ax in axs:
- ax.set_aspect("equal")
-
- axs[0].set_ylabel("z /km")
- axs[3].set_ylabel("z /km")
- for ax in axs[3:]:
- ax.set_xlabel("x /km")
-
- fig.tight_layout()
- if savefig:
- savename = "/thermo2dmeanprofiles.png"
- fig.savefig(binpath+savename, dpi=400,
- bbox_inches="tight", facecolor='w', format="png")
- print("Figure .png saved as: "+binpath+savename)
- plt.show()
-
-def plot_2dwindfield(zzh, xxh, zzf, xxf, wvel_cens, uvel_cens,
- binpath, savefig):
-
- wcen = np.mean(wvel_cens, axis=(0,1)) # avg over y and time axes
- ucen = np.mean(uvel_cens, axis=(0,1))
- norm = np.sqrt(wcen**2 + ucen**2)
-
- fig, axs = plt.subplots(nrows=1, ncols=3, figsize=(9,6))
- axs = axs.flatten()
-
- label = ["w", "u", "|wind velocity|"]
- units = " / m s$^{-1}$"
- cmaps = ["coolwarm"]*3
- for v, vel in enumerate([wcen, ucen, norm]):
- pcm = axs[v].pcolormesh(xxh, zzh, vel, cmap=cmaps[v])
- plt.colorbar(pcm, ax=axs[v], location="top", label=label[v]+units)
- axs[2].quiver(xxf, zzf, ucen, wcen)
-
- axs[0].set_ylabel("z /km")
- for ax in axs:
- ax.set_xlabel("x /km")
- ax.set_aspect("equal")
-
- fig.tight_layout()
- if savefig:
- savename = "/thermowindprofiles.png"
- fig.savefig(binpath+savename, dpi=400,
- bbox_inches="tight", facecolor='w', format="png")
- print("Figure .png saved as: "+binpath+savename)
- plt.show()
+ fig, axs = plt.subplots(nrows=1, ncols=3, figsize=(9, 6))
+ axs = axs.flatten()
+
+ label = ["w", "u", "|wind velocity|"]
+ units = " / m s$^{-1}$"
+ cmaps = ["coolwarm"] * 3
+ for v, vel in enumerate([wcen, ucen, norm]):
+ pcm = axs[v].pcolormesh(xxh, zzh, vel, cmap=cmaps[v])
+ plt.colorbar(pcm, ax=axs[v], location="top", label=label[v] + units)
+ axs[2].quiver(xxf, zzf, ucen, wcen)
+
+ axs[0].set_ylabel("z /km")
+ for ax in axs:
+ ax.set_xlabel("x /km")
+ ax.set_aspect("equal")
+
+ fig.tight_layout()
+ if savefig:
+ savename = "/thermowindprofiles.png"
+ fig.savefig(
+ binpath + savename,
+ dpi=400,
+ bbox_inches="tight",
+ facecolor="w",
+ format="png",
+ )
+ print("Figure .png saved as: " + binpath + savename)
+ plt.show()
diff --git a/pySD/thermobinary_src/thermogen.py b/pySD/thermobinary_src/thermogen.py
index 8cd542273..7b2861d41 100644
--- a/pySD/thermobinary_src/thermogen.py
+++ b/pySD/thermobinary_src/thermogen.py
@@ -1,4 +1,7 @@
-'''
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
----- CLEO -----
File: thermogen.py
Project: thermobinary_src
@@ -6,16 +9,14 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Friday 9th February 2024
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
https://opensource.org/licenses/BSD-3-Clause
-----
-Copyright (c) 2023 MPI-M, Clara Bayley
------
File Description:
-'''
+"""
import numpy as np
from scipy import integrate
@@ -23,570 +24,597 @@
from .create_thermodynamics import thermoinputsdict
from ..gbxboundariesbinary_src import read_gbxboundaries as rgrid
+
def get_Mrratio_from_constsfile(constsfile):
+ consts = cxx2py.read_cxxconsts_into_floats(constsfile)
+ mconsts = cxx2py.derive_more_floats(consts)
- consts = cxx2py.read_cxxconsts_into_floats(constsfile)
- mconsts = cxx2py.derive_more_floats(consts)
+ return mconsts["Mr_ratio"]
- return mconsts["Mr_ratio"]
def saturation_press(TEMP):
- ''' Calculate the equilibrium vapor pressure of water over
- liquid water ie. the saturation pressure (psat) given the
- temperature [K]. Equation from Bjorn Steven's "make_tetens"
- function in module "moist_thermodynamics.saturation_vapour_pressures"
- available on gitlab. Original paper "Murray, F. W. On the
- Computation of Saturation Vapor Pressure. Journal of Applied
- Meteorology and Climatology 6, 203–204 (1967). '''
+ """Calculate the equilibrium vapor pressure of water over
+ liquid water ie. the saturation pressure (psat) given the
+ temperature [K]. Equation from Bjorn Steven's "make_tetens"
+ function in module "moist_thermodynamics.saturation_vapour_pressures"
+ available on gitlab. Original paper "Murray, F. W. On the
+ Computation of Saturation Vapor Pressure. Journal of Applied
+ Meteorology and Climatology 6, 203–204 (1967)."""
- Aconst = 17.4146
- Bconst = 33.639
- TREF = 273.16 # Triple point temperature [K] of water
- PREF = 611.655 # Triple point pressure [Pa] of water
+ Aconst = 17.4146
+ Bconst = 33.639
+ TREF = 273.16 # Triple point temperature [K] of water
+ PREF = 611.655 # Triple point pressure [Pa] of water
- if (np.any(TEMP <= 0.0)):
- raise ValueError("psat ERROR: T must be larger than 0K."+\
- " T = " + str(TEMP))
+ if np.any(TEMP <= 0.0):
+ raise ValueError("psat ERROR: T must be larger than 0K." + " T = " + str(TEMP))
- return PREF * np.exp(Aconst * (TEMP - TREF) / (TEMP - Bconst)) # [Pa]
+ return PREF * np.exp(Aconst * (TEMP - TREF) / (TEMP - Bconst)) # [Pa]
def relh2qvap(press, temp, relh, Mr_ratio):
- ''' convert relative humidity [%] (relh) into vapour mass
- mixing ratio (qvap) given ambient temperature and pressure
- and ratio of molecular masses: vapour/air '''
+ """convert relative humidity [%] (relh) into vapour mass
+ mixing ratio (qvap) given ambient temperature and pressure
+ and ratio of molecular masses: vapour/air"""
+
+ vapourpress = saturation_press(temp) * relh / 100.0 # [Pa]
- vapourpress = saturation_press(temp) * relh / 100.0 # [Pa]
+ qvap = Mr_ratio * vapourpress / (press - vapourpress) # dimensionless [Kg/kg]
- qvap = Mr_ratio * vapourpress / (press - vapourpress) # dimensionless [Kg/kg]
+ return qvap
- return qvap
def sratio2qvap(sratio, press, temp, Mr_ratio):
+ psat = saturation_press(temp)
- psat = saturation_press(temp)
+ qvap = Mr_ratio * sratio
+ qvap = qvap / (press / psat - 1)
- qvap = Mr_ratio * sratio
- qvap = qvap / (press/psat - 1)
+ return qvap
- return qvap
def qparams_to_qvap(method, params, Mr_ratio, PRESS, TEMP):
- ''' returns qvaps given list of qvaps, supersaturation ratios
- or relative humidities '''
+ """returns qvaps given list of qvaps, supersaturation ratios
+ or relative humidities"""
+
+ if method == "qvap":
+ qparams = params
+ return qparams
- if method == "qvap":
- qparams = params
- return qparams
+ elif method == "sratio":
+ qparams = []
+ for sratio in params:
+ qparams.append(sratio2qvap(sratio, PRESS, TEMP, Mr_ratio))
+ return qparams
- elif method == "sratio":
- qparams = []
- for sratio in params:
- qparams.append(sratio2qvap(sratio, PRESS, TEMP, Mr_ratio))
- return qparams
+ elif method == "relh":
+ qparams = []
+ for relh in params:
+ qparams.append(relh2qvap(PRESS, TEMP, relh, Mr_ratio))
+ return qparams
- elif method == "relh":
- qparams = []
- for relh in params:
- qparams.append(relh2qvap(PRESS, TEMP, relh, Mr_ratio))
- return qparams
+ else:
+ raise ValueError("valid method not given to generate qvap")
- else:
- raise ValueError("valid method not given to generate qvap")
def constant_winds(ndims, ntime, THERMODATA, WVEL, UVEL, VVEL):
- ''' add arrays to thermodata dictionary for winds, array are
- empty by default, or given constant values WVEl, UVEL and VVEL.
- Here, shape_[X]face = no. data for wind velocity component
- defined on gridbox [X] faces '''
+ """add arrays to thermodata dictionary for winds, array are
+ empty by default, or given constant values WVEl, UVEL and VVEL.
+ Here, shape_[X]face = no. data for wind velocity component
+ defined on gridbox [X] faces"""
- for VEL in ["WVEL", "UVEL", "VVEL"]:
- THERMODATA[VEL] = np.array([])
+ for VEL in ["WVEL", "UVEL", "VVEL"]:
+ THERMODATA[VEL] = np.array([])
- if WVEL != None:
- shape_zface = int((ndims[0]+1)*ndims[1]*ndims[2]*ntime)
- THERMODATA["WVEL"] = np.full(shape_zface, WVEL)
+ if WVEL is not None:
+ shape_zface = int((ndims[0] + 1) * ndims[1] * ndims[2] * ntime)
+ THERMODATA["WVEL"] = np.full(shape_zface, WVEL)
- if UVEL != None:
- shape_xface = int((ndims[1]+1)*ndims[2]*ndims[0]*ntime)
- THERMODATA["UVEL"] = np.full(shape_xface, UVEL)
+ if UVEL is not None:
+ shape_xface = int((ndims[1] + 1) * ndims[2] * ndims[0] * ntime)
+ THERMODATA["UVEL"] = np.full(shape_xface, UVEL)
- if VVEL != None:
- shape_yface = int((ndims[2]+1)*ndims[0]*ndims[1]*ntime)
- THERMODATA["VVEL"] = np.full(shape_yface, VVEL)
+ if VVEL is not None:
+ shape_yface = int((ndims[2] + 1) * ndims[0] * ndims[1] * ntime)
+ THERMODATA["VVEL"] = np.full(shape_yface, VVEL)
- return THERMODATA
+ return THERMODATA
-def divfree_flowfield2D(wmax, zlength, xlength,
- rhotilda_zfaces, rhotilda_xfaces,
- gbxbounds, ndims):
- zfaces, xcens_z = rgrid.coords_forgridboxfaces(gbxbounds, ndims, 'z')[0:2]
- zcens_x, xfaces = rgrid.coords_forgridboxfaces(gbxbounds, ndims, 'x')[0:2]
+def divfree_flowfield2D(
+ wmax, zlength, xlength, rhotilda_zfaces, rhotilda_xfaces, gbxbounds, ndims
+):
+ zfaces, xcens_z = rgrid.coords_forgridboxfaces(gbxbounds, ndims, "z")[0:2]
+ zcens_x, xfaces = rgrid.coords_forgridboxfaces(gbxbounds, ndims, "x")[0:2]
ztilda = zlength / np.pi
- xtilda = xlength / (2*np.pi)
+ xtilda = xlength / (2 * np.pi)
wamp = 2 * wmax
WVEL = wamp / rhotilda_zfaces
WVEL = WVEL * np.sin(zfaces / ztilda) * np.sin(xcens_z / xtilda)
UVEL = wamp / rhotilda_xfaces * xtilda / ztilda
- UVEL = UVEL * np.cos(zcens_x/ztilda) * np.cos(xfaces/xtilda)
+ UVEL = UVEL * np.cos(zcens_x / ztilda) * np.cos(xfaces / xtilda)
return WVEL, UVEL
-class ConstUniformThermo:
- ''' create thermodynamics that's constant in
- time and uniform throughout the domain '''
-
- def __init__(self, PRESS, TEMP, qvap,
- qcond, WVEL, UVEL, VVEL,
- relh=False, constsfile=""):
- self.PRESS = PRESS # pressure [Pa]
- self.TEMP = TEMP # temperature [T]
-
- if relh:
- Mr_ratio = get_Mrratio_from_constsfile(constsfile)
- self.qvap = relh2qvap(PRESS, TEMP,
- relh, Mr_ratio) # water vapour content []
- else:
- self.qvap = qvap
-
- self.qcond = qcond # liquid water content []
- self.WVEL = WVEL # vertical velocity [m/s]
- self.UVEL = UVEL # horizontal eastwards velocity [m/s]
- self.VVEL = VVEL # horizontal northwards velocity [m/s]
-
-
- def generate_winds(self, ndims, ntime, THERMODATA):
-
- return constant_winds(ndims, ntime, THERMODATA,
- self.WVEL, self.UVEL, self.VVEL)
-
- def generate_thermo(self, gbxbounds, ndims, ntime):
- # shape_cen = ngridboxes * ntime = no. data for var defined at gridbox centers
- shape_cen = int(ntime * np.prod(ndims))
- THERMODATA = {
- "PRESS": np.full(shape_cen, self.PRESS),
- "TEMP": np.full(shape_cen, self.TEMP),
- "qvap": np.full(shape_cen, self.qvap),
- "qcond": np.full(shape_cen, self.qcond),
- }
-
- THERMODATA = self.generate_winds(ndims, ntime, THERMODATA)
+class ConstUniformThermo:
+ """create thermodynamics that's constant in
+ time and uniform throughout the domain"""
+
+ def __init__(
+ self, PRESS, TEMP, qvap, qcond, WVEL, UVEL, VVEL, relh=False, constsfile=""
+ ):
+ self.PRESS = PRESS # pressure [Pa]
+ self.TEMP = TEMP # temperature [T]
+
+ if relh:
+ Mr_ratio = get_Mrratio_from_constsfile(constsfile)
+ self.qvap = relh2qvap(
+ PRESS, TEMP, relh, Mr_ratio
+ ) # water vapour content []
+ else:
+ self.qvap = qvap
+
+ self.qcond = qcond # liquid water content []
+ self.WVEL = WVEL # vertical velocity [m/s]
+ self.UVEL = UVEL # horizontal eastwards velocity [m/s]
+ self.VVEL = VVEL # horizontal northwards velocity [m/s]
+
+ def generate_winds(self, ndims, ntime, THERMODATA):
+ return constant_winds(ndims, ntime, THERMODATA, self.WVEL, self.UVEL, self.VVEL)
+
+ def generate_thermo(self, gbxbounds, ndims, ntime):
+ # shape_cen = ngridboxes * ntime = no. data for var defined at gridbox centers
+ shape_cen = int(ntime * np.prod(ndims))
+ THERMODATA = {
+ "PRESS": np.full(shape_cen, self.PRESS),
+ "TEMP": np.full(shape_cen, self.TEMP),
+ "qvap": np.full(shape_cen, self.qvap),
+ "qcond": np.full(shape_cen, self.qcond),
+ }
+
+ THERMODATA = self.generate_winds(ndims, ntime, THERMODATA)
+
+ return THERMODATA
- return THERMODATA
class SimpleThermo2DFlowField:
- ''' create thermodynamics that's constant in time
- with (P,T,qc) uniform throughout the domain with relative humidity
- = 0.95 below Zbase and a 2D (z,x) dependent flow field'''
+ """create thermodynamics that's constant in time
+ with (P,T,qc) uniform throughout the domain with relative humidity
+ = 0.95 below Zbase and a 2D (z,x) dependent flow field"""
- def __init__(self, configfile, constsfile, PRESS, TEMP, qvapmethod,
- qvapparams, Zbase, qcond, WMAX, Zlength, Xlength, VVEL):
+ def __init__(
+ self,
+ configfile,
+ constsfile,
+ PRESS,
+ TEMP,
+ qvapmethod,
+ qvapparams,
+ Zbase,
+ qcond,
+ WMAX,
+ Zlength,
+ Xlength,
+ VVEL,
+ ):
+ inputs = thermoinputsdict(configfile, constsfile)
- inputs = thermoinputsdict(configfile, constsfile)
+ self.PRESS = PRESS # pressure [Pa]
+ self.TEMP = TEMP # temperature [T]
+ self.qcond = qcond # liquid water content []
- self.PRESS = PRESS # pressure [Pa]
- self.TEMP = TEMP # temperature [T]
- self.qcond = qcond # liquid water content []
+ # determine qvap [below, above] z (cloud) base
+ self.Zbase = Zbase
+ qvaps = qparams_to_qvap(qvapmethod, qvapparams, inputs["Mr_ratio"], PRESS, TEMP)
+ self.qvap_below, self.qvap_above = qvaps
- # determine qvap [below, above] z (cloud) base
- self.Zbase = Zbase
- qvaps = qparams_to_qvap(qvapmethod, qvapparams,
- inputs["Mr_ratio"], PRESS, TEMP)
- self.qvap_below, self.qvap_above = qvaps
+ self.WMAX = WMAX # max velocities constant
+ self.Zlength = Zlength # wavelength of velocity modulation in z direction [m]
+ self.Xlength = Xlength # wavelength of velocity modulation in x direction [m]
+ self.VVEL = VVEL # horizontal (y) velocity
- self.WMAX = WMAX # max velocities constant
- self.Zlength = Zlength # wavelength of velocity modulation in z direction [m]
- self.Xlength = Xlength # wavelength of velocity modulation in x direction [m]
- self.VVEL = VVEL # horizontal (y) velocity
+ self.RGAS_DRY = inputs["RGAS_DRY"]
+ self.RGAS_V = inputs["RGAS_V"]
+ self.RHO0 = inputs["RHO0"]
- self.RGAS_DRY = inputs["RGAS_DRY"]
- self.RGAS_V = inputs["RGAS_V"]
- self.RHO0 = inputs["RHO0"]
+ def generate_qvap_profile(self, zfulls):
+ qvap = np.where(zfulls >= self.Zbase, self.qvap_above, self.qvap_below)
- def generate_qvap_profile(self, zfulls):
+ return qvap
- qvap = np.where(zfulls >= self.Zbase, self.qvap_above, self.qvap_below)
+ def rhotilda(self, ZCOORDS):
+ """returns dimensionless rho_dry profile for use in stream function"""
- return qvap
+ PRESS, TEMP = self.hydrostatic_adiabatic_thermo(ZCOORDS)
- def rhotilda(self, ZCOORDS):
- ''' returns dimensionless rho_dry profile for use in stream function'''
+ RHO_DRY = PRESS / ((self.RGAS_DRY + self.qvap * self.RGAS_V) * TEMP)
- PRESS, TEMP = self.hydrostatic_adiabatic_thermo(ZCOORDS)
+ rhotilda = RHO_DRY / self.RHO0
- RHO_DRY = PRESS / ((self.RGAS_DRY + self.qvap * self.RGAS_V) * TEMP)
+ return rhotilda
- rhotilda = RHO_DRY/self.RHO0
+ def wvel_uvel_from_flowfield(self, THERMODATA, gbxbounds, ndims):
+ PRESS, TEMP = THERMODATA["PRESS"][0], THERMODATA["TEMP"][0]
+ qvap = THERMODATA["qvap"][0]
+ rho_dry = PRESS / (TEMP * (self.RGAS_DRY + qvap * self.RGAS_V))
+ rhotilda = rho_dry / self.RHO0 # scalar value is the same over entire domain
- return rhotilda
+ WVEL, UVEL = divfree_flowfield2D(
+ self.WMAX, self.Zlength, self.Xlength, rhotilda, rhotilda, gbxbounds, ndims
+ )
+ return WVEL, UVEL
- def wvel_uvel_from_flowfield(self, THERMODATA, gbxbounds, ndims):
+ def generate_winds(self, gbxbounds, ndims, ntime, THERMODATA):
+ for VEL in ["WVEL", "UVEL", "VVEL"]:
+ THERMODATA[VEL] = np.array([])
- PRESS, TEMP = THERMODATA["PRESS"][0], THERMODATA["TEMP"][0]
- qvap = THERMODATA["qvap"][0]
- rho_dry = PRESS / (TEMP * (self.RGAS_DRY + qvap * self.RGAS_V))
- rhotilda = rho_dry / self.RHO0 # scalar value is the same over entire domain
+ if self.WMAX is not None:
+ WVEL, UVEL = self.wvel_uvel_from_flowfield(THERMODATA, gbxbounds, ndims)
+ THERMODATA["WVEL"] = np.tile(WVEL, ntime)
+ THERMODATA["UVEL"] = np.tile(UVEL, ntime)
- WVEL, UVEL = divfree_flowfield2D(self.WMAX, self.Zlength, self.Xlength,
- rhotilda, rhotilda, gbxbounds, ndims)
- return WVEL, UVEL
+ if self.VVEL is not None:
+ shape_yface = int((ndims[2] + 1) * ndims[0] * ndims[1] * ntime)
+ THERMODATA["VVEL"] = np.full(shape_yface, self.VVEL)
- def generate_winds(self, gbxbounds, ndims, ntime, THERMODATA):
+ return THERMODATA
- for VEL in ["WVEL", "UVEL", "VVEL"]:
- THERMODATA[VEL] = np.array([])
+ def generate_thermo(self, gbxbounds, ndims, ntime):
+ zfulls, xfulls, yfulls = rgrid.fullcoords_forallgridboxes(gbxbounds, ndims)
- if self.WMAX != None:
- WVEL, UVEL = self.wvel_uvel_from_flowfield(THERMODATA, gbxbounds, ndims)
- THERMODATA["WVEL"] = np.tile(WVEL, ntime)
- THERMODATA["UVEL"] = np.tile(UVEL, ntime)
+ qvap = self.generate_qvap_profile(zfulls)
- if self.VVEL != None:
- shape_yface = int((ndims[2]+1)*ndims[0]*ndims[1]*ntime)
- THERMODATA["VVEL"] = np.full(shape_yface, self.VVEL)
+ shape_cen = int(ntime * np.prod(ndims))
+ THERMODATA = {
+ "PRESS": np.full(shape_cen, self.PRESS),
+ "TEMP": np.full(shape_cen, self.TEMP),
+ "qvap": np.tile(qvap, ntime),
+ "qcond": np.full(shape_cen, self.qcond),
+ }
- return THERMODATA
-
- def generate_thermo(self, gbxbounds, ndims, ntime):
-
- zfulls, xfulls, yfulls = rgrid.fullcoords_forallgridboxes(gbxbounds,
- ndims)
+ THERMODATA = self.generate_winds(gbxbounds, ndims, ntime, THERMODATA)
- qvap = self.generate_qvap_profile(zfulls)
+ return THERMODATA
- shape_cen = int(ntime * np.prod(ndims))
- THERMODATA = {
- "PRESS": np.full(shape_cen, self.PRESS),
- "TEMP": np.full(shape_cen, self.TEMP),
- "qvap": np.tile(qvap, ntime),
- "qcond": np.full(shape_cen, self.qcond),
- }
-
- THERMODATA = self.generate_winds(gbxbounds, ndims,
- ntime, THERMODATA)
-
- return THERMODATA
class ConstDryHydrostaticAdiabat:
- ''' create thermodynamics that's constant in time
- and in hydrostatic equillibrium with a dry adiabat
- accounting for the mass of water vapour in the air.
- Equations derived from Arabas et al. 2015 (sect 2.1) '''
-
- def __init__(self, configfile, constsfile, PRESSz0, THETA,
- qvapmethod, qvapparams, Zbase, qcond, WMAX,
- Zlength, Xlength, VVEL, moistlayer):
-
- inputs = thermoinputsdict(configfile, constsfile)
-
- ### parameters of profile ###
- self.PRESSz0 = PRESSz0 # pressure at z=0m [Pa]
- self.THETA = THETA # (constant) dry potential temperature [K]
- self.qcond = qcond # liquid mass mixing ratio []
- self.WMAX = WMAX # max velocities constant
- self.Zlength = Zlength # wavelength of velocity modulation in z direction [m]
- self.Xlength = Xlength # wavelength of velocity modulation in x direction [m]
- self.VVEL = VVEL # horizontal (y) velocity
-
- # determine qvap [below, above] z (cloud) base
- self.Zbase = Zbase
- self.qvapmethod, self.qvapparams = qvapmethod, qvapparams
- self.qvapz0 = qparams_to_qvap(qvapmethod, qvapparams,
- inputs["Mr_ratio"], self.PRESSz0,
- self.THETA)[0]
- self.moistlayer = moistlayer
-
- ### constants ###
- self.GRAVG = inputs["G"]
- self.CP_DRY = inputs["CP_DRY"]
- self.RGAS_DRY = inputs["RGAS_DRY"]
- self.RGAS_V = inputs["RGAS_V"]
- self.RC_DRY = self.RGAS_DRY / self.CP_DRY
- self.RCONST = 1 + self.qvapz0 * self.RGAS_V / self.RGAS_DRY
- self.P1000 = 100000 # P_1000 = 1000 hPa [Pa]
- self.CP0 = inputs["CP0"]
- self.RHO0 = inputs["RHO0"]
- self.Mr_ratio = inputs["Mr_ratio"]
-
- alpha = PRESSz0 / (self.RCONST * self.P1000)
- self.TEMPz0 = THETA * np.power(alpha, self.RC_DRY) # temperature at z=0m [K]
-
- beta = (1+self.qvapz0) / self.RCONST / self.RGAS_DRY
- self.RHOz0 = beta * self.PRESSz0 / self.TEMPz0
+ """create thermodynamics that's constant in time
+ and in hydrostatic equillibrium with a dry adiabat
+ accounting for the mass of water vapour in the air.
+ Equations derived from Arabas et al. 2015 (sect 2.1)"""
+
+ def __init__(
+ self,
+ configfile,
+ constsfile,
+ PRESSz0,
+ THETA,
+ qvapmethod,
+ qvapparams,
+ Zbase,
+ qcond,
+ WMAX,
+ Zlength,
+ Xlength,
+ VVEL,
+ moistlayer,
+ ):
+ inputs = thermoinputsdict(configfile, constsfile)
+
+ ### parameters of profile ###
+ self.PRESSz0 = PRESSz0 # pressure at z=0m [Pa]
+ self.THETA = THETA # (constant) dry potential temperature [K]
+ self.qcond = qcond # liquid mass mixing ratio []
+ self.WMAX = WMAX # max velocities constant
+ self.Zlength = Zlength # wavelength of velocity modulation in z direction [m]
+ self.Xlength = Xlength # wavelength of velocity modulation in x direction [m]
+ self.VVEL = VVEL # horizontal (y) velocity
+
+ # determine qvap [below, above] z (cloud) base
+ self.Zbase = Zbase
+ self.qvapmethod, self.qvapparams = qvapmethod, qvapparams
+ self.qvapz0 = qparams_to_qvap(
+ qvapmethod, qvapparams, inputs["Mr_ratio"], self.PRESSz0, self.THETA
+ )[0]
+ self.moistlayer = moistlayer
+
+ ### constants ###
+ self.GRAVG = inputs["G"]
+ self.CP_DRY = inputs["CP_DRY"]
+ self.RGAS_DRY = inputs["RGAS_DRY"]
+ self.RGAS_V = inputs["RGAS_V"]
+ self.RC_DRY = self.RGAS_DRY / self.CP_DRY
+ self.RCONST = 1 + self.qvapz0 * self.RGAS_V / self.RGAS_DRY
+ self.P1000 = 100000 # P_1000 = 1000 hPa [Pa]
+ self.CP0 = inputs["CP0"]
+ self.RHO0 = inputs["RHO0"]
+ self.Mr_ratio = inputs["Mr_ratio"]
+
+ alpha = PRESSz0 / (self.RCONST * self.P1000)
+ self.TEMPz0 = THETA * np.power(alpha, self.RC_DRY) # temperature at z=0m [K]
+
+ beta = (1 + self.qvapz0) / self.RCONST / self.RGAS_DRY
+ self.RHOz0 = beta * self.PRESSz0 / self.TEMPz0
+
+ def hydrostatic_adiabatic_profile(self, ZCOORDS):
+ """returns *profile* of density (not the density itself!)
+ rho = rhoprofile^((1-RC_DRY)/RC_DRY) = profile^pow"""
+
+ pow = 1 / self.RC_DRY - 1
+
+ Aa = (1 + self.qvapz0) * np.power(self.P1000, self.RC_DRY)
+ Aa = self.THETA * self.RGAS_DRY / Aa
+ Aconst = self.RCONST * np.power(Aa, (1 / (1 - self.RC_DRY)))
+
+ RHOconst = -1 * self.GRAVG * self.RC_DRY / Aconst
+ RHOprofile = np.power(self.RHOz0, 1 / pow) + RHOconst * ZCOORDS # RHO^pow
+
+ return RHOprofile, Aconst
+
+ def hydrostatic_adiabatic_thermo(self, ZCOORDS):
+ RHOprof, Aconst = self.hydrostatic_adiabatic_profile(ZCOORDS)
+
+ # RHO = np.power(RHOprof, (1 / self.RC_DRY - 1))
+
+ PRESS = Aconst * np.power(RHOprof, 1 / self.RC_DRY)
+
+ TEMPconst = np.power(Aconst / (self.RCONST * self.P1000), self.RC_DRY)
+ TEMP = self.THETA * TEMPconst * RHOprof
+
+ return PRESS, TEMP
+
+ def rhotilda(self, ZCOORDS):
+ """returns dimensionless rho_dry profile for use in stream function"""
+
+ PRESS, TEMP = self.hydrostatic_adiabatic_thermo(ZCOORDS)
+
+ RHO_DRY = PRESS / ((self.RGAS_DRY + self.qvapz0 * self.RGAS_V) * TEMP)
+
+ rhotilda = RHO_DRY / self.RHO0
+
+ return rhotilda
+
+ def wvel_uvel_from_flowfield(self, gbxbounds, ndims):
+ zfaces = rgrid.coords_forgridboxfaces(gbxbounds, ndims, "z")[0]
+ xfaces = rgrid.coords_forgridboxfaces(gbxbounds, ndims, "x")[1]
+ rhotilda_zfaces = self.rhotilda(zfaces)
+ rhotilda_xfaces = self.rhotilda(xfaces)
+
+ WVEL, UVEL = divfree_flowfield2D(
+ self.WMAX,
+ self.Zlength,
+ self.Xlength,
+ rhotilda_zfaces,
+ rhotilda_xfaces,
+ gbxbounds,
+ ndims,
+ )
+ return WVEL, UVEL
+
+ def generate_winds(self, gbxbounds, ndims, ntime, THERMODATA):
+ for VEL in ["WVEL", "UVEL", "VVEL"]:
+ THERMODATA[VEL] = np.array([])
+
+ if self.WMAX is not None:
+ WVEL, UVEL = self.wvel_uvel_from_flowfield(gbxbounds, ndims)
+ THERMODATA["WVEL"] = np.tile(WVEL, ntime)
+ THERMODATA["UVEL"] = np.tile(UVEL, ntime)
- def hydrostatic_adiabatic_profile(self, ZCOORDS):
- ''' returns *profile* of density (not the density itself!)
- rho = rhoprofile^((1-RC_DRY)/RC_DRY) = profile^pow '''
+ if self.VVEL is not None:
+ shape_yface = int((ndims[2] + 1) * ndims[0] * ndims[1] * ntime)
+ THERMODATA["VVEL"] = np.full(shape_yface, self.VVEL)
- pow = 1/self.RC_DRY - 1
+ return THERMODATA
- Aa = (1+self.qvapz0)*np.power(self.P1000, self.RC_DRY)
- Aa = (self.THETA * self.RGAS_DRY / Aa)
- Aconst = self.RCONST * np.power(Aa, (1 / (1-self.RC_DRY)))
+ def generate_qvap(self, zfulls, xfulls, PRESS, TEMP):
+ qvaps = qparams_to_qvap(
+ self.qvapmethod, self.qvapparams, self.Mr_ratio, PRESS, TEMP
+ )
+ qvap = np.where(zfulls < self.Zbase, qvaps[0], qvaps[1])
- RHOconst = -1 * self.GRAVG * self.RC_DRY / Aconst
- RHOprofile = np.power(self.RHOz0, 1/pow) + RHOconst * ZCOORDS # RHO^pow
+ if self.moistlayer:
+ z1, z2 = self.moistlayer["z1"], self.moistlayer["z2"]
+ x1, x2 = self.moistlayer["x1"], self.moistlayer["x2"]
+ mlqvap = sratio2qvap(
+ self.moistlayer["mlsratio"], PRESS, TEMP, self.Mr_ratio
+ )
+ moistregion = (
+ (zfulls >= z1) & (zfulls < z2) & (xfulls >= x1) & (xfulls < x2)
+ )
+ qvap = np.where(moistregion, mlqvap, qvap)
- return RHOprofile, Aconst
+ return qvap
- def hydrostatic_adiabatic_thermo(self, ZCOORDS):
+ def generate_thermo(self, gbxbounds, ndims, ntime):
+ zfulls, xfulls, yfulls = rgrid.fullcoords_forallgridboxes(gbxbounds, ndims)
+ PRESS, TEMP = self.hydrostatic_adiabatic_thermo(zfulls)
- RHOprof, Aconst = self.hydrostatic_adiabatic_profile(ZCOORDS)
+ qvap = self.generate_qvap(zfulls, xfulls, PRESS, TEMP)
- RHO = np.power(RHOprof, (1/self.RC_DRY - 1))
+ shape_cen = int(ntime * np.prod(ndims))
+ THERMODATA = {
+ "PRESS": np.tile(PRESS, ntime),
+ "TEMP": np.tile(TEMP, ntime),
+ "qvap": np.tile(qvap, ntime),
+ "qcond": np.full(shape_cen, self.qcond),
+ }
- PRESS = Aconst * np.power(RHOprof, 1/self.RC_DRY)
+ THERMODATA = self.generate_winds(gbxbounds, ndims, ntime, THERMODATA)
- TEMPconst = np.power(Aconst / (self.RCONST * self.P1000), self.RC_DRY)
- TEMP = self.THETA * TEMPconst * RHOprof
+ return THERMODATA
- return PRESS, TEMP
-
- def rhotilda(self, ZCOORDS):
- ''' returns dimensionless rho_dry profile for use in stream function'''
-
- PRESS, TEMP = self.hydrostatic_adiabatic_thermo(ZCOORDS)
-
- RHO_DRY = PRESS / ((self.RGAS_DRY + self.qvapz0 * self.RGAS_V) * TEMP)
-
- rhotilda = RHO_DRY/self.RHO0
-
- return rhotilda
-
- def wvel_uvel_from_flowfield(self, gbxbounds, ndims):
-
- zfaces = rgrid.coords_forgridboxfaces(gbxbounds, ndims, 'z')[0]
- xfaces = rgrid.coords_forgridboxfaces(gbxbounds, ndims, 'x')[1]
- rhotilda_zfaces = self.rhotilda(zfaces)
- rhotilda_xfaces = self.rhotilda(xfaces)
-
- WVEL, UVEL = divfree_flowfield2D(self.WMAX, self.Zlength, self.Xlength,
- rhotilda_zfaces, rhotilda_xfaces,
- gbxbounds, ndims)
- return WVEL, UVEL
-
- def generate_winds(self, gbxbounds, ndims, ntime, THERMODATA):
-
- for VEL in ["WVEL", "UVEL", "VVEL"]:
- THERMODATA[VEL] = np.array([])
-
- if self.WMAX != None:
- WVEL, UVEL = self.wvel_uvel_from_flowfield(gbxbounds, ndims)
- THERMODATA["WVEL"] = np.tile(WVEL, ntime)
- THERMODATA["UVEL"] = np.tile(UVEL, ntime)
-
- if self.VVEL != None:
- shape_yface = int((ndims[2]+1)*ndims[0]*ndims[1]*ntime)
- THERMODATA["VVEL"] = np.full(shape_yface, self.VVEL)
-
- return THERMODATA
-
- def generate_qvap(self, zfulls, xfulls, PRESS, TEMP):
-
- qvaps = qparams_to_qvap(self.qvapmethod, self.qvapparams,
- self.Mr_ratio, PRESS, TEMP)
- qvap = np.where(zfulls=z1) & (zfulls=x1) & (xfulls < x2)
- qvap = np.where(moistregion, mlqvap, qvap)
-
- return qvap
-
- def generate_thermo(self, gbxbounds, ndims, ntime):
-
- zfulls, xfulls, yfulls = rgrid.fullcoords_forallgridboxes(gbxbounds,
- ndims)
- PRESS, TEMP = self.hydrostatic_adiabatic_thermo(zfulls)
-
- qvap = self.generate_qvap(zfulls, xfulls, PRESS, TEMP)
-
- shape_cen = int(ntime * np.prod(ndims))
- THERMODATA = {
- "PRESS": np.tile(PRESS, ntime),
- "TEMP": np.tile(TEMP, ntime),
- "qvap": np.tile(qvap, ntime),
- "qcond": np.full(shape_cen, self.qcond),
- }
-
- THERMODATA = self.generate_winds(gbxbounds, ndims,
- ntime, THERMODATA)
-
- return THERMODATA
class ConstHydrostaticLapseRates:
- ''' create thermodynamics that's constant in time
- and in hydrostatic equillibrium and following adiabats
- with constant lapse rates above/below zbase. '''
-
- def __init__(self, configfile, constsfile,
- PRESS0, TEMP0, qvap0, Zbase,
- TEMPlapses, qvaplapses, qcond,
- WMAX, UVEL, VVEL, Wlength):
-
- self.PRESS0 = PRESS0 # surface pressure [Pa]
- self.TEMP0 = TEMP0 # surface temperature [T]
- self.qvap0 = qvap0 # surface water vapour content [Kg/Kg]
- self.Zbase = Zbase # cloud base height [m]
- self.TEMPlapses = TEMPlapses # temp lapse rates [below, above] Zbase [K km^-1]
- self.qvaplapses = qvaplapses # qvap lapse rates [below, above] Zbase [g/Kg km^-1]
-
- self.qcond = qcond # liquid water content [Kg/Kg]
- self.WMAX = WMAX # vertical (coord3) velocity [m/s]
- self.UVEL = UVEL # horizontal eastwards (coord1) velocity [m/s]
- self.VVEL = VVEL # horizontal northwards (coord2) velocity [m/s]
- self.Wlength = Wlength # [m] use constant W (Wlength=0.0), or sinusoidal 1-D profile below cloud base
-
- inputs = thermoinputsdict(configfile, constsfile)
- self.GRAVG = inputs["G"]
- self.RGAS_DRY = inputs["RGAS_DRY"]
- self.Mr_ratio = inputs["Mr_ratio"]
-
- def temp1(self, z):
- ''' note unit conversion of input lapse rates:
- templapse rate = -dT/dz [K km^-1] --> [K m^-1] '''
-
- temp1 = self.TEMP0 - self.TEMPlapses[0]/1000 * z
- if np.any((temp1 <= 0.0)):
- raise ValueError("TEMP > 0.0K")
- return temp1
-
- def temp2(self, z):
- ''' note unit conversion of input lapse rates:
- templapse rate = -dT/dz [K km^-1] --> [K m^-1]'''
-
- T_Zbase = self.temp1(self.Zbase) # TEMP at Zbase
- temp2 = T_Zbase - self.TEMPlapses[1]/1000 * (z - self.Zbase)
- if np.any((temp2 <= 0.0)):
- raise ValueError("TEMP > 0.0K")
- return temp2
-
- def hydrostatic_pressure(self, P0, integral):
-
- exponent = - self.GRAVG / self.RGAS_DRY * integral
- return P0 * np.exp(exponent)
-
- def press1(self, z):
- ''' hydrostatic pressure for value z where z <= self.Zbase'''
- P0 = self.PRESS0
- integral = integrate.quad(lambda x: 1/self.temp1(x), 0.0, z)[0]
- return self.hydrostatic_pressure(P0, integral)
-
- def press2(self, z):
- ''' hydrostatic pressure for value z where z > self.Zbase'''
- P0 = self.press1(self.Zbase)
- integral = integrate.quad(lambda x: 1/self.temp2(x), self.Zbase, z)[0]
- return self.hydrostatic_pressure(P0, integral)
-
- def qvap1(self, z):
- ''' note unit conversion of input lapse rates:
- qvaplapse rate = -dqvap/dz [g/Kg km^-1] --> [m^-1]'''
-
- if self.qvaplapses[0] == "saturated":
- sratio = 1.001
- qvap2 = sratio2qvap(sratio, self.press2(z),
- self.temp2(z), self.Mr_ratio)
- else:
- qvap1 = self.qvap0 - self.qvaplapses[0]/1e6 * z
-
- if np.any((qvap1 <= 0.0)):
- raise ValueError("TEMP > 0.0K")
- return qvap1
-
- def qvap2(self, z):
- ''' note unit conversion of input lapse rates:
- qvaplapse rate = -dqvap/dz [g/Kg km^-1] --> [m^-1]'''
- if self.qvaplapses[1] == "saturated":
- sratio = 1.001
- qvap2 = sratio2qvap(sratio, self.press2(z),
- self.temp2(z), self.Mr_ratio)
- else:
- qvap_Zbase = self.qvap1(self.Zbase) # qvap at Zbase
- qvap2 = qvap_Zbase - self.qvaplapses[1]/1e6 * (z - self.Zbase)
-
- if np.any((qvap2 <= 0.0)):
- raise ValueError("TEMP > 0.0K")
- return qvap2
-
- def below_above_zbase(self, zfulls, func1, func2):
-
- return np.where(zfulls <= self.Zbase,
- func1(zfulls), func2(zfulls))
-
- def below_above_zbase_qvap(self, zfulls):
-
- qvap = []
- for z in zfulls:
- if z < self.Zbase:
- qvap.append(self.qvap1(z))
- else:
- qvap.append(self.qvap2(z))
-
- return np.asarray(qvap)
-
- def below_above_zbase_pressure(self, zfulls):
-
- PRESS = []
- for z in zfulls:
- if z < self.Zbase:
- PRESS.append(self.press1(z))
- else:
- PRESS.append(self.press2(z))
-
- return np.asarray(PRESS)
-
- def hydrostatic_lapserates_thermo(self, zfulls):
-
- TEMP = self.below_above_zbase(zfulls, self.temp1, self.temp2)
- PRESS = self.below_above_zbase_pressure(zfulls)
- qvap = self.below_above_zbase_qvap(zfulls)
-
- return TEMP, PRESS, qvap
-
- def wvel_profile(self, gbxbounds, ndims, ntime):
- ''' returns updraught (w always >=0.0) sinusoidal
- profile with amplitude WMAX and wavelength 2*Wlength'''
-
- zfaces = rgrid.coords_forgridboxfaces(gbxbounds, ndims, "z")[0]
- WVEL = self.WMAX * np.sin(np.pi * zfaces/(2*self.Wlength))
-
- WVEL[WVEL < 0.0] = 0.0
-
- return np.tile(WVEL, ntime)
-
- def generate_winds(self, gbxbounds, ndims, ntime, THERMODATA):
-
- THERMODATA = constant_winds(ndims, ntime, THERMODATA,
- self.WMAX, self.UVEL, self.VVEL)
- if self.Wlength > 0.0:
- THERMODATA["WVEL"] = self.wvel_profile(gbxbounds, ndims, ntime)
-
- return THERMODATA
-
- def generate_thermo(self, gbxbounds, ndims, ntime):
-
- zfulls, xfulls, yfulls = rgrid.fullcoords_forallgridboxes(gbxbounds,
- ndims)
-
- TEMP, PRESS, qvap = self.hydrostatic_lapserates_thermo(zfulls)
-
- shape_cen = int(ntime * np.prod(ndims))
- THERMODATA = {
- "PRESS": np.tile(PRESS, ntime),
- "TEMP": np.tile(TEMP, ntime),
- "qvap": np.tile(qvap, ntime),
- "qcond": np.full(shape_cen, self.qcond),
- }
-
- THERMODATA = self.generate_winds(gbxbounds, ndims, ntime, THERMODATA)
-
- return THERMODATA
+ """create thermodynamics that's constant in time
+ and in hydrostatic equillibrium and following adiabats
+ with constant lapse rates above/below zbase."""
+
+ def __init__(
+ self,
+ configfile,
+ constsfile,
+ PRESS0,
+ TEMP0,
+ qvap0,
+ Zbase,
+ TEMPlapses,
+ qvaplapses,
+ qcond,
+ WMAX,
+ UVEL,
+ VVEL,
+ Wlength,
+ ):
+ self.PRESS0 = PRESS0 # surface pressure [Pa]
+ self.TEMP0 = TEMP0 # surface temperature [T]
+ self.qvap0 = qvap0 # surface water vapour content [Kg/Kg]
+ self.Zbase = Zbase # cloud base height [m]
+ self.TEMPlapses = TEMPlapses # temp lapse rates [below, above] Zbase [K km^-1]
+ self.qvaplapses = (
+ qvaplapses # qvap lapse rates [below, above] Zbase [g/Kg km^-1]
+ )
+
+ self.qcond = qcond # liquid water content [Kg/Kg]
+ self.WMAX = WMAX # vertical (coord3) velocity [m/s]
+ self.UVEL = UVEL # horizontal eastwards (coord1) velocity [m/s]
+ self.VVEL = VVEL # horizontal northwards (coord2) velocity [m/s]
+ self.Wlength = Wlength # [m] use constant W (Wlength=0.0), or sinusoidal 1-D profile below cloud base
+
+ inputs = thermoinputsdict(configfile, constsfile)
+ self.GRAVG = inputs["G"]
+ self.RGAS_DRY = inputs["RGAS_DRY"]
+ self.Mr_ratio = inputs["Mr_ratio"]
+
+ def temp1(self, z):
+ """note unit conversion of input lapse rates:
+ templapse rate = -dT/dz [K km^-1] --> [K m^-1]"""
+
+ temp1 = self.TEMP0 - self.TEMPlapses[0] / 1000 * z
+ if np.any((temp1 <= 0.0)):
+ raise ValueError("TEMP > 0.0K")
+ return temp1
+
+ def temp2(self, z):
+ """note unit conversion of input lapse rates:
+ templapse rate = -dT/dz [K km^-1] --> [K m^-1]"""
+
+ T_Zbase = self.temp1(self.Zbase) # TEMP at Zbase
+ temp2 = T_Zbase - self.TEMPlapses[1] / 1000 * (z - self.Zbase)
+ if np.any((temp2 <= 0.0)):
+ raise ValueError("TEMP > 0.0K")
+ return temp2
+
+ def hydrostatic_pressure(self, P0, integral):
+ exponent = -self.GRAVG / self.RGAS_DRY * integral
+ return P0 * np.exp(exponent)
+
+ def press1(self, z):
+ """hydrostatic pressure for value z where z <= self.Zbase"""
+ P0 = self.PRESS0
+ integral = integrate.quad(lambda x: 1 / self.temp1(x), 0.0, z)[0]
+ return self.hydrostatic_pressure(P0, integral)
+
+ def press2(self, z):
+ """hydrostatic pressure for value z where z > self.Zbase"""
+ P0 = self.press1(self.Zbase)
+ integral = integrate.quad(lambda x: 1 / self.temp2(x), self.Zbase, z)[0]
+ return self.hydrostatic_pressure(P0, integral)
+
+ def qvap1(self, z):
+ """note unit conversion of input lapse rates:
+ qvaplapse rate = -dqvap/dz [g/Kg km^-1] --> [m^-1]"""
+
+ if self.qvaplapses[0] == "saturated":
+ sratio = 1.001
+ qvap1 = sratio2qvap(sratio, self.press2(z), self.temp2(z), self.Mr_ratio)
+ else:
+ qvap1 = self.qvap0 - self.qvaplapses[0] / 1e6 * z
+
+ if np.any((qvap1 <= 0.0)):
+ raise ValueError("TEMP > 0.0K")
+ return qvap1
+
+ def qvap2(self, z):
+ """note unit conversion of input lapse rates:
+ qvaplapse rate = -dqvap/dz [g/Kg km^-1] --> [m^-1]"""
+ if self.qvaplapses[1] == "saturated":
+ sratio = 1.001
+ qvap2 = sratio2qvap(sratio, self.press2(z), self.temp2(z), self.Mr_ratio)
+ else:
+ qvap_Zbase = self.qvap1(self.Zbase) # qvap at Zbase
+ qvap2 = qvap_Zbase - self.qvaplapses[1] / 1e6 * (z - self.Zbase)
+
+ if np.any((qvap2 <= 0.0)):
+ raise ValueError("TEMP > 0.0K")
+ return qvap2
+
+ def below_above_zbase(self, zfulls, func1, func2):
+ return np.where(zfulls <= self.Zbase, func1(zfulls), func2(zfulls))
+
+ def below_above_zbase_qvap(self, zfulls):
+ qvap = []
+ for z in zfulls:
+ if z < self.Zbase:
+ qvap.append(self.qvap1(z))
+ else:
+ qvap.append(self.qvap2(z))
+
+ return np.asarray(qvap)
+
+ def below_above_zbase_pressure(self, zfulls):
+ PRESS = []
+ for z in zfulls:
+ if z < self.Zbase:
+ PRESS.append(self.press1(z))
+ else:
+ PRESS.append(self.press2(z))
+
+ return np.asarray(PRESS)
+
+ def hydrostatic_lapserates_thermo(self, zfulls):
+ TEMP = self.below_above_zbase(zfulls, self.temp1, self.temp2)
+ PRESS = self.below_above_zbase_pressure(zfulls)
+ qvap = self.below_above_zbase_qvap(zfulls)
+
+ return TEMP, PRESS, qvap
+
+ def wvel_profile(self, gbxbounds, ndims, ntime):
+ """returns updraught (w always >=0.0) sinusoidal
+ profile with amplitude WMAX and wavelength 2*Wlength"""
+
+ zfaces = rgrid.coords_forgridboxfaces(gbxbounds, ndims, "z")[0]
+ WVEL = self.WMAX * np.sin(np.pi * zfaces / (2 * self.Wlength))
+
+ WVEL[WVEL < 0.0] = 0.0
+
+ return np.tile(WVEL, ntime)
+
+ def generate_winds(self, gbxbounds, ndims, ntime, THERMODATA):
+ THERMODATA = constant_winds(
+ ndims, ntime, THERMODATA, self.WMAX, self.UVEL, self.VVEL
+ )
+ if self.Wlength > 0.0:
+ THERMODATA["WVEL"] = self.wvel_profile(gbxbounds, ndims, ntime)
+
+ return THERMODATA
+
+ def generate_thermo(self, gbxbounds, ndims, ntime):
+ zfulls, xfulls, yfulls = rgrid.fullcoords_forallgridboxes(gbxbounds, ndims)
+
+ TEMP, PRESS, qvap = self.hydrostatic_lapserates_thermo(zfulls)
+
+ shape_cen = int(ntime * np.prod(ndims))
+ THERMODATA = {
+ "PRESS": np.tile(PRESS, ntime),
+ "TEMP": np.tile(TEMP, ntime),
+ "qvap": np.tile(qvap, ntime),
+ "qcond": np.full(shape_cen, self.qcond),
+ }
+
+ THERMODATA = self.generate_winds(gbxbounds, ndims, ntime, THERMODATA)
+
+ return THERMODATA
diff --git a/pySD/writebinary.py b/pySD/writebinary.py
index 9ffd4b4ac..e572c4f9a 100644
--- a/pySD/writebinary.py
+++ b/pySD/writebinary.py
@@ -1,21 +1,43 @@
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
+----- CLEO -----
+File: writebinary.py
+Project: pySD
+Created Date: Tuesday 7th May 2024
+Author: Clara Bayley (CB)
+Additional Contributors:
+-----
+Last Modified: Tuesday 7th May 2024
+Modified By: CB
+-----
+License: BSD 3-Clause "New" or "Revised" License
+https://opensource.org/licenses/BSD-3-Clause
+-----
+File Description:
+"""
+
+
import numpy as np
import struct
-def writebinary(filename, data, ndata, datatypes,
- units, scale_factors, metastr):
- ''' 'data' is 1D array containing continuous list of variables.
+
+def writebinary(filename, data, ndata, datatypes, units, scale_factors, metastr):
+ """'data' is 1D array containing continuous list of variables.
The number of data points of each variable is give by it's index in
'ndata', likewise it's datatype, unit and scale_factor are in
those lists. 'data' is written to binary file with this metadata
beforehand and a global metadata string explaingnig how to interpret
- the file '''
+ the file"""
check_validinputs(data, ndata, datatypes, units, scale_factors)
nvars = np.uintc(len(ndata))
- metamaker = CreateMetadataForBinaryArray(nvars, ndata, datatypes, units,
- scale_factors, metastr)
+ metamaker = CreateMetadataForBinaryArray(
+ nvars, ndata, datatypes, units, scale_factors, metastr
+ )
metadata, metaformat = metamaker.get_metadata()
dataformat = get_dataformat(nvars, ndata, datatypes)
@@ -23,169 +45,178 @@ def writebinary(filename, data, ndata, datatypes,
array2write = metadata + data
format = metaformat + dataformat
- print("Writing gridbox boundaries binary file to:\n "+filename)
+ print("Writing gridbox boundaries binary file to:\n " + filename)
s = struct.pack(format, *array2write)
- f = open(filename, 'wb')
+ f = open(filename, "wb")
f.write(s)
f.close()
+
def check_validinputs(data, ndata, datatypes, units, scale_factors):
- '''' check that each variable's scale_factor is a double, unit is
+ """' check that each variable's scale_factor is a double, unit is
a binary encoded single character and that the dataype given matches
- the type of the data '''
+ the type of the data"""
if any([type(s) != np.double for s in scale_factors]):
raise ValueError("type of scale_factors must be C type double")
for u in units:
- if type(u) != type(b'c') or np.size(u) != 1:
+ if not isinstance(u, bytes) or np.size(u) != 1:
raise ValueError("type of units is not binary C type char")
i = 0
for j, n in enumerate(ndata):
- if any([type(d) != datatypes[j] for d in data[i:i+n]]):
- err = "stated datatype "+str(datatypes[j])+\
- " doesn't match type(data) "+str(type(data[n]))
- raise ValueError(err)
- i += n
+ if any([type(d) != datatypes[j] for d in data[i : i + n]]):
+ err = (
+ "stated datatype "
+ + str(datatypes[j])
+ + " doesn't match type(data) "
+ + str(type(data[n]))
+ )
+ raise ValueError(err)
+ i += n
-def get_dataformat(nvars, ndata, datatypes):
+def get_dataformat(nvars, ndata, datatypes):
dtc = DataTypeCodes()
- dataformat = ''
+ dataformat = ""
for n in range(nvars):
nvar = np.uintc(ndata[n])
- dataformat += dtc.d2f[datatypes[n]]*nvar
+ dataformat += dtc.d2f[datatypes[n]] * nvar
return dataformat
-class DataTypeCodes:
+class DataTypeCodes:
def __init__(self):
-
self.d2f = {
- # dict for converting dtype to struct formatting code
+ # dict for converting dtype to struct formatting code
np.uintc: "I",
np.double: "d",
np.uint: "Q",
- type('c'): 'c'
+ type("c"): "c",
}
self.d2binaryf = {
- # dict for converting dtype to binary encoded struct formatting
+ # dict for converting dtype to binary encoded struct formatting
np.uintc: b"I",
np.double: b"d",
np.uint: b"Q",
- type('c'): b'c'
+ type("c"): b"c",
}
def dtype2bytesize(self, datatype):
- '''returns C type unsigned int for the size of the
- datatype given int's format when stored in binary using
- python's struct module '''
+ """returns C type unsigned int for the size of the
+ datatype given int's format when stored in binary using
+ python's struct module"""
- dcode = self.d2f[datatype]
+ dcode = self.d2f[datatype]
- return np.uintc(struct.calcsize(dcode))
+ return np.uintc(struct.calcsize(dcode))
def format_size(self, format):
- '''returns size in bytes of data stored in a given format
- using python's struct module '''
+ """returns size in bytes of data stored in a given format
+ using python's struct module"""
- bytesize = 0
- for c in format:
- bytesize += struct.calcsize(c)
+ bytesize = 0
+ for c in format:
+ bytesize += struct.calcsize(c)
- return bytesize
+ return bytesize
-class MetadataPerVariable(DataTypeCodes):
+class MetadataPerVariable(DataTypeCodes):
def __init__(self):
+ super(MetadataPerVariable, self).__init__()
- super(MetadataPerVariable, self).__init__()
-
- self.mpv_format = 'IIIccd'
- self.mpv_bytesize = self.format_size(self.mpv_format)
+ self.mpv_format = "IIIccd"
+ self.mpv_bytesize = self.format_size(self.mpv_format)
def varmetadata(self, datap0, ndata, datatype, unit, scale_factor):
+ bytespos0 = np.uintc(datap0) # position of first datapoint of var
+ varsz = self.dtype2bytesize(datatype) # size in bytes of 1 datapoint of var
+ nvar = np.uintc(ndata) # number of datapoints of var
+ vartype = self.d2binaryf[datatype] # binary char symbolising datatype of var
+ varunts = unit # binary char symbolising units of var when * sclae_factor
+ varsf = np.double(scale_factor) # double for scale_factor constant
- bytespos0 = np.uintc(datap0) # position of first datapoint of var
- varsz = self.dtype2bytesize(datatype) # size in bytes of 1 datapoint of var
- nvar = np.uintc(ndata) # number of datapoints of var
- vartype = self.d2binaryf[datatype] # binary char symbolising datatype of var
- varunts = unit # binary char symbolising units of var when * sclae_factor
- varsf = np.double(scale_factor) # double for scale_factor constant
+ metapervar = [bytespos0, varsz, nvar, vartype, varunts, varsf]
- metapervar = [bytespos0, varsz, nvar, vartype, varunts, varsf]
+ varbytes = varsz * nvar
- varbytes = varsz * nvar
+ return metapervar, varbytes
- return metapervar, varbytes
class CreateMetadataForBinaryArray(MetadataPerVariable):
-
- def __init__(self, nvars, ndata, datatypes, units, scale_factors, metastr):
-
- super(CreateMetadataForBinaryArray, self).__init__()
-
- self.nvars = nvars
- self.ndata = ndata
- self.datatypes = datatypes
- self.units = units
- self.scale_factors = scale_factors
-
- self.gblmetastr = '4 unsigned ints before this metadata string are'+\
- ' [1. position of first byte of data (after all the metadata),'+\
- ' 2. no. bytes of (this) global metadata string, 3. no. bytes'+\
- ' per variable specific metadata, 4. no. of variables in data].'+\
- ' After this global metadata string comes variable specific'+\
- ' metadata. For each variable, this is 3 unsigned ints, 2 chars'+\
- ' and then a double; it states: [1. position of first databyte,'+\
- ' 2. size (in bytes) of one datapoint, 3. no. of datapoints,'+\
- ' 4. char to indicate python struct type, 5. char to indicate'+\
- ' the units once multiplied by, 6. the scale factor]. ' + metastr
-
- def get_metadata(self):
-
- gblmeta, gblmeta_format, gblmeta_bytes = self.metastr_to_chars(self.gblmetastr)
-
- datap0 = self.format_size("IIII") + gblmeta_bytes + self.mpv_bytesize * self.nvars
- metapvars, metapvars_format, metapv_bytes = self.variables_metadata(datap0)
-
- metaformat = "= zlim
+nsupers = crdgens.nsupers_at_domain_top(
+ gridfile, constsfile, npergbx, zlim
+) # supers where z >= zlim
# nsupers = 100
### ------------------------------------------- ###
@@ -61,16 +67,16 @@
# monor = 0.05e-6 # all SDs have this same radius [m]
# radiigen = rgens.MonoAttrGen(monor) # all SDs have the same radius [m]
-rspan = [1e-8, 1e-4] # min and max range of radii to sample [m]
-radiigen = rgens.SampleLog10RadiiGen(rspan) # radii are sampled from rspan [m]
+rspan = [1e-8, 1e-4] # min and max range of radii to sample [m]
+radiigen = rgens.SampleLog10RadiiGen(rspan) # radii are sampled from rspan [m]
### ---------------------------------------------- ###
### --- Choice of Superdroplet Dry Radii Generator --- ###
-monodryr = 1e-9 # all SDs have this same dryradius [m]
-dryradiigen = rgens.MonoAttrGen(monodryr) # all SDs have the same dryradius [m]
+# monodryr = 1e-9 # all SDs have this same dryradius [m]
+# dryradiigen = rgens.MonoAttrGen(monodryr) # all SDs have the same dryradius [m]
-# dryr_sf = 1.0 # scale factor for dry radii [m]
-# dryradiigen = dryrgens.ScaledRadiiGen(dryr_sf) # dryradii are 1/sf of radii [m]
+dryr_sf = 1.0 # scale factor for dry radii [m]
+dryradiigen = dryrgens.ScaledRadiiGen(dryr_sf) # dryradii are 1/sf of radii [m]
### ---------------------------------------------- ###
@@ -119,7 +125,7 @@
### --- Choice of Superdroplet Coord3 Generator --- ###
# monocoord3 = 1000 # all SDs have this same coord3 [m]
# coord3gen = crdgens.MonoCoordGen(monocoord3)
-coord3gen = crdgens.SampleCoordGen(True) # sample coord3 range randomly or not
+coord3gen = crdgens.SampleCoordGen(True) # sample coord3 range randomly or not
# coord3gen = None # do not generate superdroplet coord3s
### ----------------------------------------------- ###
@@ -127,14 +133,14 @@
# monocoord1 = 200 # all SDs have this same coord1 [m]
# coord1gen = crdgens.MonoCoordGen(monocoord1)
# coord1gen = crdgens.SampleCoordGen(True) # sample coord1 range randomly or not
-coord1gen = None # do not generate superdroplet coord1s
+coord1gen = None # do not generate superdroplet coord1s
### ----------------------------------------------- ###
### --- Choice of Superdroplet Coord2 Generator --- ###
# monocoord2 = 1000 # all SDs have this same coord2 [m]
# coord2gen = crdgens.MonoCoordGen(monocoord2)
# coord2gen = crdgens.SampleCoordGen(True) # sample coord1 range randomly or not
-coord2gen = None # do not generate superdroplet coord2s
+coord2gen = None # do not generate superdroplet coord2s
### ----------------------------------------------- ###
### ---------------------------------------------------------------- ###
@@ -143,23 +149,30 @@
### -------------------- BINARY FILE GENERATION--------------------- ###
### ensure build, share and bin directories exist
if path2CLEO == path2build:
- raise ValueError("build directory cannot be CLEO")
+ raise ValueError("build directory cannot be CLEO")
else:
- Path(path2build).mkdir(exist_ok=True)
- Path(binariespath).mkdir(exist_ok=True)
+ Path(path2build).mkdir(exist_ok=True)
+ Path(binariespath).mkdir(exist_ok=True)
### write initial superdrops binary
-attrsgen = attrsgen.AttrsGenerator(radiigen, dryradiigen, xiprobdist,
- coord3gen, coord1gen, coord2gen)
-csupers.write_initsuperdrops_binary(initsupersfile, attrsgen,
- configfile, constsfile,
- gridfile, nsupers, numconc)
+attrsgen = attrsgen.AttrsGenerator(
+ radiigen, dryradiigen, xiprobdist, coord3gen, coord1gen, coord2gen
+)
+csupers.write_initsuperdrops_binary(
+ initsupersfile, attrsgen, configfile, constsfile, gridfile, nsupers, numconc
+)
### plot initial superdrops binary
if isfigures[0]:
if isfigures[1]:
Path(savefigpath).mkdir(exist_ok=True)
- rsupers.plot_initGBxs_distribs(configfile, constsfile, initsupersfile,
- gridfile, savefigpath, isfigures[1],
- gbxs2plt)
+ rsupers.plot_initGBxs_distribs(
+ configfile,
+ constsfile,
+ initsupersfile,
+ gridfile,
+ savefigpath,
+ isfigures[1],
+ gbxs2plt,
+ )
### ---------------------------------------------------------------- ###
diff --git a/scripts/create_thermobinaries_script.py b/scripts/create_thermobinaries_script.py
index 1a9d2837e..7f47e6ba2 100644
--- a/scripts/create_thermobinaries_script.py
+++ b/scripts/create_thermobinaries_script.py
@@ -1,4 +1,7 @@
-'''
+"""
+Copyright (c) 2024 MPI-M, Clara Bayley
+
+
----- CLEO -----
File: create_thermobinaries_script.py
Project: scripts
@@ -6,26 +9,22 @@
Author: Clara Bayley (CB)
Additional Contributors:
-----
-Last Modified: Wednesday 17th April 2024
+Last Modified: Tuesday 7th May 2024
Modified By: CB
-----
License: BSD 3-Clause "New" or "Revised" License
https://opensource.org/licenses/BSD-3-Clause
-----
-Copyright (c) 2023 MPI-M, Clara Bayley
------
File Description:
uses pySD module to create binary files
for the dynamics to read into CLEO when
using a from file data for coupled dynamics
-'''
-
+"""
import sys
-import numpy as np
from pathlib import Path
-sys.path.append(sys.argv[1]) # path to pySD (same as to CLEO)
+sys.path.append(sys.argv[1]) # path to pySD (same as to CLEO)
from pySD.thermobinary_src import thermogen
from pySD.thermobinary_src import create_thermodynamics as cthermo
from pySD.thermobinary_src import read_thermodynamics as rthermo
@@ -41,12 +40,14 @@
isfigures = [True, True]
### essential paths and filenames
-constsfile = path2CLEO+"/libs/cleoconstants.hpp"
-binariespath = path2build+"/share/"
-savefigpath = path2build+"/bin/"
+constsfile = path2CLEO + "/libs/cleoconstants.hpp"
+binariespath = path2build + "/share/"
+savefigpath = path2build + "/bin/"
-gridfile = binariespath+"/dimlessGBxboundaries.dat" # note this should match config.yaml
-thermofile = binariespath+"/dimlessthermo.dat"
+gridfile = (
+ binariespath + "/dimlessGBxboundaries.dat"
+) # note this should match config.yaml
+thermofile = binariespath + "/dimlessthermo.dat"
### --- Choose Initial Thermodynamic Conditions for Gridboxes --- ###
@@ -63,22 +64,33 @@
# relh=relh_init, constsfile=constsfile)
### --- 1-D T and qv set by Lapse Rates --- ###
-PRESS0 = 101315 # [Pa]
-TEMP0 = 297.9 # [K]
-qvap0 = 0.016 # [Kg/Kg]
-Zbase = 800 # [m]
-TEMPlapses = [9.8, 6.5] # -dT/dz [K/km]
-qvaplapses = [2.97, "saturated"] # -dvap/dz [g/Kg km^-1]
-qcond = 0.0 # [Kg/Kg]
-WMAX = 0.0 # [m/s]
-Wlength = 1000 # [m] use constant W (Wlength=0.0), or sinusoidal 1-D profile below cloud base
-
-thermodyngen = thermogen.ConstHydrostaticLapseRates(configfile, constsfile,
- PRESS0, TEMP0, qvap0,
- Zbase, TEMPlapses,
- qvaplapses, qcond,
- WMAX, None, None,
- Wlength)
+PRESS0 = 101315 # [Pa]
+TEMP0 = 297.9 # [K]
+qvap0 = 0.016 # [Kg/Kg]
+Zbase = 800 # [m]
+TEMPlapses = [9.8, 6.5] # -dT/dz [K/km]
+qvaplapses = [2.97, "saturated"] # -dvap/dz [g/Kg km^-1]
+qcond = 0.0 # [Kg/Kg]
+WMAX = 0.0 # [m/s]
+Wlength = (
+ 1000 # [m] use constant W (Wlength=0.0), or sinusoidal 1-D profile below cloud base
+)
+
+thermodyngen = thermogen.ConstHydrostaticLapseRates(
+ configfile,
+ constsfile,
+ PRESS0,
+ TEMP0,
+ qvap0,
+ Zbase,
+ TEMPlapses,
+ qvaplapses,
+ qcond,
+ WMAX,
+ None,
+ None,
+ Wlength,
+)
# ### --- 2D Flow Field with Hydrostatic --- ###
# ### --- or Simple z Profile --- ###
@@ -112,13 +124,14 @@
### ---------------------------------------------------------------- ###
### -------------------- BINARY FILE GENERATION--------------------- ###
-cthermo.write_thermodynamics_binary(thermofile, thermodyngen, configfile,
- constsfile, gridfile)
+cthermo.write_thermodynamics_binary(
+ thermofile, thermodyngen, configfile, constsfile, gridfile
+)
if isfigures[0]:
if isfigures[1]:
Path(savefigpath).mkdir(exist_ok=True)
- rthermo.plot_thermodynamics(constsfile, configfile, gridfile,
- thermofile, savefigpath,
- isfigures[1])
+ rthermo.plot_thermodynamics(
+ constsfile, configfile, gridfile, thermofile, savefigpath, isfigures[1]
+ )
### ---------------------------------------------------------------- ###
diff --git a/setup.py b/setup.py
index 40117eb78..44a59631f 100644
--- a/setup.py
+++ b/setup.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: setup.py
Project: CLEO
@@ -13,18 +13,18 @@
https://opensource.org/licenses/BSD-3-Clause
-----
Copyright (c) 2023 MPI-M, Clara Bayley
-'''
+"""
from setuptools import setup, find_packages
setup(
- name='CLEO',
- version='0.1.0',
+ name="CLEO",
+ version="0.1.0",
packages=find_packages(),
install_requires=[
- 'pytest',
- 'sphinx',
- 'matplotlib',
+ "pytest",
+ "sphinx",
+ "matplotlib",
],
)
diff --git a/tests/test_math.py b/tests/test_math.py
index 35b5296fc..19314a05d 100644
--- a/tests/test_math.py
+++ b/tests/test_math.py
@@ -1,4 +1,4 @@
-'''
+"""
----- CLEO -----
File: test_math.py
Project: tests
@@ -13,10 +13,9 @@
https://opensource.org/licenses/BSD-3-Clause
-----
Copyright (c) 2023 MPI-M, Clara Bayley
-'''
+"""
def test_math():
-
- assert 1+1 == 2
- assert 1-1 == 0
+ assert 1 + 1 == 2
+ assert 1 - 1 == 0