diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4bfecfedbc3cd35b637371f7b267459f76908e86..f08ce4098e1464dc53396d39d3127e6c406136c1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,6 +29,10 @@ before_script: - pip install numpy - pip install matplotlib +stages: + - test + - diags + test-advection: stage: test script: @@ -141,5 +145,107 @@ test-PrescEmi: - artifacts/PrescEmi.logs - artifacts/PrescEmi_*.png - +test-2dtest-ppmw: + stage: test + script: + - python 2dtestcases.py --ts ppmw --windfield leveque --nx 5 --ny 5 --period 86400. --initshape smooth --chemistry ozoneco --split strang --dsize 1.e+5 --makedumps --label leveque-ozoneco-smooth-ppmw > artifacts/ppmw.log + artifacts: + paths: + - artifacts/CO_leveque-ozoneco-smooth-ppmw-086400.png + - artifacts/NO2_leveque-ozoneco-smooth-ppmw-086400.png + - artifacts/NO_leveque-ozoneco-smooth-ppmw-086400.png + - artifacts/O3_leveque-ozoneco-smooth-ppmw-086400.png + - artifacts/TRC_leveque-ozoneco-smooth-ppmw-086400.png + - artifacts/leveque-ozoneco-smooth-ppmw-*.pickle + - artifacts/ppmw.log + +test-2dtest-ppmSL: + stage: test + script: + - python 2dtestcases.py --ts ppmSL --windfield leveque --nx 5 --ny 5 --period 86400. --initshape smooth --chemistry ozoneco --split strang --dsize 1.e+5 --makedumps --label leveque-ozoneco-smooth-ppmSL > artifacts/ppmSL.log + + artifacts: + paths: + - artifacts/CO_leveque-ozoneco-smooth-ppmSL-086400.png + - artifacts/NO2_leveque-ozoneco-smooth-ppmSL-086400.png + - artifacts/NO_leveque-ozoneco-smooth-ppmSL-086400.png + - artifacts/O3_leveque-ozoneco-smooth-ppmSL-086400.png + - artifacts/TRC_leveque-ozoneco-smooth-ppmSL-086400.png + - artifacts/leveque-ozoneco-smooth-ppmSL-*.pickle + - artifacts/ppmSL.log + +test-2dtest-upwind: + stage: test + script: + - python 2dtestcases.py --ts upwind --windfield leveque --nx 5 --ny 5 --period 86400. --initshape smooth --chemistry ozoneco --split strang --dsize 1.e+5 --makedumps --label leveque-ozoneco-smooth-upwind > artifacts/upwind.log + artifacts: + paths: + - artifacts/CO_leveque-ozoneco-smooth-upwind-086400.png + - artifacts/NO2_leveque-ozoneco-smooth-upwind-086400.png + - artifacts/NO_leveque-ozoneco-smooth-upwind-086400.png + - artifacts/O3_leveque-ozoneco-smooth-upwind-086400.png + - artifacts/TRC_leveque-ozoneco-smooth-upwind-086400.png + - artifacts/leveque-ozoneco-smooth-upwind-*.pickle + - artifacts/upwind.log + +test-2dtest-vanleerSL: + stage: test + script: + - python 2dtestcases.py --ts vanleerSL --windfield leveque --nx 5 --ny 5 --period 86400. --initshape smooth --chemistry ozoneco --split strang --dsize 1.e+5 --makedumps --label leveque-ozoneco-smooth-vanleerSL > artifacts/vanleerSL.log + + artifacts: + paths: + - artifacts/CO_leveque-ozoneco-smooth-vanleerSL-086400.png + - artifacts/NO2_leveque-ozoneco-smooth-vanleerSL-086400.png + - artifacts/NO_leveque-ozoneco-smooth-vanleerSL-086400.png + - artifacts/O3_leveque-ozoneco-smooth-vanleerSL-086400.png + - artifacts/TRC_leveque-ozoneco-smooth-vanleerSL-086400.png + - artifacts/leveque-ozoneco-smooth-vanleerSL-*.pickle + - artifacts/vanleerSL.log + +test-2dtest-walcek: + stage: test + script: + - python 2dtestcases.py --ts walcek --windfield leveque --nx 5 --ny 5 --period 86400. --initshape smooth --chemistry ozoneco --split strang --dsize 1.e+5 --makedumps --label leveque-ozoneco-smooth-walcek > artifacts/walcek.log + + artifacts: + paths: + - artifacts/CO_leveque-ozoneco-smooth-walcek-086400.png + - artifacts/NO2_leveque-ozoneco-smooth-walcek-086400.png + - artifacts/NO_leveque-ozoneco-smooth-walcek-086400.png + - artifacts/O3_leveque-ozoneco-smooth-walcek-086400.png + - artifacts/TRC_leveque-ozoneco-smooth-walcek-086400.png + - artifacts/leveque-ozoneco-smooth-walcek-*.pickle + - artifacts/walcek.log + +test-2dtest-null: + stage: test + script: + - python 2dtestcases.py --ts null --windfield leveque --nx 5 --ny 5 --period 86400. --initshape smooth --chemistry ozoneco --split strang --dsize 1.e+5 --makedumps --label leveque-ozoneco-smooth-null > artifacts/null.log + artifacts: + paths: + - artifacts/CO_leveque-ozoneco-smooth-null-086400.png + - artifacts/NO2_leveque-ozoneco-smooth-null-086400.png + - artifacts/NO_leveque-ozoneco-smooth-null-086400.png + - artifacts/O3_leveque-ozoneco-smooth-null-086400.png + - artifacts/TRC_leveque-ozoneco-smooth-null-086400.png + - artifacts/leveque-ozoneco-smooth-null-*.pickle + - artifacts/null.log + +diags-2d: + stage: diags + needs: ["test-2dtest-null", "test-2dtest-walcek", "test-2dtest-vanleerSL", "test-2dtest-upwind", "test-2dtest-ppmSL", "test-2dtest-ppmw"] + script: ./2d-diags.py --CIcheck > artifacts/2d-diags.log + artifacts: + paths: + - artifacts/norm2.tex + - artifacts/norm1.tex + - artifacts/e1.tex + - artifacts/2d-diags.log + - artifacts/pdfdist*.png + - artifacts/finalpdf_fancy_*.png + - artifacts/cminmax.png + - artifacts/testnminmax.png + - artifacts/map-*-086400.png + \ No newline at end of file diff --git a/2d-diags.py b/2d-diags.py new file mode 100755 index 0000000000000000000000000000000000000000..ed538ce931674c822ecc1b9e114e2c02536f31b1 --- /dev/null +++ b/2d-diags.py @@ -0,0 +1,1250 @@ +#!/bin/env python + +# Standard modules + +import numpy as np +import matplotlib.pyplot as plt +import matplotlib as mpl +import sys +import os +import pickle +import argparse + +# Toyctm +from toyctm.toyctm import ctm # Main module. Defines the ctm class + +# Toyctm addons +from toyctm_addons.initialization import initialization +from toyctm_addons.chemistry import mechanism, reaction +from toyctm_addons.twodgrid import Latlongrid +from toyctm_addons.threedimgrid import Threedimgrid +from toyctm_addons.ctmtransport import TransportScheme +from toyctm_addons.threedmassfluxes import MassFluxGenerator, MassFluxParams +from toyctm_addons.defaults import defaults +import toyctm.constants as constants + +plt.ioff() + +# Set the default text font size +plt.rc("font", size=14) + +# Set the axes title font size +plt.rc("axes", titlesize=14) + +# Set the axes labels font size +plt.rc("axes", labelsize=14) + +# Set the font size for x tick labels +plt.rc("xtick", labelsize=10) + +# Set the font size for y tick labels +plt.rc("ytick", labelsize=10) + +# Set the legend font size +plt.rc("legend", fontsize=8) + +# Set the font size of the figure title +plt.rc("figure", titlesize=16) + +############################################################# +# Initialize some stuff to avoid pickle load errors +def makeplotchem( + self, sp, simlabel="toyctm", unit="ppb", spname="FAMILY" +): # unit should be ppb or None + print("making plot for", sp) + if type(sp) is tuple: + sptup = sp + else: + sptup = (sp,) + spname = sp + + if unit == "ppb": + factor = 1.0 + else: + factor = 1.0e-9 + + tlabel = "{0}-{1:06d}".format(simlabel, int(self.t + 0.5)) + field = None + for spec in sptup: + print("Extracting field", spec) + if field is None: + field = self.activeConc_ppb(spec) * factor + else: + field = field + self.activeConc_ppb(spec) * factor + + ax = plt.figure(figsize=(4.5, 4.0)) + plt.pcolormesh( + self.tdgrid.hgrid.activeloncorners / 1000.0, + self.tdgrid.hgrid.activelatcorners / 1000.0, + field[:, :, 0], + cmap=plt.cm.Reds, + ) + plt.xlabel("x (km)") + plt.ylabel("y (km)") + cbar = plt.colorbar() + if unit == "ppb": + cbar.set_label(r"$\alpha{}_{" + spname + r"} \mathrm{(ppb)}$") + else: + cbar.set_label(r"$\alpha{}_{" + spname + r"}$") + plt.savefig( + "{path}map-{spname}-{tlabel}.png".format( + path=diagspath, spname=spname, tlabel=tlabel + ), + dpi=300, + bbox_inches="tight", + ) + plt.savefig( + "{path}map-{spname}-{tlabel}.pdf".format( + path=diagspath, spname=spname, tlabel=tlabel + ), + bbox_inches="tight", + ) + plt.close() + + +ctm.makeplotchem = makeplotchem + +############################################################# +# Initialize some stuff to avoid pickle load errors + + +def psi_translation(inst, x, y, t, dt): + return inst.params.V0 * x - inst.params.U0 * y + + +def psi_solid(inst, x, y, t, dt): + rsq = (x - inst.params.xo) ** 2.0 + (y - inst.params.yo) ** 2.0 + return np.pi * rsq / inst.params.period + + +def psi_leveque(inst, x, y, t, dt): + U0 = inst.params.U0 + L0 = inst.params.L0 + xo = inst.params.xo + yo = inst.params.yo + T = inst.params.period + prof = ( + U0 + * L0 + * np.cos(0.5 * (x - inst.params.xo) * np.pi / L0) ** 2.0 + * np.cos(0.5 * (y - inst.params.yo) * np.pi / L0) ** 2.0 + ) + if not (dt == 0.0): + ta = T / (np.pi * dt) * (np.sin(np.pi * (t + dt) / T) - np.sin(np.pi * t / T)) + else: + ta = np.cos(np.pi * t / T) + return prof * ta + + +def frompsi(inst, t, dt, x1, y1, zbot1, ztop1, x2, y2, zbot2, ztop2): + zbot = 0.5 * (zbot1 + zbot2) # Per surface du trapeze this should work + ztop = 0.5 * (ztop1 + ztop2) + return ( + (ztop - zbot) + * inst.params.density + * (inst.psi(inst, x1, y1, t, dt) - inst.psi(inst, x2, y2, t, dt)) + ) + + +def plotpsi(self, xmin, xmax, ymin, ymax, t, fname=None): + nxplot = 100 + nyplot = ( + 110 # nxplot slighty differs from nyplot to avoid any permutation in dimensions + ) + xvec = np.linspace(xmin, xmax, nxplot + 1) + yvec = np.linspace(ymin, ymax, nyplot + 1) + psiar = np.zeros((nxplot + 1, nyplot + 1)) + xar = np.zeros((nxplot + 1, nyplot + 1)) + yar = np.zeros((nxplot + 1, nyplot + 1)) + if fname is None: + fname = "psiplots/psi_" + self.params.name + ".png" + + for i in range(nxplot + 1): + for j in range(nyplot + 1): + psiar[i, j] = self.psi(self, xvec[i], yvec[j], t, 0.0) + xar[i, j] = xvec[i] + yar[i, j] = yvec[j] + + gradx, grady = np.gradient(psiar, xvec, yvec) + uspeed = -grady + vspeed = gradx + module = np.sqrt(uspeed * uspeed + vspeed * vspeed) + maxmod = np.max(module) + minmod = np.min(module) + meanspeed = (maxmod + minmod) / 2.0 + ampli = np.abs((maxmod - minmod) / maxmod) + plt.figure(figsize=(4.5, 4.0)) + print(xvec.shape) + print(yvec.shape) + print(psiar.shape) + plt.contour(xar / 1000.0, yar / 1000.0, psiar, colors="black") + if ampli > 0.001: + plt.contourf(xar / 1000.0, yar / 1000.0, module, cmap=plt.cm.viridis) + else: + plt.contourf( + xar / 1000.0, + yar / 1000.0, + module, + [meanspeed * 0.999, meanspeed * 1.001], + cmap=plt.cm.viridis, + ) # We do not want to plot numerical noise + + cbar = plt.colorbar() + cbar.set_label("U (m/s)") + plt.xlabel("x (km)") + plt.ylabel("y (km)") + plt.quiver( + xar[::10, ::10] / 1000.0, + yar[::10, ::10] / 1000.0, + uspeed[::10, ::10], + vspeed[::10, ::10], + ) + plt.savefig(fname + ".png", dpi=300, bbox_inches="tight") + plt.savefig(fname + ".pdf", bbox_inches="tight") + plt.close() + + +# Enhance MassFluxGenerator class +MassFluxGenerator.plotpsi = plotpsi + +TransportScheme.__computeFluxSL = None +TransportScheme.__computeFluxMOL = None + + +# END OF PICKLE WORKAROUNDS +# + +############################################################# +# Scatter class +# + + +class Scatter: + def __init__(self, x, y, t=0.0, unitx="ppb", unity="ppb", spx=None, spy=None): + # x, y provided in ppb + + # Sort by increasing x to permit connected plots + xvec = np.asarray(x) + yvec = np.asarray(y) + p = xvec.argsort() + + self.x = xvec[p] + self.y = yvec[p] + self.t = t + self.spx = spx + self.spy = spy + self.unitx = unitx + self.unity = unity + if unitx == None: + self.x = self.x / 1.0e9 + if unity == None: + self.y = self.y / 1.0e9 + + def plot(self, simlabel="toyctm", color="k", refscat=None, lw=1.0): + fig, ax1 = plt.subplots(1, 1, figsize=(4.5, 4.0)) + tlabel = "{0}-{1:06d}".format(simlabel, int(t + 0.5)) + ax1.scatter(self.x, self.y, color=color, s=1) + ax1.set_title(nicelabel[simdict[lab].tsname]) + if refscat is not None: + ax1.plot(refscat.x, refscat.y, color="k", lw=lw) + if self.unitx is None: + ax1.set_xlim(0.0, 1.0) + if self.unity is None: + ax1.set_ylim(0.0, 1.0) + plt.savefig( + "{0}{1}-{2}-{3}-{4}.png".format(diagspath, "scat", sp1, sp2, tlabel), + dpi=300, + bbox_inches="tight", + ) + plt.savefig( + "{0}{1}-{2}-{3}-{4}.pdf".format(diagspath, "scat", sp1, sp2, tlabel), + bbox_inches="tight", + ) + plt.close() + + +############################################################# +# Simulation class +# +class simulation: + def __init__(self, myctm): + self.label = myctm.label + self.tsname = myctm.ts.name + self.times = [] + self.o3max = [] + self.o3min = [] + self.cmin = [] + self.cmax = [] + self.n2min = [] + self.n2max = [] + self.testnmin = [] + self.testnmax = [] + self.omass = [] + self.hmass = [] + self.nmass = [] + self.n2mass = [] + self.mmass = [] + self.o3mass = [] + self.nomass = [] + self.no2mass = [] + self.trcmass = [] + self.cmass = [] + self.trcmax = [] + self.trchalf = [] + self.niters = [] + self.pdfdist = None + self.scatters = {} + + def add_diags(self, myctm, myctmref=None): + self.times.append( + np.fix(myctm.t + 0.5) + ) # round up to the nearest integer second to avoid truncature errors + + # Mass conservation + self.omass.append( + 2.0 * myctm.moleculesNumber("NO2") + + myctm.moleculesNumber("NO") + + myctm.moleculesNumber("O") + + 2.0 * myctm.moleculesNumber("O2") + + 3.0 * myctm.moleculesNumber("O3") + + myctm.moleculesNumber("O1D") + + myctm.moleculesNumber("H2O") + + 2.0 * myctm.moleculesNumber("CO2") + + myctm.moleculesNumber("CO") + + myctm.moleculesNumber("OH") + + 2.0 * myctm.moleculesNumber("HO2") + + 2.0 * myctm.moleculesNumber("H2O2") + + 3.0 * myctm.moleculesNumber("HNO3") + ) + self.hmass.append( + 2.0 * myctm.moleculesNumber("H2O") + + myctm.moleculesNumber("OH") + + myctm.moleculesNumber("HO2") + + 2.0 * myctm.moleculesNumber("H2O2") + + myctm.moleculesNumber("HNO3") + ) + self.nmass.append( + myctm.moleculesNumber("NO") + + myctm.moleculesNumber("NO2") + + myctm.moleculesNumber("HNO3") + ) + self.n2mass.append(2.0 * myctm.moleculesNumber("N2")) + self.mmass.append(myctm.moleculesNumber("M")) + self.o3mass.append(myctm.moleculesNumber("O3")) + self.nomass.append(myctm.moleculesNumber("NO")) + self.no2mass.append(myctm.moleculesNumber("NO2")) + self.cmass.append(myctm.moleculesNumber("CO") + myctm.moleculesNumber("CO2")) + self.trcmass.append(myctm.moleculesNumber("TRC")) + + # cmin and cmax conservation + carray = myctm.activeConc_ppb("CO") + myctm.activeConc_ppb("CO2") + cmin, cmax = np.amin(carray), np.amax(carray) + self.cmin.append(cmin) + self.cmax.append(cmax) + + # testnmin and testnmax conservation + testnarray = ( + myctm.activeConc_ppb("NO") + + myctm.activeConc_ppb("NO2") + + myctm.activeConc_ppb("HNO3") + + myctm.activeConc_ppb("TRCb") + ) + testnmin, testnmax = np.amin(testnarray), np.amax(testnarray) + self.testnmin.append(testnmin) + self.testnmax.append(testnmax) + + # n2min and n2max conservation + n2array = myctm.activeConc_ppb("N2") + n2min, n2max = np.amin(n2array), np.amax(n2array) + self.n2min.append(n2min) + self.n2max.append(n2max) + + o3array = myctm.activeConc_ppb("O3") + o3minconc, o3maxconc = np.amin(o3array), np.amax(o3array) + self.o3max.append(o3maxconc) + self.o3min.append(o3minconc) + + self.trcmax.append(np.amax(myctm.activeConc_ppb("TRC"))) + self.trchalf.append(halfvolume(myctm, "TRC")) + if myctm.t > 0.0: + self.niters.append(myctm.niters) + else: + self.niters.append(np.nan) + + if myctmref is not None: + if self.pdfdist is None: + self.pdfdist = {} + for sp in accurspecs: + self.pdfdist[sp] = [] + for sp in accurspecs: + self.pdfdist[sp].append(pdfdist(sp, myctm, myctmref)) + + def addscatter(self, myctm, sp1, sp2): + if sp1 == "NOx": + self.scatters[sp1, sp2, myctm.t] = Scatter( + myctm.activeConc_ppb("NO").flatten() + + myctm.activeConc_ppb("NO2").flatten(), + myctm.activeConc_ppb(sp2).flatten(), + myctm.t, + unitx="ppb", + unity="ppb", + spx=sp1, + spy=sp2, + ) + elif sp1 == "TRC1" and sp2 == "TRC2": + self.scatters[sp1, sp2, myctm.t] = Scatter( + myctm.activeConc_ppb(sp1).flatten(), + myctm.activeConc_ppb(sp2).flatten(), + myctm.t, + unitx=None, + unity=None, + spx=sp1, + spy=sp2, + ) + else: + self.scatters[sp1, sp2, myctm.t] = Scatter( + myctm.activeConc_ppb(sp1), + myctm.activeConc_ppb(sp2).flatten(), + myctm.t, + unitx="ppb", + unity="ppb", + spx=sp1, + spy=sp2, + ) + + +# END SIMULATION CLASS DEFINITION + + +def dict2latex(normdict, reflabel, norm="norm1"): + out = "" + spset = set() + labset = set() + + for label in normdict: + lab, sp = label + if lab == reflabel: + continue + spset.add(sp) + labset.add(lab) + sptup = simdict + + splist = sorted(list(spset), reverse=True) # we like to have TRC and O3 first + lablist = sorted(list(labset)) + + ccc = "c" + for sp in splist: + ccc = ccc + "c" + + line = "\\begin{{tabular}}{{ {cols} }}".format(cols=ccc) + out = out + line + "\n" + + line = "\\hline" + out = out + line + "\n" + + line = " " + for sp in splist: + line = line + "&" + line = line + " {specname} ".format(specname=sp) + line = line + "\\\\" + out = out + line + "\n" + + line = "\\hline" + out = out + line + "\n" + + for lab in lablist: + line = "{label} ".format(label=lab) + for sp in splist: + line = line + "&" + line = line + " {:1.3} ".format(normdict[lab, sp]) + line = line + "\\\\" + out = out + line + "\n" + + line = "\\hline" + out = out + line + "\n" + + line = "\\end{tabular}" + out = out + line + "\n" + + return out + + +def pdfdist(spec, myctm, myctmref): + conc = myctm.activeConc_ppb(spec) + concref = myctmref.activeConc_ppb(spec) + cflat = np.sort(conc, axis=None) # sort the flattened array in increasing order + crefflat = np.sort(concref, axis=None) + return np.sum(np.abs(cflat - crefflat)) / np.sum( + np.abs(crefflat) + ) # The result is adimensionnal typically between 0 and 1 + + +def plotpdfs(myctm, myctmref, prefix="pdfdisttribs-", fancy=False): + + for sp in accurspecs: + conc = np.sort( + myctm.activeConc_ppb(sp), axis=None + ) # sort the flattened array in increasing order + concref = np.sort(myctmref.activeConc_ppb(sp), axis=None) + norm1err = np.sum(np.abs(conc - concref)) / np.sum(np.abs(concref)) + pdf = ( np.cumsum(np.ones(conc.shape)) -1. ) / conc.size + + # Add initial and final point + a = np.array([0.0]) + z = np.array([1.0]) + conc = np.concatenate((a, conc, np.array([conc[-1]]))) + concref = np.concatenate((a, concref, np.array([concref[-1]]))) + pdf = np.concatenate((a, pdf, z)) + + fig, ax1 = plt.subplots(1, 1, figsize=(4.5, 4.0)) + ax1.step( + conc, + pdf, + label=nicelabel[myctm.ts.name], + color=colordict[myctm.ts.name], + linewidth=1.0, + ) + if sp in {"TRC", "TRC1", "TRC2"}: + ax1.step( + concref, + pdf, + label=r"$\mathcal{S}^0$", + color=colordict[myctmref.ts.name], + linewidth=1.0, + ) + else: + ax1.step( + concref, + pdf, + label=nicelabel[myctmref.ts.name], + color=colordict[myctmref.ts.name], + linewidth=1.0, + ) + + ax1.set_ylim(0.0, 1.0) + ax1.set_xlim(-0.002 * np.max(concref), np.max(concref)) + ax1.text( + 0.5, + 0.01, + r"$\mathcal{{E}}_1={:1.3}$".format(norm1err), + verticalalignment="bottom", + horizontalalignment="center", + transform=ax1.transAxes, + color="green", + fontsize=15, + ) + if fancy: + for i in range(conc.size - 1): + # An additional point for origin has beed added to conc so the + # actual number of cells is conc.size-1 + ymin, ymax = pdf[i], pdf[i + 1] + xmin, xmax = min(conc[i], concref[i]), max(conc[i], concref[i]) + ax1.add_patch( + mpl.patches.Rectangle( + (xmin, ymin), + width=xmax - xmin, + height=ymax - ymin, + edgecolor=None, + facecolor="orange", + linewidth=0.75, + ) + ) + plt.rcParams["hatch.linewidth"] = 0.30 + ax1.fill_between( + concref, + pdf, + y2=1.0, + step="pre", + hatch="/////", + zorder=2, + edgecolor="black", + facecolor="none", + linewidth=0.0, + ) + plt.legend() + plt.xlabel(r"$\alpha{}\ \mathrm{(ppb)}$") + plt.ylabel(r"$\mathcal{S}^t$") + # plt.show() + plt.savefig( + diagspath + prefix + myctm.ts.name + "-" + sp + ".png", + dpi=300, + bbox_inches="tight", + ) + plt.savefig( + diagspath + prefix + myctm.ts.name + "-" + sp + ".pdf", bbox_inches="tight" + ) + plt.close() + + +def halfvolume(myctm, spec): + conc = myctm.activeConc_ppb(spec) * myctm.activeDensity() / 1.0e9 + # Here we suppose that all the cell volumes are equal and we produce a volume normalized by the volume of entire box + cflat = np.flip( + np.sort(conc, axis=None) + ) # sort the flattened array in decreasing order + totconc = np.sum(cflat) + ncells = cflat.shape[0] + partconc = 0.0 + for i in range(ncells): + partconc = partconc + cflat[i] + if partconc > 0.5 * totconc: + break + partconc = partconc - cflat[i] + iaccur = float(i) + (0.5 * totconc - partconc) / cflat[i] + return iaccur / float(ncells) + + +# parameters + +############################################################# +# ARGUMENTS MANAGEMENT +parser = argparse.ArgumentParser( + description="""This script performs a 2d test case for horizontal advection in a square domain, for three possible advection schemes and three possible wind fields. Graphic outputs are in psiplots.""", + epilog="""Refer to the README for more details (maybe)""", +) + +parser.add_argument( + "--picklespath", + type=str, + default="artifacts/", + help="Relative or absolute path where the pickles are to be found", +) + +parser.add_argument( + "--diagspath", + type=str, + default="artifacts/", + help="Relative or absolute path to store the outputs", +) + +parser.add_argument('--CIcheck', + action = 'store_true', + help = 'Perform CI check for expected convergence rates') + +args = parser.parse_args() + + +picklespath = args.picklespath +diagspath = args.diagspath + +colordict = {} +colordict["null"] = "g" +colordict["ppmSL"] = "b" +colordict["vanleerSL"] = "r" +colordict["walcek"] = "m" +colordict["upwind"] = "c" +colordict["ppmw"] = "y" + +nicelabel = {} +nicelabel["null"] = "Base" +nicelabel["ppmSL"] = "PPM" +nicelabel["vanleerSL"] = "Van Leer" +nicelabel["walcek"] = "Walcek" +nicelabel["upwind"] = "Godunov" +nicelabel["ppmw"] = "PPM+W" + +accurspecs = {"O3", "NO", "NO2", "TRC", "TRC1", "TRC2"} + +plt.rc("axes", titlesize=16) # Increase axis title font for readability + +# Read pickles, list labels and times +files = [picklespath + f for f in os.listdir(picklespath) if not f.startswith(".") if f.endswith(".pickle")] +files.sort() # The list of files is then sorted alphabetically. +# Since files are named ${label}-${time} theyy are sorted by simulation and then chronological order + +# First loop to sort pickles +pickdict = {} +simdict = {} +for f in files: + with open(f, "rb") as file: + myctm = pickle.load(file) + t = np.fix( + myctm.t + 0.5 + ) # round up to the nearest integer second to avoid truncature errors + pickdict[myctm.label, t] = f + if myctm.label not in simdict: + simdict[myctm.label] = simulation(myctm) + if myctm.ts.name == "null": + reflabel = myctm.label + +for label, t in pickdict: + print(label, t) + f = pickdict[label, t] + + with open(f, "rb") as file: + myctm = pickle.load(file) + if label == reflabel: + simdict[myctm.label].add_diags(myctm) + else: + fref = pickdict[reflabel, t] + with open(fref, "rb") as file: + myctmref = pickle.load(file) + simdict[myctm.label].add_diags(myctm, myctmref) + if myctm.t < 1.0: # Initial time + simdict[myctm.label].addscatter(myctm, "TRC1", "TRC2") + simdict[myctm.label].inictm = myctm + if myctm.t > 43199.0 and myctm.t < 43201.0: # halftime + simdict[myctm.label].addscatter(myctm, "TRC1", "TRC2") + simdict[myctm.label].addscatter(myctm, "NOx", "O3") + simdict[myctm.label].halfctm = myctm + if myctm.t > 86399.0: # last time step + simdict[myctm.label].addscatter(myctm, "TRC1", "TRC2") + simdict[myctm.label].addscatter(myctm, "NOx", "O3") + simdict[myctm.label].finalctm = myctm + +# Plot diags + +# plot o3min and o3max +plt.figure(figsize=(4.5, 4.0)) +for lab in simdict: + plt.plot( + simdict[lab].times, + simdict[lab].o3max, + label=nicelabel[simdict[lab].tsname], + color=colordict[simdict[lab].tsname], + ) + + plt.plot( + simdict[lab].times, + simdict[lab].o3min, + linestyle="--", + color=colordict[simdict[lab].tsname], + ) +plt.legend() +plt.xlabel("t (s)") +plt.ylabel(r"$\alpha{}_{\mathrm{O3}}\,\mathrm{(ppb)}$") +# plt.show() +plt.savefig(diagspath + "o3minmax.png", dpi=300, bbox_inches="tight") +plt.savefig(diagspath + "o3minmax.pdf", bbox_inches="tight") +plt.close() + +# plot cmin and cmax +plt.figure(figsize=(4.5, 4.0)) +for lab in simdict: + plt.plot( + simdict[lab].times, + simdict[lab].cmax, + label=nicelabel[simdict[lab].tsname], + color=colordict[simdict[lab].tsname], + ) + + plt.plot( + simdict[lab].times, + simdict[lab].cmin, + linestyle="--", + color=colordict[simdict[lab].tsname], + ) +plt.xlabel("t (s)") +plt.legend() +# plt.show() +plt.savefig(diagspath + "cminmax.png", dpi=300, bbox_inches="tight") +plt.savefig(diagspath + "cminmax.pdf", bbox_inches="tight") +plt.close() + +# plot testnmin and testnmax +plt.figure(figsize=(4.5, 4.0)) +for lab in simdict: + plt.plot( + simdict[lab].times, + simdict[lab].testnmax, + label=nicelabel[simdict[lab].tsname], + color=colordict[simdict[lab].tsname], + ) + + plt.plot( + simdict[lab].times, + simdict[lab].testnmin, + linestyle="--", + color=colordict[simdict[lab].tsname], + ) +plt.xlabel("t (s)") +plt.legend() +# plt.show() +plt.savefig(diagspath + "testnminmax.png", dpi=300, bbox_inches="tight") +plt.savefig(diagspath + "testnminmax.pdf", bbox_inches="tight") +plt.close() + +# plot n2min and n2max +plt.figure(figsize=(4.5, 4.0)) +for lab in simdict: + plt.plot( + simdict[lab].times, + simdict[lab].n2max, + label=nicelabel[simdict[lab].tsname], + color=colordict[simdict[lab].tsname], + ) + + plt.plot( + simdict[lab].times, + simdict[lab].n2min, + linestyle="--", + color=colordict[simdict[lab].tsname], + ) +plt.xlabel("t (s)") +plt.legend() +# plt.show() +plt.savefig(diagspath + "n2minmax.png", dpi=300, bbox_inches="tight") +plt.savefig(diagspath + "n2minmax.pdf", bbox_inches="tight") +plt.close() + +# plot max TRC +plt.figure(figsize=(4.5, 4.0)) +for lab in simdict: + plt.plot( + simdict[lab].times, + simdict[lab].trcmax, + label=nicelabel[simdict[lab].tsname], + color=colordict[simdict[lab].tsname], + ) + +plt.legend() +plt.xlabel("t (s)") +plt.ylabel(r"$\alpha{}_{\mathrm{TRC}}\,\mathrm{(ppb)}$") +# plt.show() +plt.savefig(diagspath + "trcmax.png", dpi=300, bbox_inches="tight") +plt.savefig(diagspath + "trcmax.pdf", bbox_inches="tight") +plt.close() + +# plot TRC half volume +plt.figure(figsize=(4.5, 4.0)) +for lab in simdict: + plt.plot( + simdict[lab].times, + simdict[lab].trchalf, + label=nicelabel[simdict[lab].tsname], + color=colordict[simdict[lab].tsname], + ) + +plt.legend() +ylim = np.asarray(plt.gca().get_ylim()) +plt.gca().set_ylim((0.0, ylim[1])) # Force axis minimum to 0 +# plt.show() +plt.xlabel("t (s)") +plt.ylabel("Half-volume (nd)") +plt.savefig(diagspath + "halfvolume.png", dpi=300, bbox_inches="tight") +plt.savefig(diagspath + "halfvolume.pdf", bbox_inches="tight") +plt.close() + +# plot the mass conservation checks +plt.figure(figsize=(4.5, 4.0)) +for lab in simdict: + plt.plot( + simdict[lab].times, + simdict[lab].nmass, + label=nicelabel[simdict[lab].tsname], + color=colordict[simdict[lab].tsname], + ) + +plt.legend() +# plt.show() +plt.savefig(diagspath + "nmass.png", dpi=300, bbox_inches="tight") +plt.savefig(diagspath + "nmass.pdf", bbox_inches="tight") +plt.close() + + +# N2 mass +plt.figure(figsize=(4.5, 4.0)) +for lab in simdict: + plt.plot( + simdict[lab].times, + simdict[lab].n2mass, + label=nicelabel[simdict[lab].tsname], + color=colordict[simdict[lab].tsname], + ) + +plt.legend() +# plt.show() +plt.savefig(diagspath + "n2mass.png", dpi=300, bbox_inches="tight") +plt.savefig(diagspath + "n2mass.pdf", bbox_inches="tight") +plt.close() + + +# O3 mixing ratio +plt.figure(figsize=(4.5, 4.0)) +for lab in simdict: + plt.plot( + simdict[lab].times, + np.asarray(simdict[lab].o3mass) / np.asarray(simdict[lab].mmass) * 1.0e9, + label=nicelabel[simdict[lab].tsname], + color=colordict[simdict[lab].tsname], + ) + +plt.xlabel("t (s)") +plt.ylabel(r"Average $\mathrm{O}_3$ mixing ratio (ppb)") +plt.legend() +# plt.show() +plt.savefig(diagspath + "o3mr.png", dpi=300, bbox_inches="tight") +plt.savefig(diagspath + "o3mr.pdf", bbox_inches="tight") +plt.close() + +# NO mixing ratio +plt.figure(figsize=(4.5, 4.0)) +for lab in simdict: + plt.plot( + simdict[lab].times, + np.asarray(simdict[lab].nomass) / np.asarray(simdict[lab].mmass) * 1.0e9, + label=nicelabel[simdict[lab].tsname], + color=colordict[simdict[lab].tsname], + ) + +plt.xlabel("t (s)") +plt.ylabel(r"Average NO mixing ratio (ppb)") +plt.legend() +# plt.show() +plt.savefig(diagspath + "nomr.png", dpi=300, bbox_inches="tight") +plt.savefig(diagspath + "nomr.pdf", bbox_inches="tight") +plt.close() + +# NO2 mixing ratio +plt.figure(figsize=(4.5, 4.0)) +for lab in simdict: + plt.plot( + simdict[lab].times, + np.asarray(simdict[lab].no2mass) / np.asarray(simdict[lab].mmass) * 1.0e9, + label=nicelabel[simdict[lab].tsname], + color=colordict[simdict[lab].tsname], + ) + +plt.xlabel("t (s)") +plt.ylabel(r"Average NO2 mixing ratio (ppb)") +plt.legend() +# plt.show() +plt.savefig(diagspath + "no2mr.png", dpi=300, bbox_inches="tight") +plt.savefig(diagspath + "no2mr.pdf", bbox_inches="tight") +plt.close() + + +plt.figure(figsize=(4.5, 4.0)) +for lab in simdict: + plt.plot( + simdict[lab].times, + simdict[lab].hmass, + label=nicelabel[simdict[lab].tsname], + color=colordict[simdict[lab].tsname], + ) + +plt.legend() +# plt.show() +plt.savefig(diagspath + "hmass.png", dpi=300, bbox_inches="tight") +plt.savefig(diagspath + "hmass.pdf", bbox_inches="tight") +plt.close() + +plt.figure(figsize=(4.5, 4.0)) +for lab in simdict: + plt.plot( + simdict[lab].times, + simdict[lab].cmass, + label=nicelabel[simdict[lab].tsname], + color=colordict[simdict[lab].tsname], + ) + +plt.legend() +# plt.show() +plt.savefig(diagspath + "cmass.png", dpi=300, bbox_inches="tight") +plt.savefig(diagspath + "cmass.pdf", bbox_inches="tight") +plt.close() + +plt.figure() +for lab in simdict: + plt.plot( + simdict[lab].times, + simdict[lab].trcmass, + label=nicelabel[simdict[lab].tsname], + color=colordict[simdict[lab].tsname], + ) + +plt.legend() +# plt.show() +plt.savefig(diagspath + "trcmass.png", dpi=300, bbox_inches="tight") +plt.savefig(diagspath + "trcmass.pdf", bbox_inches="tight") +plt.close() + + +plt.figure(figsize=(4.5, 4.0)) +for lab in simdict: + plt.plot( + simdict[lab].times, + simdict[lab].omass, + label=nicelabel[simdict[lab].tsname], + color=colordict[simdict[lab].tsname], + ) + +plt.legend() +# plt.show() +plt.savefig(diagspath + "omass.png", dpi=300, bbox_inches="tight") +plt.savefig(diagspath + "omass.pdf", bbox_inches="tight") +plt.close() + + +# plot niters +plt.figure(figsize=(4.5, 4.0)) +for lab in simdict: + plt.plot( + simdict[lab].times, + simdict[lab].niters, + label=nicelabel[simdict[lab].tsname], + color=colordict[simdict[lab].tsname], + ) + +plt.legend() +# plt.show() +plt.xlabel("t (s)") +plt.savefig(diagspath + "niters.png", dpi=300, bbox_inches="tight") +plt.savefig(diagspath + "niters.pdf", bbox_inches="tight") +plt.close() + + +# Plot pdf error with and without Godunov + +for plotGodunov in True, False: + for sp in accurspecs: + plt.figure(figsize=(6.4, 4.8)) + # This is the default figure size in matplotlib, bigger than the files intended for 3column to obtain more or less uniform fonts + for lab in simdict: + if lab == reflabel: + continue + if not plotGodunov: + if nicelabel[simdict[lab].tsname] == "Godunov": + continue + plt.plot( + simdict[lab].times, + simdict[lab].pdfdist[sp], + label=nicelabel[simdict[lab].tsname], + color=colordict[simdict[lab].tsname], + ) + plt.legend() + plt.xlabel("t (s)") + plt.ylabel(r"$\mathcal{E}_1$") + if plotGodunov: + plt.savefig( + diagspath + "pdfdist-" + sp + ".png", dpi=300, bbox_inches="tight" + ) + plt.savefig(diagspath + "pdfdist-" + sp + ".pdf", bbox_inches="tight") + else: + plt.savefig( + diagspath + "pdfdist-noGod-" + sp + ".png", dpi=300, bbox_inches="tight" + ) + plt.savefig(diagspath + "pdfdist-noGod-" + sp + ".pdf", bbox_inches="tight") + + plt.close() + + +# Plot scatter + +xvec = np.linspace(0.1, 0.9, 100) +refscat_trc = Scatter( + xvec * 1.0e9, + np.asarray([0.9 - 0.8 * x ** 2 for x in xvec]) * 1.0e9, + unitx=None, + unity=None, + spx="TRC1", + spy="TRC2", +) + +for lab in simdict: + if lab == reflabel: + continue + for key in simdict[lab].scatters: + sp1, sp2, t = key + scat = simdict[lab].scatters[key] + if sp1 == "TRC1" and sp2 == "TRC2": + scat.plot( + color=colordict[simdict[lab].tsname], refscat=refscat_trc, simlabel=lab + ) + else: + scat.plot( + color=colordict[simdict[lab].tsname], + refscat=simdict[reflabel].scatters[key], + simlabel=lab, + ) + +# Plot complete PDFs +for lab in simdict: + if lab == reflabel: + continue + plotpdfs(simdict[lab].finalctm, simdict[reflabel].finalctm, prefix="finalpdf") + plotpdfs(simdict[lab].halfctm, simdict[reflabel].halfctm, prefix="halfpdf") + plotpdfs( + simdict[lab].finalctm, + simdict[reflabel].finalctm, + prefix="finalpdf_fancy_", + fancy=True, + ) + plotpdfs( + simdict[lab].halfctm, + simdict[reflabel].halfctm, + prefix="halfpdf_fancy_", + fancy=True, + ) + +# Make some maps +# Map the flow + +for lab in simdict: + if lab == reflabel: + continue + simdict[lab].inictm.MassFluxFunc.plotpsi( + 0.0, 1.0e5, 0.0, 1.0e5, simdict[lab].inictm.t, fname=diagspath + "psi_leveque" + ) + break + +# Initialisations for maps +sdict = {} +sdict["O3"] = "ppb" +sdict["NO"] = "ppb" +sdict["NO2"] = "ppb" +sdict["TRC"] = "ppb" +sdict["TRC1"] = "ppb" +sdict["TRC2"] = "ppb" +sdict["CO"] = "ppb" +sdict["testN"] = "ppb" +sdict["testC"] = "ppb" + +for lab in simdict: + ctmtup = (simdict[lab].inictm, simdict[lab].halfctm, simdict[lab].finalctm) + + for chemtm in ctmtup: + for spec in sdict: + if spec == "testN": + chemtm.makeplotchem( + sp=("NO", "NO2", "HNO3", "TRCb"), + simlabel=lab, + unit=sdict[spec], + spname="testN", + ) + elif spec == "testC": + chemtm.makeplotchem( + sp=("CO", "CO2"), simlabel=lab, unit=sdict[spec], spname="testC" + ) + else: + chemtm.makeplotchem(sp=spec, simlabel=lab, unit=sdict[spec]) + +# Accuracy diagnostics + +norm1dict = {} +norm2dict = {} +e1dict = {} + +# Norm-2 +for lab in simdict: + if lab == reflabel: + continue + + for sp in accurspecs: + diff = simdict[lab].finalctm.activeConc_ppb(sp) - simdict[ + reflabel + ].finalctm.activeConc_ppb(sp) + ref = simdict[reflabel].finalctm.activeConc_ppb(sp) + norm2error = np.sqrt(np.sum(diff * diff)) / np.sqrt( + np.sum(ref * ref) + ) # RMSE normalized by reference value + norm2dict[nicelabel[simdict[lab].tsname], sp] = norm2error + +latex2 = dict2latex(norm2dict, reflabel, norm="norm2") +print(latex2) +with open(diagspath + "norm2.tex", "w") as text_file: + text_file.write(latex2) + +# Norm-1 +for lab in simdict: + if lab == reflabel: + continue + for sp in accurspecs: + diff = np.abs( + simdict[lab].finalctm.activeConc_ppb(sp) + - simdict[reflabel].finalctm.activeConc_ppb(sp) + ) + ref = simdict[reflabel].finalctm.activeConc_ppb(sp) + norm1error = np.sum(diff) / np.sum(ref) # RMSE normalized by reference value + norm1dict[nicelabel[simdict[lab].tsname], sp] = norm1error + +latex1 = dict2latex(norm1dict, reflabel, norm="norm1") +print(latex1) +with open(diagspath + "norm1.tex", "w") as text_file: + text_file.write(latex1) + +# e1 +for lab in simdict: + if lab == reflabel: + continue + for sp in accurspecs: + e1dict[nicelabel[simdict[lab].tsname], sp] = pdfdist( + sp, simdict[lab].finalctm, simdict[reflabel].finalctm + ) + +latex_e1 = dict2latex(e1dict, reflabel, norm="e1") +print(latex_e1) +with open(diagspath + "e1.tex", "w") as text_file: + text_file.write(latex_e1) + + +# Screen outputs for initial / final mass for C, H, N +for lab in simdict: + print( + nicelabel[simdict[lab].tsname], + "C & & ", + simdict[lab].cmass[0], + "& ", + simdict[lab].cmass[-1], + "&", + np.abs(simdict[lab].cmass[-1] - simdict[lab].cmass[0]) / simdict[lab].cmass[0], + ) + print( + nicelabel[simdict[lab].tsname], + "N & & ", + simdict[lab].nmass[0], + "& ", + simdict[lab].nmass[-1], + "&", + np.abs(simdict[lab].nmass[-1] - simdict[lab].nmass[0]) / simdict[lab].nmass[0], + ) + print( + nicelabel[simdict[lab].tsname], + "H & & ", + simdict[lab].hmass[0], + "& ", + simdict[lab].hmass[-1], + "&", + np.abs(simdict[lab].hmass[-1] - simdict[lab].hmass[0]) / simdict[lab].hmass[0], + ) + print( + nicelabel[simdict[lab].tsname], + "TRC & & ", + simdict[lab].trcmass[0], + "& ", + simdict[lab].trcmass[-1], + "&", + np.abs(simdict[lab].trcmass[-1] - simdict[lab].trcmass[0]) + / simdict[lab].trcmass[0], + ) + +# if CI test +if(args.CIcheck): + CItolerance = 0.01 + passed = True + targetErrorDict = {} + targetErrorDict['Godunov', 'TRC'] = 1.23 + targetErrorDict['PPM', 'TRC'] = 0.986 + targetErrorDict['PPM+W', 'TRC'] = 0.946 + targetErrorDict['Van Leer', 'TRC'] = 1.05 + targetErrorDict['Walcek', 'TRC'] = 0.961 + targetErrorDict['Godunov', 'O3'] = 1.53 + targetErrorDict['PPM', 'O3'] = 0.979 + targetErrorDict['PPM+W', 'O3'] = 0.897 + targetErrorDict['Van Leer', 'O3'] = 1.09 + targetErrorDict['Walcek', 'O3'] = 0.914 + for sp in ('TRC', 'O3'): + for scheme in ('Godunov', 'PPM', 'PPM+W', 'Van Leer', 'Walcek', 'Godunov'): + if (norm1dict[scheme, sp] < targetErrorDict[scheme, sp] + 0.01): + print('CI test', scheme, sp, norm1dict[scheme, sp], '<=', targetErrorDict[scheme, sp],'PASSED') + else: + passed = False + print('CI test', scheme, sp, norm1dict[scheme, sp], '> ', targetErrorDict[scheme, sp],'FAILED') + + if(passed): + print('CI test sussessful. Leaving') + sys.exit(0) + else: + print('ERROR: CI test failed') + sys.exit(1) + diff --git a/2dtestcases.py b/2dtestcases.py index 089813b34b12916dac3e69800894f1b1616eb9fd..334aa9a48465e0d4281f565ccdc0b0a93a6cc22d 100755 --- a/2dtestcases.py +++ b/2dtestcases.py @@ -106,7 +106,7 @@ parser.add_argument( parser.add_argument( "--picklespath", type=str, - default="pickles/", + default="artifacts/", help="Relative or absolute path to store the pickles", ) @@ -611,7 +611,7 @@ print("init TRC mass", myctm.mass("TRC")) if makedumps: file = open( - picklespath + "{0}-{1:06d}".format(myctm.label, int(myctm.t + 0.5)), "wb" + picklespath + "{0}-{1:06d}.pickle".format(myctm.label, int(myctm.t + 0.5)), "wb" ) pickle.dump(myctm, file) file.close() @@ -644,7 +644,7 @@ for i in range(nt): # Dump myctm if makedumps: print(myctm.t, int(myctm.t + 0.5)) - file = open(picklespath + flabel, "wb") + file = open(picklespath + flabel + '.pickle', "wb") pickle.dump(myctm, file) file.close()