diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d288dc85..ad7447584 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,4 +52,30 @@ jobs: - name: Run tests working-directory: ./build - run: ctest --output-on-failure \ No newline at end of file + run: ctest --output-on-failure + + # Additional multithreading reproducibility step for one build (no need to do it for both) + - name: Install uv for multithreading reproducibility test + if: strategy.job-index == 0 + uses: astral-sh/setup-uv@v9.0.0 + + - name: Multithreading reproducibility test + if: strategy.job-index == 0 + shell: bash + timeout-minutes: 15 + env: + PYTHONUNBUFFERED: "1" + # Run steps: + # Define where the test is and where its inputs are + # Run APCEMM twice, using a base.yaml overridden by a second yaml setting the thread count to 1 or 4 + # Use tee to also write stdout to a log file + # grep the log to ensure that APCEMM correctly ran with the expected number of threads + # Run the comparison script with uv to ensure they are bitwise identical + run: | + TEST_DIR="$GITHUB_WORKSPACE/Code.v05-00/tests/multithreading" + INPUTS="$TEST_DIR/inputs" + "$GITHUB_WORKSPACE/build/APCEMM" "$INPUTS/base.yaml" "$INPUTS/1_thread.yaml" | tee run_1_thread.log + "$GITHUB_WORKSPACE/build/APCEMM" "$INPUTS/base.yaml" "$INPUTS/4_threads.yaml" | tee run_4_threads.log + grep -q ">>> APCEMM OpenMP Num Threads is set to 1$" run_1_thread.log + grep -q ">>> APCEMM OpenMP Num Threads is set to 4$" run_4_threads.log + uv run "$TEST_DIR/compare_outputs.py" "$TEST_DIR/APCEMM_out_1_thread/" "$TEST_DIR/APCEMM_out_4_threads/" \ No newline at end of file diff --git a/.gitignore b/.gitignore index c669190e7..f85e127eb 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,7 @@ Code*/debug* rundirs/* !rundirs/SampleRunDir !rundirs/CreateRunFolder.sh +Code*/tests/multithreading/APCEMM_out* # netCDF files # ################ diff --git a/Code.v05-00/src/AIM/Aerosol.cpp b/Code.v05-00/src/AIM/Aerosol.cpp index aa1ca2a90..eb22ebba7 100644 --- a/Code.v05-00/src/AIM/Aerosol.cpp +++ b/Code.v05-00/src/AIM/Aerosol.cpp @@ -666,15 +666,11 @@ void Aerosol::addAerosolToPDF( const Aerosol &rhs ) { /* Only run coagulation where aerosol volume is greater * than 0.1 um^3/cm^3 */ - #pragma omp master - { - /* Update kernel (updates kernel.f and kernel.indices). - * This needs to be performed in serial as the class - * kernel is specific to the grid cell (jNy, iNx) */ - kernel.buildF(bin_VCenters, jNy, iNx); - } + /* Update kernel (updates kernel.f and kernel.indices). + * This needs to be performed in serial as the class + * kernel is specific to the grid cell (jNy, iNx) */ + kernel.buildF(bin_VCenters, jNy, iNx); - #pragma omp barrier #pragma omp parallel for default(shared) private(iBin, jBin, kBin, kBin_, nPart) \ schedule(dynamic, 1) if (!PARALLEL_CASES) @@ -772,8 +768,6 @@ void Aerosol::addAerosolToPDF( const Aerosol &rhs ) { Vector_3D iceVol = Volume( ); Vector_2D totH2O = H2O; - double pSat; - #pragma omp parallel if( !PARALLEL_CASES ) default( shared ) { @@ -814,7 +808,7 @@ void Aerosol::addAerosolToPDF( const Aerosol &rhs ) { locT = T[jNy][iNx]; /* Store local saturation pressure w.r.t ice */ - pSat = physFunc::pSat_H2Os( locT ); + double pSat = physFunc::pSat_H2Os( locT ); if ( H2O[jNy][iNx] * kB_ * locT / pSat > 0.0 ) { APC_Scheme(jNy,iNx, locT, locP, dt, H2O, totH2O, icePart, iceVol ); @@ -1177,34 +1171,39 @@ void Aerosol::addAerosolToPDF( const Aerosol &rhs ) { Vector_2D Grid_Aerosol::Moment() const { - UInt jNy = 0; - UInt iNx = 0; - UInt iBin = 0; - Vector_2D moment(Ny, Vector_1D(Nx, 0.0E+00)); const double FACTOR = 3.0 / double(4.0 * PI); - #pragma omp parallel for default(shared) private(iNx, jNy, iBin) \ - schedule(dynamic, 1) if (!PARALLEL_CASES) - for (iBin = 0; iBin < nBin; iBin++) + /* Parallelize over the row, not the bin so that each iteration computes + * moment[jNy] so there is no race. + * The bin summation order per cell is fixed, so results are reproducible + * with varying thread counts. Innermost loop keeps the contiguous data + * access pattern */ + #pragma omp parallel for default(shared) schedule(static) if (!PARALLEL_CASES) + for (UInt jNy = 0; jNy < Ny; jNy++) { - double pow_value; - for (jNy = 0; jNy < Ny; jNy++) + Vector_1D& momentRow = moment[jNy]; + for (UInt iBin = 0; iBin < nBin; iBin++) { - for (iNx = 0; iNx < Nx; iNx++) + // [[maybe_unused]] because N = 0 does not use vCenterRow and compiler would flag it + [[maybe_unused]] const Vector_1D& vCenterRow = bin_VCenters[iBin][jNy]; + const Vector_1D& pdfRow = pdf[iBin][jNy]; + const double dLogBin = log_Bin_Edges[iBin+1] - log_Bin_Edges[iBin]; + for (UInt iNx = 0; iNx < Nx; iNx++) { + double pow_value; // Moment function only used for N = 0, 1, 2, 3 --> eliminate usage of pow() to save time if constexpr (N == 0) pow_value = 1; else if constexpr (N == 1) - pow_value = cbrt(FACTOR * bin_VCenters[iBin][jNy][iNx]); + pow_value = cbrt(FACTOR * vCenterRow[iNx]); else if constexpr (N == 2) - pow_value = cbrt(FACTOR * FACTOR * bin_VCenters[iBin][jNy][iNx] * bin_VCenters[iBin][jNy][iNx]); + pow_value = cbrt(FACTOR * FACTOR * vCenterRow[iNx] * vCenterRow[iNx]); else if constexpr (N == 3) - pow_value = FACTOR * bin_VCenters[iBin][jNy][iNx]; + pow_value = FACTOR * vCenterRow[iNx]; else - pow_value = pow(FACTOR * bin_VCenters[iBin][jNy][iNx], N / 3.0); // fallback for safety - moment[jNy][iNx] += (log_Bin_Edges[iBin+1] - log_Bin_Edges[iBin]) * pow_value * pdf[iBin][jNy][iNx]; + pow_value = pow(FACTOR * vCenterRow[iNx], N / 3.0); // fallback for safety + momentRow[iNx] += dLogBin * pow_value * pdfRow[iNx]; } } } @@ -1222,24 +1221,23 @@ void Aerosol::addAerosolToPDF( const Aerosol &rhs ) { case 3: return Moment<3>(); default: { - UInt jNy = 0; - UInt iNx = 0; - UInt iBin = 0; - Vector_2D moment(Ny, Vector_1D(Nx, 0.0E+00)); const double FACTOR = 3.0 / double(4.0 * PI); - #pragma omp parallel for default(shared) private(iNx, jNy, iBin) \ - schedule(dynamic, 1) if (!PARALLEL_CASES) - for (iBin = 0; iBin < nBin; iBin++) + // See Moment() for parallelization pattern + #pragma omp parallel for default(shared) schedule(static) if (!PARALLEL_CASES) + for (UInt jNy = 0; jNy < Ny; jNy++) { - double pow_value; - for (jNy = 0; jNy < Ny; jNy++) + Vector_1D& momentRow = moment[jNy]; + for (UInt iBin = 0; iBin < nBin; iBin++) { - for (iNx = 0; iNx < Nx; iNx++) + const Vector_1D& vCenterRow = bin_VCenters[iBin][jNy]; + const Vector_1D& pdfRow = pdf[iBin][jNy]; + const double dLogBin = log_Bin_Edges[iBin+1] - log_Bin_Edges[iBin]; + for (UInt iNx = 0; iNx < Nx; iNx++) { - pow_value = pow(FACTOR * bin_VCenters[iBin][jNy][iNx], n / 3.0); - moment[jNy][iNx] += (log_Bin_Edges[iBin+1] - log_Bin_Edges[iBin]) * pow_value * pdf[iBin][jNy][iNx]; + double pow_value = pow(FACTOR * vCenterRow[iNx], n / 3.0); + momentRow[iNx] += dLogBin * pow_value * pdfRow[iNx]; } } } @@ -1293,10 +1291,7 @@ void Aerosol::addAerosolToPDF( const Aerosol &rhs ) { UInt iNx = 0; UInt jNy = 0; - #pragma omp parallel for default(shared) private(iNx, jNy) \ - reduction(+ \ - : totalnumber_sum) \ - schedule(dynamic, 1) if (!PARALLEL_CASES) + // Do not parallelize: floating point parallel reduction is not deterministic for (jNy = 0; jNy < Ny; jNy++) { for (iNx = 0; iNx < Nx; iNx++) @@ -1416,10 +1411,7 @@ void Aerosol::addAerosolToPDF( const Aerosol &rhs ) { UInt iNx = 0; UInt jNy = 0; - #pragma omp parallel for default(shared) private(iNx, jNy) \ - reduction(+ \ - : totalicemass_sum) \ - schedule(dynamic, 1) if (!PARALLEL_CASES) + // Do not parallelize: floating point parallel reduction is not deterministic for (jNy = 0; jNy < Ny; jNy++) { for (iNx = 0; iNx < Nx; iNx++) @@ -1724,10 +1716,6 @@ void Aerosol::addAerosolToPDF( const Aerosol &rhs ) { double moment = 0.0E+00; - #pragma omp parallel for default(shared) private(iBin) \ - reduction(+ \ - : moment) \ - schedule(dynamic, 1) if (!PARALLEL_CASES) for (iBin = 0; iBin < nBin; iBin++) { // Moment function only used for N = 0, 1, 2, 3 --> eliminate usage of pow() to save time @@ -1762,10 +1750,6 @@ void Aerosol::addAerosolToPDF( const Aerosol &rhs ) { UInt iBin = 0; double moment = 0.0E+00; - #pragma omp parallel for default(shared) private(iBin) \ - reduction(+ \ - : moment) \ - schedule(dynamic, 1) if (!PARALLEL_CASES) for (iBin = 0; iBin < nBin; iBin++) { moment += (log_Bin_Edges[iBin+1] - log_Bin_Edges[iBin]) * pow(bin_Centers[iBin], n) * PDF[iBin]; @@ -1785,10 +1769,6 @@ void Aerosol::addAerosolToPDF( const Aerosol &rhs ) { double moment = 0.0E+00; const double FACTOR = 3.0 / double(4.0 * PI); - #pragma omp parallel for default(shared) private(iBin) \ - reduction(+ \ - : moment) \ - schedule(dynamic, 1) if (!PARALLEL_CASES) for (iBin = 0; iBin < nBin; iBin++){ double pow_value; // Moment function only used for N = 0, 1, 2, 3 --> eliminate usage of pow() to save time @@ -1823,10 +1803,6 @@ void Aerosol::addAerosolToPDF( const Aerosol &rhs ) { double moment = 0.0E+00; const double FACTOR = 3.0 / double(4.0 * PI); - #pragma omp parallel for default(shared) private(iBin) \ - reduction(+ \ - : moment) \ - schedule(dynamic, 1) if (!PARALLEL_CASES) for (iBin = 0; iBin < nBin; iBin++){ moment += (log_Bin_Edges[iBin+1] - log_Bin_Edges[iBin]) * pow(FACTOR * bin_VCenters[iBin][jNy][iNx], n / 3.0) * pdf[iBin][jNy][iNx]; } diff --git a/Code.v05-00/src/Core/LAGRIDPlumeModel.cpp b/Code.v05-00/src/Core/LAGRIDPlumeModel.cpp index f66313656..afd67ca71 100644 --- a/Code.v05-00/src/Core/LAGRIDPlumeModel.cpp +++ b/Code.v05-00/src/Core/LAGRIDPlumeModel.cpp @@ -187,10 +187,9 @@ SimStatus LAGRIDPlumeModel::runFullModel() { // dp/dz = -rho*g = -(n/V)Mg Vector_3D& pdfRef = iceAerosol_.getPDF(); auto pressureEdges = met_.PressEdges(); - double localND; #pragma omp parallel for for (std::size_t j=0; j>> APCEMM OpenMP Num Threads is set to " << input.SIMULATION_OMP_NUM_THREADS << std::endl; if (input.SIMULATION_OMP_NUM_THREADS > 4){ - std::cout << ">>> Hint: APCEMM performance does not scale past 4 threads... ('OpenMP Num Threads' is set to " << input.SIMULATION_OMP_NUM_THREADS << ") <<<" << std::endl; + std::cout << ">>> Hint: APCEMM performance does not scale past 4 threads..." << std::endl; } YAML::Node paramSweepSubmenu = simNode["PARAM SWEEP SUBMENU"]; diff --git a/Code.v05-00/tests/multithreading/README.md b/Code.v05-00/tests/multithreading/README.md new file mode 100644 index 000000000..eb92e3c07 --- /dev/null +++ b/Code.v05-00/tests/multithreading/README.md @@ -0,0 +1,7 @@ +# Multithreading reproducibility test in GitHub Actions CI + +Run ISSL-140 example with 1 thread and 4 threads to check that results are bitwise identical. +APCEMM is run for 3h and results are saved every 30 mins to avoid having too many files to compare. + +3h seems like enough time for floating point difference to propagate sufficiently to lead to a difference +in outputs without making the CI too long. diff --git a/Code.v05-00/tests/multithreading/compare_outputs.py b/Code.v05-00/tests/multithreading/compare_outputs.py new file mode 100644 index 000000000..2f01d599e --- /dev/null +++ b/Code.v05-00/tests/multithreading/compare_outputs.py @@ -0,0 +1,104 @@ +""" +Script to compare the outputs of two APCEMM runs to ensure data variables are +bitwise identical. Uses Python PEP 723 for script dependency declaration, works +nicely with uv. Run this with 'uv run compare_outputs.py dir1/path1/ dir2/path2/ + +Returns 0 if files in directories are identical. +""" +# /// script +# requires-python = ">=3.12" +# dependencies = ["xarray", "netCDF4"] +# /// + +import sys +from pathlib import Path + +import xarray as xr + +ALL_IDENTICAL = 0 +NOT_IDENTICAL = 1 +BAD_USAGE = 2 + + +def compare_identical(path1: Path, path2: Path) -> int: + """ + Compare the ts_aerosol.nc files in two APCEMM output directories. + Use xarray.equals() to ensure that each variable is bitwise identical + without enforcing that attrs must be identical + + Parameters + ---------- + path1 : Path + Path to APCEMM output dir + path2 : Path + Path to APCEMM output dir + + Returns + ------- + int + 0 if all equal + 1 if at least an output is different + 2 bad call to function + """ + + if path1.resolve() == path2.resolve(): + print( + "Do not compare the same output directory (paths are the same)", flush=True + ) + return BAD_USAGE + + # Fetch all ts_aerosol files + files1 = sorted(path1.glob("ts*.nc")) + files2 = sorted(path2.glob("ts*.nc")) + + if len(files1) != len(files2): + print("Not same number of files", flush=True) + return NOT_IDENTICAL + + if len(files1) == 0: + print("Found 0 files to compare", flush=True) + return NOT_IDENTICAL + + print(f"Found {len(files1)} files to compare", flush=True) + + with ( + xr.open_dataset(path1 / "epm-output.nc", decode_times=False) as ds1, + xr.open_dataset(path2 / "epm-output.nc", decode_times=False) as ds2, + ): + if not ds1.equals(ds2): + print("EPM outputs are different", flush=True) + return NOT_IDENTICAL + + all_equal = True + + for f1, f2 in zip(files1, files2, strict=True): + with ( + xr.open_dataset(f1, decode_times=False) as ds1, + xr.open_dataset(f2, decode_times=False) as ds2, + ): + is_equal = ds1.equals(ds2) + all_equal = all_equal and is_equal + + if not is_equal: + print(f"{f1} and {f2} are different!", flush=True) + break + + print(f"{all_equal = }", flush=True) + + if not all_equal: + return NOT_IDENTICAL + + return ALL_IDENTICAL + + +if __name__ == "__main__": + # Check we have both paths as arguments + if len(sys.argv) != 3: + print("Script usage: compare_outputs.py dir1/path1/ dir2/path2/", flush=True) + sys.exit(BAD_USAGE) + + path1 = Path(sys.argv[1]) + path2 = Path(sys.argv[2]) + + # Propagate return code of function to script return code + sys.exit(compare_identical(path1, path2)) diff --git a/Code.v05-00/tests/multithreading/inputs/1_thread.yaml b/Code.v05-00/tests/multithreading/inputs/1_thread.yaml new file mode 100644 index 000000000..b98cacd99 --- /dev/null +++ b/Code.v05-00/tests/multithreading/inputs/1_thread.yaml @@ -0,0 +1,4 @@ +SIMULATION MENU: + OpenMP Num Threads (positive int): 1 + OUTPUT SUBMENU: + Output folder (string): ../APCEMM_out_1_thread/ diff --git a/Code.v05-00/tests/multithreading/inputs/4_threads.yaml b/Code.v05-00/tests/multithreading/inputs/4_threads.yaml new file mode 100644 index 000000000..b7ac0424a --- /dev/null +++ b/Code.v05-00/tests/multithreading/inputs/4_threads.yaml @@ -0,0 +1,4 @@ +SIMULATION MENU: + OpenMP Num Threads (positive int): 4 + OUTPUT SUBMENU: + Output folder (string): ../APCEMM_out_4_threads/ diff --git a/Code.v05-00/tests/multithreading/inputs/base.yaml b/Code.v05-00/tests/multithreading/inputs/base.yaml new file mode 100644 index 000000000..6018fffd5 --- /dev/null +++ b/Code.v05-00/tests/multithreading/inputs/base.yaml @@ -0,0 +1,218 @@ +# Same as ISSL 140 example but override number of threads and save dir with other yaml +SIMULATION MENU: + + #OpenMP Num Threads (positive int): 8 + + PARAM SWEEP SUBMENU: + Parameter sweep (T/F): T + #-OR--------------- + Run Monte Carlo (T/F): F + Num Monte Carlo runs (int): 2 + # Where APCEMM output for this set of runs will go + OUTPUT SUBMENU: + + # Output folder (string): APCEMM_out/ + + Overwrite if folder exists (T/F): T + # FFT options (for spectral solver) + Use threaded FFT (T/F): F + FFTW WISDOM SUBMENU: + Use FFTW WISDOM (T/F): T + Dir w/ write permission (string): ./ + # This mostly contains information on background aerosol concentration; Not too relevant for contrail behavior + Input background condition (string): ../../../../input_data/init.txt + # All parameters here are overwritten in EMISSION INDICES SUBMENU + Input engine emissions (string): ../../../../input_data/ENG_EI.txt + # Ignore/Don't change these, these are deprecated features. + SAVE FORWARD RESULTS SUBMENU: + Save forward results (T/F): F + netCDF filename format (string): APCEMM_Case_* + ADJOINT OPTIMIZATION SUBMENU: + Turn on adjoint optim. (T/F): F + netCDF filename format (string): APCEMM_ADJ_Case_* + BOX MODEL SUBMENU: + Run box model (T/F): F + netCDF filename format (string): APCEMM_BOX_CASE_* + RANDOM NUMBER GENERATION SUBMENU: + Force seed value (T/F): T + Seed value (positive int): 0 + EPM type (original/external/new): original + +# Format of parameter items: +# Param name [unit] (Variable type) +PARAMETER MENU: + # Parameter sweep format : Format is either: x1 x2 x3 or start:increment:end + # : Example: 200 220 240 and 200:20:240 are identical + + # Monte Carlo simulation : min:max + # : Example: 200:240 will generate values for the parameter in between 200 and 240 + + # Maximum simulation time if contrail isn't gone by then: + Plume Process [hr] (double): 3 + + # Temperature, RH, and wind shear can be overwritten if using meteorological input files + METEOROLOGICAL PARAMETERS SUBMENU: + # Pressure altitude at which the contrail is initialized + Pressure [hPa] (double): 265 + Horiz. diff. coeff. [m^2/s] (double): 15.0 + # Can be overwritten if met file is passed + Verti. diff. [m^2/s] (double): 0.15 + Brunt-Vaisala Frequency [s^-1] (double): 0.013 + LOCATION AND TIME SUBMENU: + # Affects for solar zenith angle and photolysis which affect EPM/diurnal variations of temperature + LON [deg] (double): -15 + LAT [deg] (double): 60 + Emission day [1-365] (int): 81 + Emission time [hr] (double) : 8 + BACKGROUND MIXING RATIOS SUBMENU: + # Affects the EPM only + NOx [ppt] (double): 5100 + HNO3 [ppt] (double): 81.5 + O3 [ppb] (double): 100 + CO [ppb] (double): 40 + CH4 [ppm] (double): 1.76 + SO2 [ppt] (double): 7.25 + EMISSION INDICES SUBMENU: + # Engine specific parameters + # Default parameters are estimates for a 737-800 at 35k ft + # Affects EPM mostly: + NOx [g(NO2)/kg_fuel] (double): 10 # EDB for CFM56-5B3 + CO [g/kg_fuel] (double): 1 + UHC [g/kg_fuel] (double): 0.6 + # Affects contrail diffusion model: + SO2 [g/kg_fuel] (double): 1.2 # Assuming 600ppm of Sulfur + SO2 to SO4 conv [%] (double): 2 # AEDT paper, Barrett et al. (2010) + Soot [g/kg_fuel] (double): 0.008 # Assuming EI(nvPM) = 10^14 + Soot Radius [m] (double): 20.0E-09 + # Total fuel flow is then divided per engine + Total fuel flow [kg/s] (double) : 0.7 + Aircraft mass [kg] (double): 1.00e+05 # At beginning of cruise (35k ft), NPSS + Flight speed [m/s] (double): 265.42 # NPSS + Num. of engines [2/4] (int): 2 + Wingspan [m] (double): 34.32 # https://www.skybrary.aero/aircraft/b738 + # Values depend on where we assume condensation/freezing to occur and engine type + Core exit temp. [K] (double): 553.65 # NPSS + Exit bypass area [m^2] (double): 0.9772 + +TRANSPORT MENU: + # Keep on + Turn on Transport (T/F): T + # Outdated, not used (was used by spectral solver) + Fill Negative Values (T/F): T + Transport Timestep [min] (double): 1 + # Keep off: not sure of the effect yet + met updraft is included (if met file input) + PLUME UPDRAFT SUBMENU: + Turn on plume updraft (T/F): F + Updraft timescale [s] (double): 3600 + Updraft veloc. [cm/s] (double): 5 + +# Chemistry component of APCEMM hasn't been touched in a long time; leave off if only interested in contrail simulation +CHEMISTRY MENU: + Turn on Chemistry (T/F): F + Perform hetero. chem. (T/F): F + Chemistry Timestep [min] (double): 10 + Photolysis rates folder (string): /path/to/input/ + +AEROSOL MENU: + # Keep on + Turn on grav. settling (T/F): T + # Keep on + Turn on solid coagulation (T/F): T + # Keep off + Turn on liquid coagulation (T/F): F + Coag. timestep [min] (double): 60 + # Keep on + Turn on ice growth (T/F): T + Ice growth timestep [min] (double): 1 + +# At least one of "Use met. input", "Impose moist layer depth", or "Impose lapse rate" must be true +# Imposing moist layer depth will automatically calculate the lapse rate and override the imposed lapse rate + +# If using met. input: +# Exactly one of "Init temp. from met." and "Impose lapse rate" must be true +# Exactly one of "Init RH from met." and "Impose moist layer depth" must be true +METEOROLOGY MENU: + # --- MET INPUT OPTIONS ---+ + METEOROLOGICAL INPUT SUBMENU: + Use met. input (T/F): T + Met input file path (string): ../../../../examples/issl_rhi140/example_met_file.nc + # Frequency of met data availability + Time series data timestep [hr] (double): 1.0 + # If off, uses the parameter specified in METEOROLOGICAL PARAMETERS SUBMENU + Init temp. from met. (T/F): T + Temp. time series input (T/F): T + # Always interpolates in time, this controls spatial interpolation + Interpolate temp. met. data (T/F): T + # Same options as the ones for Temperature: + Init RH from met. (T/F): T + RH time series input (T/F): T + Interpolate RH met. data (T/F): T + Init wind shear from met. (T/F): T + Wind shear time series input (T/F): T + Interpolate shear met. data (T/F): F + Init vert. veloc. from met. data (T/F): T + Vert. veloc. time series input (T/F): F + Interpolate vert. veloc. met. data (T/F): F + # Option to modify NWP RH data + + # Perturbations to the temperature field can be added to the contrail simulation to account for some effects of + # atmospheric turbulence and gravity waves. + # Every N minutes, a temperature perturbation of e1*e2*T_amp where e1 and e2 are random variables + # uniformly distributed between -1 and 1 generated individually for each grid cell. + # The importance of turbulence and grav. waves can be increased by increasing T_amp + # The relative importance of grav. waves vs. turb. is increased by increasing the timescale + # See Lewellen, Persistent Contrails and Contrail Cirrus (2014) for full details. + TEMPERATURE PERTURBATION SUBMENU: + Enable Temp. Pert. (T/F): F + Temp. Perturb. Amplitude (double): 1.0 + Temp. Perturb. Timescale (min): 10 + +# The only thing that should be changed here is the save frequency +DIAGNOSTIC MENU: + netCDF filename format (string): trac_avg.apcemm.hhmm + # Leave "save species timeseries" off. It will do nothing without also turning chemistry on. + SPECIES TIMESERIES SUBMENU: + Save species timeseries (T/F): F + Inst timeseries file (string): ts_hhmm.nc + #list input: separate by spaces. e.g. 1 2 3 4 5 + Species indices to include (list of ints): 1 + Save frequency [min] (double): 10 + # Index 1 (ice) is the only relevant thing to save + AEROSOL TIMESERIES SUBMENU: + Save aerosol timeseries (T/F): T + Inst timeseries file (string): ts_aerosol_hhmm.nc + #list input: separate by spaces. e.g. 1 2 3 4 5 + Aerosol indices to include (list of ints): 1 + Save frequency [min] (double): 30 + # Keep off if chemistry is also off + PRODUCTION & LOSS SUBMENU: + Turn on P/L diag (T/F): F + Save O3 P/L (T/F): F + +# Sometimes you have to change YLIM_DOWN here if the supersaturated layer is very thick +# because YLIM_DOWN must be larger than the layer thickness. +ADVANCED OPTIONS MENU: + Save gridded particle size distribution (T/F): F # WARNING: If T, it can generate very large output files and slow down the simulation + # Domain is defined as: X [-XLIM_LEFT, XLIM_RIGHT], Y [-YLIM_DOWN, YLIM_UP] + GRID SUBMENU: + NX (positive int): 200 + NY (positive int): 180 + XLIM_RIGHT (positive double): 1.0e+3 + XLIM_LEFT (positive double): 1.0e+3 + YLIM_UP (positive double): 300 + YLIM_DOWN (positive double): 1.5e+3 + INITIAL CONTRAIL SIZE SUBMENU: + #Depth = BaseDepth + DepthScalingFactor * Default_Depth + #Same formula for width + Base Contrail Depth [m] (double): 0.0 + Contrail Depth Scaling Factor [-] (double): 1.0 + Base Contrail Width [m] (double): 0.0 + Contrail Width Scaling Factor [-] (double): 1.0 + Ambient Lapse Rate [K/km] (double): -3.0 + Tropopause Pressure [Pa] (double): 2.0e+4 + EARLY PLUME SUBMENU: + Reference ice crystal count [#/m] (double): 3.38e12 + Reference wingspan [m] (double): 60.3 + # The values below can be overriden if required. + Override post-jet ice crystal count (T/F): F + Post-jet ice crystal count [#/m] (double): 1e15