/* This file is part of CDO. CDO is a collection of Operators to manipulate and analyse Climate model Data. Copyright (C) 2003-2020 Uwe Schulzweida, See COPYING file for copying and redistribution conditions. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ /* This module contains the following operators: Vertint ml2pl Model to pressure level interpolation Vertint ml2hl Model to height level interpolation */ #include #include "cdo_options.h" #include "process_int.h" #include "cdo_vlist.h" #include "field_vinterp.h" #include "stdnametable.h" #include "constants.h" #include "util_string.h" #include "const.h" #include "cdo_zaxis.h" #include "param_conversion.h" static bool zaxis_is_hybrid(int zaxistype) { return (zaxistype == ZAXIS_HYBRID || zaxistype == ZAXIS_HYBRID_HALF); } static void change_hybrid_zaxis(int vlistID1, int vlistID2, int nvct, double *vct, int zaxisID2, int nhlevf, int nhlevh) { const auto nzaxis = vlistNzaxis(vlistID1); for (int iz = 0; iz < nzaxis; ++iz) { const auto zaxisID = vlistZaxis(vlistID1, iz); const auto nlevel = zaxisInqSize(zaxisID); const auto zaxistype = zaxisInqType(zaxisID); if (zaxis_is_hybrid(zaxistype) && (nlevel == nhlevh || nlevel == nhlevf)) { const auto nvct2 = zaxisInqVctSize(zaxisID); if (nvct2 == nvct && memcmp(vct, zaxisInqVctPtr(zaxisID), nvct * sizeof(double)) == 0) vlistChangeZaxisIndex(vlistID2, iz, zaxisID2); } } } void * Vertintml(void *process) { ModelMode mode(ModelMode::UNDEF); enum { func_pl, func_hl }; enum { type_lin, type_log }; int nrecs; int varID, levelID; bool sgeopot_needed = false; bool extrapolate = false; int sgeopotID = -1, geopotID = -1, tempID = -1, psID = -1, lnpsID = -1, gheightID = -1; char paramstr[32]; char varname[CDI_MAX_NAME], stdname[CDI_MAX_NAME]; gribcode_t gribcodes; memset(&gribcodes, 0, sizeof(gribcode_t)); cdoInitialize(process); // clang-format off const auto ML2PL = cdoOperatorAdd("ml2pl", func_pl, type_lin, "pressure levels in pascal"); const auto ML2PLX = cdoOperatorAdd("ml2plx", func_pl, type_lin, "pressure levels in pascal"); const auto ML2HL = cdoOperatorAdd("ml2hl", func_hl, type_lin, "height levels in meter"); const auto ML2HLX = cdoOperatorAdd("ml2hlx", func_hl, type_lin, "height levels in meter"); const auto ML2PL_LP = cdoOperatorAdd("ml2pl_lp", func_pl, type_log, "pressure levels in pascal"); const auto ML2PLX_LP = cdoOperatorAdd("ml2plx_lp", func_pl, type_log, "pressure levels in pascal"); const auto ML2HL_LP = cdoOperatorAdd("ml2hl_lp", func_hl, type_log, "height levels in meter"); const auto ML2HLX_LP = cdoOperatorAdd("ml2hlx_lp", func_hl, type_log, "height levels in meter"); // clang-format on const auto operatorID = cdoOperatorID(); const bool useHightLevel = cdoOperatorF1(operatorID) == func_hl; const bool useLogType = cdoOperatorF2(operatorID) == type_log; if (operatorID == ML2PL || operatorID == ML2HL || operatorID == ML2PL_LP || operatorID == ML2HL_LP) { const char *envstr = getenv("EXTRAPOLATE"); if (envstr && isdigit((int) envstr[0])) { if (atoi(envstr) == 1) extrapolate = true; if (extrapolate) cdoPrint("Extrapolation of missing values enabled!"); } } else if (operatorID == ML2PLX || operatorID == ML2HLX || operatorID == ML2PLX_LP || operatorID == ML2HLX_LP) { extrapolate = true; } operatorInputArg(cdoOperatorEnter(operatorID)); Varray plev; if (operatorArgc() == 1 && cdoOperatorArgv(0).compare("default") == 0) { if (useHightLevel) plev = { 10, 50, 100, 500, 1000, 5000, 10000, 15000, 20000, 25000, 30000 }; else plev = { 100000, 92500, 85000, 70000, 60000, 50000, 40000, 30000, 25000, 20000, 15000, 10000, 7000, 5000, 3000, 2000, 1000 }; } else { plev = cdoArgvToFlt(cdoGetOperArgv()); } int nplev = plev.size(); const auto streamID1 = cdoOpenRead(0); const auto vlistID1 = cdoStreamInqVlist(streamID1); const auto vlistID2 = vlistDuplicate(vlistID1); const auto taxisID1 = vlistInqTaxis(vlistID1); const auto taxisID2 = taxisDuplicate(taxisID1); vlistDefTaxis(vlistID2, taxisID2); const auto gridsize = vlist_check_gridsize(vlistID1); const int zaxistype = useHightLevel ? ZAXIS_HEIGHT : ZAXIS_PRESSURE; const auto zaxisIDp = zaxisCreate(zaxistype, nplev); zaxisDefLevels(zaxisIDp, plev.data()); int nvct = 0; int zaxisIDh = -1; int nhlev = 0, nhlevf = 0, nhlevh = 0; Varray vct; vlist_read_vct(vlistID1, &zaxisIDh, &nvct, &nhlev, &nhlevf, &nhlevh, vct); change_hybrid_zaxis(vlistID1, vlistID2, nvct, vct.data(), zaxisIDp, nhlevf, nhlevh); int psvarID = -1; bool linvertvct = false; if (!vct.empty() && nvct && nvct % 2 == 0) { psvarID = vlist_get_psvarid(vlistID1, zaxisIDh); int i; for (i = nvct / 2 + 1; i < nvct; i++) if (vct[i] > vct[i - 1]) break; if (i == nvct) linvertvct = true; } if (Options::cdoVerbose) cdoPrint("linvertvct = %d", (int) linvertvct); if (linvertvct) { Varray vctbuf(nvct); for (int i = 0; i < nvct; ++i) vctbuf[i] = vct[i]; for (int i = 0; i < nvct / 2; i++) { vct[nvct / 2 - 1 - i] = vctbuf[i]; vct[nvct - 1 - i] = vctbuf[i + nvct / 2]; } } VarList varList1, varList2; varListInit(varList1, vlistID1); varListInit(varList2, vlistID2); varListSetUniqueMemtype(varList1); const auto memtype = varList1[0].memType; const auto nvars = vlistNvars(vlistID1); std::vector vars(nvars), varinterp(nvars); std::vector> varnmiss(nvars); Field3DVector vardata1(nvars), vardata2(nvars); const int maxlev = nhlevh > nplev ? nhlevh : nplev; std::vector pnmiss; if (!extrapolate) pnmiss.resize(nplev); // check levels if (zaxisIDh != -1) { const auto nlev = zaxisInqSize(zaxisIDh); if (nlev != nhlev) cdoAbort("Internal error, wrong number of hybrid level!"); } std::vector vert_index; Field3D full_press, half_press; if (zaxisIDh != -1 && gridsize > 0) { vert_index.resize(gridsize * nplev); CdoVar var3Df, var3Dh; var3Df.gridsize = gridsize; var3Df.nlevels = nhlevf; var3Df.memType = memtype; full_press.init(var3Df); var3Dh.gridsize = gridsize; var3Dh.nlevels = nhlevh; var3Dh.memType = memtype; half_press.init(var3Dh); } else cdoWarning("No 3D variable with hybrid sigma pressure coordinate found!"); if (useHightLevel) { Varray phlev(nplev); height2pressure(&phlev[0], plev.data(), nplev); if (Options::cdoVerbose) for (int i = 0; i < nplev; ++i) cdoPrint("level = %d height = %g pressure = %g", i + 1, plev[i], phlev[i]); plev = phlev; } if (useLogType) for (int k = 0; k < nplev; k++) plev[k] = std::log(plev[k]); bool useTable = false; for (varID = 0; varID < nvars; varID++) { const int tableNum = tableInqNum(vlistInqVarTable(vlistID1, varID)); if (tableNum > 0 && tableNum != 255) { useTable = true; break; } } if (Options::cdoVerbose && useTable) cdoPrint("Using code tables!"); for (varID = 0; varID < nvars; varID++) { const auto gridID = varList1[varID].gridID; const auto zaxisID = varList1[varID].zaxisID; const auto zaxistype = zaxisInqType(zaxisID); const auto nlevels = varList1[varID].nlevels; const auto instNum = institutInqCenter(vlistInqVarInstitut(vlistID1, varID)); const auto tableNum = tableInqNum(vlistInqVarTable(vlistID1, varID)); auto code = vlistInqVarCode(vlistID1, varID); const auto param = vlistInqVarParam(vlistID1, varID); cdiParamToString(param, paramstr, sizeof(paramstr)); int pnum, pcat, pdis; cdiDecodeParam(param, &pnum, &pcat, &pdis); if (pdis >= 0 && pdis < 255) code = -1; if (useTable) { if (tableNum == 2) { mode = ModelMode::WMO; wmo_gribcodes(&gribcodes); } else if (tableNum == 128 || tableNum == 0) { mode = ModelMode::ECHAM; echam_gribcodes(&gribcodes); } // KNMI: HIRLAM model version 7.2 uses tableNum=1 (LAMH_D11*) // KNMI: HARMONIE model version 36 uses tableNum=1 (grib*) (opreational NWP version) // KNMI: HARMONIE model version 38 uses tableNum=253 (grib,grib_md) and tableNum=1 (grib_sfx) (research version) else if (tableNum == 1 || tableNum == 253) { mode = ModelMode::HIRLAM; hirlam_harmonie_gribcodes(&gribcodes); } } else { mode = ModelMode::ECHAM; echam_gribcodes(&gribcodes); } if (Options::cdoVerbose) { vlistInqVarName(vlistID1, varID, varname); cdoPrint("Mode=%d Center=%d TableNum=%d Code=%d Param=%s Varname=%s varID=%d", mode, instNum, tableNum, code, paramstr, varname, varID); } if (code <= 0 || code == 255) { vlistInqVarName(vlistID1, varID, varname); cstrToLowerCase(varname); vlistInqVarStdname(vlistID1, varID, stdname); cstrToLowerCase(stdname); code = echamcode_from_stdname(stdname); if (code == -1) { // ECHAM ECMWF // clang-format off if (sgeopotID == -1 && (cstrIsEqual(varname, "geosp") || cstrIsEqual(varname, "z"))) code = gribcodes.geopot; else if (tempID == -1 && (cstrIsEqual(varname, "st") || cstrIsEqual(varname, "t"))) code = gribcodes.temp; else if (psID == -1 && (cstrIsEqual(varname, "aps") || cstrIsEqual(varname, "sp"))) code = gribcodes.ps; else if (lnpsID == -1 && (cstrIsEqual(varname, "lsp") || cstrIsEqual(varname, "lnsp"))) code = gribcodes.lsp; else if (geopotID == -1 && cstrIsEqual(stdname, "geopotential_full")) code = gribcodes.geopot; // else if (cstrIsEqual(varname, "geopoth")) code = 156; // clang-format on } } if (mode == ModelMode::ECHAM) { // clang-format off if (code == gribcodes.geopot && nlevels == 1) sgeopotID = varID; else if (code == gribcodes.geopot && nlevels == nhlevf) geopotID = varID; else if (code == gribcodes.temp && nlevels == nhlevf) tempID = varID; else if (code == gribcodes.ps && nlevels == 1) psID = varID; else if (code == gribcodes.lsp && nlevels == 1) lnpsID = varID; else if (code == gribcodes.gheight && nlevels == nhlevf) gheightID = varID; // clang-format on } else if (mode == ModelMode::WMO || mode == ModelMode::HIRLAM) { // clang-format off if (code == gribcodes.geopot && nlevels == 1) sgeopotID = varID; else if (code == gribcodes.geopot && nlevels == nhlevf) geopotID = varID; else if (code == gribcodes.temp && nlevels == nhlevf) tempID = varID; else if (code == gribcodes.ps && nlevels == 1) psID = varID; // clang-format on } if (gridInqType(gridID) == GRID_SPECTRAL && zaxis_is_hybrid(zaxistype)) cdoAbort("Spectral data on model level unsupported!"); if (gridInqType(gridID) == GRID_SPECTRAL) cdoAbort("Spectral data unsupported!"); if (varID == gheightID) varList1[varID].nlevels = nlevels + 1; vardata1[varID].init(varList1[varID]); // varinterp[varID] = ( zaxis_is_hybrid(zaxistype) && zaxisIDh != -1 && nlevels == nhlev ); varinterp[varID] = (zaxisID == zaxisIDh || (zaxis_is_hybrid(zaxistype) && zaxisIDh != -1 && (nlevels == nhlevh || nlevels == nhlevf))); if (varinterp[varID]) { varnmiss[varID].resize(maxlev, 0); vardata2[varID].init(varList2[varID]); } else { if (zaxis_is_hybrid(zaxistype) && zaxisIDh != -1 && nlevels > 1) { vlistInqVarName(vlistID1, varID, varname); cdoWarning("Parameter %d has wrong number of levels, skipped! (param=%s nlevel=%d)", varID + 1, varname, nlevels); } varnmiss[varID].resize(nlevels); } } if (Options::cdoVerbose) { cdoPrint("Found:"); if (tempID != -1) cdoPrint(" %s -> %s", var_stdname(air_temperature), cdoVlistInqVarName(vlistID1, tempID, varname)); if (psID != -1) cdoPrint(" %s -> %s", var_stdname(surface_air_pressure), cdoVlistInqVarName(vlistID1, psID, varname)); if (lnpsID != -1) cdoPrint(" LOG(%s) -> %s", var_stdname(surface_air_pressure), cdoVlistInqVarName(vlistID1, lnpsID, varname)); if (sgeopotID != -1) cdoPrint(" %s -> %s", var_stdname(surface_geopotential), cdoVlistInqVarName(vlistID1, sgeopotID, varname)); if (geopotID != -1) cdoPrint(" %s -> %s", var_stdname(geopotential), cdoVlistInqVarName(vlistID1, geopotID, varname)); if (gheightID != -1) cdoPrint(" %s -> %s", var_stdname(geopotential_height), cdoVlistInqVarName(vlistID1, gheightID, varname)); } if (tempID != -1 || gheightID != -1) sgeopot_needed = true; if (zaxisIDh != -1 && gheightID != -1 && tempID == -1) cdoAbort("Temperature not found, needed for vertical interpolation of geopotheight!"); int presID = lnpsID; if (psvarID != -1) presID = psvarID; if (zaxisIDh != -1 && presID == -1) { if (psID == -1) cdoAbort("%s not found!", var_stdname(surface_air_pressure)); else presID = psID; } if (Options::cdoVerbose) { if (presID == lnpsID) cdoPrint("using LOG(%s) from %s", var_stdname(surface_air_pressure), varList1[presID].name); else cdoPrint("using %s from %s", var_stdname(surface_air_pressure), varList1[presID].name); } Field ps_prog; ps_prog.init(varList1[presID]); Field sgeopot; if (zaxisIDh != -1 && sgeopot_needed) { sgeopot.init(varList1[presID]); if (sgeopotID == -1) { if (extrapolate) { if (geopotID == -1) cdoWarning("%s not found - set to zero!", var_stdname(surface_geopotential)); else cdoPrint("%s not found - using bottom layer of %s!", var_stdname(surface_geopotential), var_stdname(geopotential)); } fieldFill(sgeopot, 0.0); } } // check VCT if (zaxisIDh != -1) { double suma = 0, sumb = 0; for (int i = 0; i < nhlevh; ++i) suma += vct[i]; for (int i = 0; i < nhlevh; ++i) sumb += vct[i + nhlevh]; if (!(suma > 0 || sumb > 0)) cdoWarning("VCT is empty!"); } const auto streamID2 = cdoOpenWrite(1); cdoDefVlist(streamID2, vlistID2); int tsID = 0; while ((nrecs = cdoStreamInqTimestep(streamID1, tsID))) { for (varID = 0; varID < nvars; ++varID) vars[varID] = false; taxisCopyTimestep(taxisID2, taxisID1); cdoDefTimestep(streamID2, tsID); for (int recID = 0; recID < nrecs; recID++) { cdoInqRecord(streamID1, &varID, &levelID); const auto zaxisID = varList1[varID].zaxisID; const auto nlevels = varList1[varID].nlevels; if (linvertvct && zaxisIDh != -1 && zaxisID == zaxisIDh) levelID = nlevels - 1 - levelID; cdoReadRecord(streamID1, vardata1[varID], levelID, &varnmiss[varID][levelID]); vars[varID] = true; } if (zaxisIDh != -1) { if (sgeopot_needed) { if (sgeopotID != -1) fieldCopy(vardata1[sgeopotID], sgeopot); else if (geopotID != -1) fieldCopy(vardata1[geopotID], nhlevf - 1, sgeopot); // check range of surface geopot if (extrapolate && (sgeopotID != -1 || geopotID != -1)) { const auto minmax = fieldMinMax(sgeopot); if (minmax.first < MIN_FIS || minmax.second > MAX_FIS) cdoWarning("Surface geopotential out of range (min=%g max=%g) [timestep:%d]!", minmax.first, minmax.second, tsID + 1); if (gridsize > 1 && minmax.first >= 0 && minmax.second <= 9000 && IS_NOT_EQUAL(minmax.first, minmax.second)) cdoWarning("Surface geopotential has an unexpected range (min=%g max=%g) [timestep:%d]!", minmax.first, minmax.second, tsID + 1); } } if (presID == lnpsID) { if (memtype == MemType::Float) for (size_t i = 0; i < gridsize; i++) ps_prog.vec_f[i] = std::exp(vardata1[lnpsID].vec_f[i]); else for (size_t i = 0; i < gridsize; i++) ps_prog.vec_d[i] = std::exp(vardata1[lnpsID].vec_d[i]); } else if (presID != -1) fieldCopy(vardata1[presID], ps_prog); // check range of ps_prog const auto minmax = fieldMinMax(ps_prog); if (minmax.first < MIN_PS || minmax.second > MAX_PS) cdoWarning("Surface pressure out of range (min=%g max=%g) [timestep:%d]!", minmax.first, minmax.second, tsID + 1); if (memtype == MemType::Float) presh(full_press.vec_f.data(), half_press.vec_f.data(), vct.data(), ps_prog.vec_f.data(), nhlevf, gridsize); else presh(full_press.vec_d.data(), half_press.vec_d.data(), vct.data(), ps_prog.vec_d.data(), nhlevf, gridsize); if (useLogType) { if (memtype == MemType::Float) { for (size_t i = 0; i < gridsize; i++) ps_prog.vec_f[i] = std::log(ps_prog.vec_f[i]); for (size_t ki = 0; ki < nhlevh * gridsize; ki++) half_press.vec_f[ki] = std::log(half_press.vec_f[ki]); for (size_t ki = 0; ki < nhlevf * gridsize; ki++) full_press.vec_f[ki] = std::log(full_press.vec_f[ki]); } else { for (size_t i = 0; i < gridsize; i++) ps_prog.vec_d[i] = std::log(ps_prog.vec_d[i]); for (size_t ki = 0; ki < nhlevh * gridsize; ki++) half_press.vec_d[ki] = std::log(half_press.vec_d[ki]); for (size_t ki = 0; ki < nhlevf * gridsize; ki++) full_press.vec_d[ki] = std::log(full_press.vec_d[ki]); } } genind(vert_index, plev, full_press, gridsize); if (!extrapolate) genindmiss(vert_index, plev, gridsize, ps_prog, pnmiss); } for (varID = 0; varID < nvars; varID++) { if (vars[varID]) { if (varinterp[varID]) { const auto nlevels = varList1[varID].nlevels; if (nlevels != nhlevf && nlevels != nhlevh) cdoAbort("Number of hybrid level differ from full/half level (param=%s)!", varList1[varID].name); for (levelID = 0; levelID < nlevels; levelID++) { if (varnmiss[varID][levelID]) cdoAbort("Missing values unsupported for this operator!"); } if (varID == tempID) { if (nlevels == nhlevh) cdoAbort("Temperature on half level unsupported!"); if (useLogType && extrapolate) cdoAbort("Log. extrapolation of temperature unsupported!"); vertical_interp_T(nlevels, full_press, half_press, vardata1[varID], vardata2[varID], sgeopot, vert_index, plev, gridsize); } else if (varID == gheightID) { if (memtype == MemType::Float) for (size_t i = 0; i < gridsize; ++i) vardata1[varID].vec_f[gridsize * nlevels + i] = sgeopot.vec_f[i] / PlanetGrav; else for (size_t i = 0; i < gridsize; ++i) vardata1[varID].vec_d[gridsize * nlevels + i] = sgeopot.vec_d[i] / PlanetGrav; vertical_interp_Z(nlevels, full_press, half_press, vardata1[varID], vardata2[varID], vardata1[tempID], sgeopot, vert_index, plev, gridsize); } else { vertical_interp_X(nlevels, full_press, half_press, vardata1[varID], vardata2[varID], vert_index, plev, gridsize); } if (!extrapolate) varrayCopy(nplev, pnmiss, varnmiss[varID]); } const auto nlevels = varList2[varID].nlevels; for (levelID = 0; levelID < nlevels; levelID++) { cdoDefRecord(streamID2, varID, levelID); cdoWriteRecord(streamID2, varinterp[varID] ? vardata2[varID] : vardata1[varID], levelID, varnmiss[varID][levelID]); } } } tsID++; } cdoStreamClose(streamID2); cdoStreamClose(streamID1); cdoFinish(); return nullptr; }