Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4b186f1
plumber2 scripts: Find csv file from anywhere.
samsrabin Jun 18, 2025
0bc1cce
plumber2_surf_wrapper: Call subset_data directly.
samsrabin Jun 18, 2025
55b97e6
plumber2_surf_wrapper: Specify --lon-type 180.
samsrabin Jun 18, 2025
44d461d
plumber2_surf_wrapper: Respect --verbose.
samsrabin Jun 18, 2025
6d96107
plumber2_surf_wrapper: Stop on errors.
samsrabin Jun 18, 2025
8781571
plumber2_surf_wrapper: Add optional --plumber2-sites-csv argument.
samsrabin Jun 18, 2025
6943fe4
plumber2_surf_wrapper: Fix handling of one-PFT sites.
samsrabin Jun 18, 2025
d2c7118
Add Python system test for plumber2_surf_wrapper.
samsrabin Jun 18, 2025
132d6f8
Remove an unused import.
samsrabin Jun 19, 2025
194ce43
Test plumber2_surf_wrapper invalid-PFT error.
samsrabin Jun 19, 2025
b9ed339
Replace plumber2_surf_wrapper args test with useful ones. Failing.
samsrabin Jun 19, 2025
3019c4e
plumber2_surf_wrapper: Respect user not saying --16pft.
samsrabin Jun 19, 2025
4707843
plumber2_surf_wrapper: Test full run with --16pft.
samsrabin Jun 19, 2025
30f4558
plumber2_surf_wrapper: Add --overwrite option.
samsrabin Jun 19, 2025
1c5c1e7
plumber2_surf_wrapper: Improve execute() comments.
samsrabin Jun 19, 2025
63d61f4
plumber2_surf_wrapper: Switch --16pft to --78pft to preserve previous…
samsrabin Jun 19, 2025
d491e64
Merge branch 'subset_data-lon-fixes' into plumber2_surf_wrapper-fixes
samsrabin Jun 20, 2025
56a36f1
plumber2 scripts: Remove addition of CTSM python dir to path.
samsrabin Jun 20, 2025
cf86bfc
Simplify PLUMBER2_SITES_CSV path.
samsrabin Jun 20, 2025
602fe61
Move Python constants for PFT numbers to pft_utils.py.
samsrabin Jun 20, 2025
8ca6e45
Rename and replace some PFT number constants for clarity.
samsrabin Jun 20, 2025
f6a9afc
Use MAX_PFT_ variables in more places.
samsrabin Jun 20, 2025
ddca30f
plumber2_surf_wrapper: Avoid mentioning 78.
samsrabin Jun 20, 2025
b7f7af3
plumber2_surf_wrapper: Better is_valid_pft() check.
samsrabin Jun 20, 2025
75db098
Reformat with black.
samsrabin Jun 20, 2025
0eb376d
Add previous commit to .git-blame-ignore-revs.
samsrabin Jun 20, 2025
c325805
Resolve pylint complaint.
samsrabin Jun 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,4 @@ cdf40d265cc82775607a1bf25f5f527bacc97405
3dd489af7ebe06566e2c6a1c7ade18550f1eb4ba
742cfa606039ab89602fde5fef46458516f56fd4
4ad46f46de7dde753b4653c15f05326f55116b73
75db098206b064b8b7b2a0604d3f0bf8fdb950cc
21 changes: 21 additions & 0 deletions python/ctsm/pft_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""
Constants and functions relating to PFTs
"""

MIN_PFT = 0 # bare ground
MIN_NAT_PFT = 1 # minimum natural pft (not including bare ground)
MAX_NAT_PFT = 14 # maximum natural pft
MAX_PFT_GENERICCROPS = 16 # for runs with generic crops
MAX_PFT_MANAGEDCROPS = 78 # for runs with explicit crops


def is_valid_pft(pft_num, managed_crops):
"""
Given a number, check whether it represents a valid PFT (bare ground OK)
"""
if managed_crops:
max_allowed_pft = MAX_PFT_MANAGEDCROPS
else:
max_allowed_pft = MAX_PFT_GENERICCROPS

return MIN_PFT <= pft_num <= max_allowed_pft
21 changes: 21 additions & 0 deletions python/ctsm/site_and_regional/plumber2_shared.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""
Things shared between plumber2 scripts
"""

import os
import pandas as pd
from ctsm.path_utils import path_to_ctsm_root

PLUMBER2_SITES_CSV = os.path.join(
path_to_ctsm_root(),
"tools",
"site_and_regional",
"PLUMBER2_sites.csv",
)


def read_plumber2_sites_csv(file=PLUMBER2_SITES_CSV):
"""
Read PLUMBER2_sites.csv using pandas
"""
return pd.read_csv(file, skiprows=5)
187 changes: 100 additions & 87 deletions python/ctsm/site_and_regional/plumber2_surf_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,18 @@

import argparse
import logging
import os
import subprocess
import sys
import tqdm

import pandas as pd
# pylint:disable=wrong-import-position
from ctsm.site_and_regional.plumber2_shared import PLUMBER2_SITES_CSV, read_plumber2_sites_csv
from ctsm import subset_data
from ctsm.pft_utils import MAX_PFT_MANAGEDCROPS, is_valid_pft


def get_parser():
def get_args():
"""
Get parser object for this script.
Get arguments for this script.
"""
parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
Expand All @@ -45,137 +47,148 @@ def get_parser():
help="Verbose mode will print more information. ",
action="store_true",
dest="verbose",
default=False,
)

parser.add_argument(
"--16pft",
help="Create and/or modify 16-PFT surface datasets (e.g. for a FATES run) ",
"--crop",
help=f"Create and/or modify {MAX_PFT_MANAGEDCROPS}-PFT "
"surface datasets (e.g. for a non-FATES run)",
action="store_true",
dest="pft_16",
default=True,
dest="use_managed_crops",
)

return parser
parser.add_argument(
"--overwrite",
help="Overwrite any existing files",
action="store_true",
)

parser.add_argument(
"--plumber2-sites-csv",
help=f"Comma-separated value (CSV) file with Plumber2 sites. Default: {PLUMBER2_SITES_CSV}",
default=PLUMBER2_SITES_CSV,
)

return parser.parse_args()


def execute(command):
"""
Function for running a command on shell.
Runs subset_data with given arguments.
Args:
command (str):
command that we want to run.
command (list):
list of args for command that we want to run.
Raises:
Error with the return code from shell.
Whatever error subset_data gives, if any.
"""
print("\n", " >> ", *command, "\n")

try:
subprocess.check_call(command, stdout=open(os.devnull, "w"), stderr=subprocess.STDOUT)

except subprocess.CalledProcessError as err:
# raise RuntimeError("command '{}' return with error
# (code {}): {}".format(e.cmd, e.returncode, e.output))
# print (e.ouput)
print(err)
sys.argv = command
subset_data.main()


def main():
"""
Read plumber2_sites from csv, iterate through sites, and add dominant PFT
"""

args = get_parser().parse_args()
args = get_args()

if args.verbose:
logging.basicConfig(level=logging.DEBUG)

plumber2_sites = pd.read_csv("PLUMBER2_sites.csv", skiprows=4)
plumber2_sites = read_plumber2_sites_csv(args.plumber2_sites_csv)

for _, row in tqdm.tqdm(plumber2_sites.iterrows()):
lat = row["Lat"]
lon = row["Lon"]
site = row["Site"]

clmsite = "1x1_PLUMBER2_" + site
print("Now processing site :", site)

# Set up part of subset_data command that is shared among all options
subset_command = [
"./subset_data",
"point",
"--lat",
str(lat),
"--lon",
str(lon),
"--site",
clmsite,
"--create-surface",
"--uniform-snowpack",
"--cap-saturation",
"--lon-type",
"180",
]

# Read info for first PFT
pft1 = row["pft1"]
if not is_valid_pft(pft1, args.use_managed_crops):
raise RuntimeError(f"pft1 must be a valid PFT; got {pft1}")
pctpft1 = row["pft1-%"]
cth1 = row["pft1-cth"]
cbh1 = row["pft1-cbh"]
pft2 = row["pft2"]
pctpft2 = row["pft2-%"]
cth2 = row["pft2-cth"]
cbh2 = row["pft2-cbh"]
# overwrite missing values from .csv file
if pft1 == -999:
pft1 = 0
pctpft1 = 0
cth1 = 0
cbh1 = 0
if pft2 == -999:
pft2 = 0
pctpft2 = 0
cth2 = 0
cbh2 = 0
clmsite = "1x1_PLUMBER2_" + site
print("Now processing site :", site)

if args.pft_16:
# use surface dataset with 16 pfts, but overwrite to 100% 1 dominant PFT
# don't set crop flag
# set dominant pft
subset_command = [
"./subset_data",
"point",
"--lat",
str(lat),
"--lon",
str(lon),
"--site",
clmsite,
# Read info for second PFT, if a valid one is given in the .csv file
pft2 = row["pft2"]
if is_valid_pft(pft2, args.use_managed_crops):
pctpft2 = row["pft2-%"]
cth2 = row["pft2-cth"]
cbh2 = row["pft2-cbh"]

# Set dominant PFT(s)
if is_valid_pft(pft2, args.use_managed_crops):
subset_command += [
"--dompft",
str(pft1),
str(pft2),
"--pctpft",
str(pctpft1),
str(pctpft2),
"--cth",
str(cth1),
str(cth2),
"--cbh",
str(cbh1),
str(cbh2),
"--create-surface",
"--uniform-snowpack",
"--cap-saturation",
"--verbose",
"--overwrite",
]
else:
# use surface dataset with 78 pfts, and overwrite to 100% 1 dominant PFT
# NOTE: FATES will currently not run with a 78-PFT surface dataset
# set crop flag
# set dominant pft
subset_command = [
"./subset_data",
"point",
"--lat",
str(lat),
"--lon",
str(lon),
"--site",
clmsite,
"--crop",
subset_command += [
"--dompft",
str(pft1),
str(pft2),
"--pctpft",
str(pctpft1),
str(pctpft2),
"--create-surface",
"--uniform-snowpack",
"--cap-saturation",
"--verbose",
"--overwrite",
]

if not args.use_managed_crops:
# use surface dataset with 78 pfts, but overwrite to 100% 1 dominant PFT
# don't set crop flag
# set canopy top and bottom heights
if is_valid_pft(pft2, args.use_managed_crops):
subset_command += [
"--cth",
str(cth1),
str(cth2),
"--cbh",
str(cbh1),
str(cbh2),
]
else:
subset_command += [
"--cth",
str(cth1),
"--cbh",
str(cbh1),
]
else:
# use surface dataset with 78 pfts, and overwrite to 100% 1 dominant PFT
# NOTE: FATES will currently not run with a 78-PFT surface dataset
# set crop flag
subset_command += ["--crop"]
# don't set canopy top and bottom heights

if args.verbose:
subset_command += ["--verbose"]
if args.overwrite:
subset_command += ["--overwrite"]

execute(subset_command)


Expand Down
5 changes: 3 additions & 2 deletions python/ctsm/site_and_regional/plumber2_usermods.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
import os
import tqdm

import pandas as pd
# pylint:disable=wrong-import-position
from ctsm.site_and_regional.plumber2_shared import read_plumber2_sites_csv


# Big ugly function to create usermod_dirs for each site
Expand Down Expand Up @@ -155,7 +156,7 @@ def main():
"""

# For now we can just run the 'main' program as a loop
plumber2_sites = pd.read_csv("PLUMBER2_sites.csv", skiprows=4)
plumber2_sites = read_plumber2_sites_csv()

for _, row in tqdm.tqdm(plumber2_sites.iterrows()):
lat = row["Lat"]
Expand Down
Loading
Loading