Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
603 changes: 452 additions & 151 deletions pycoupler/config.py

Large diffs are not rendered by default.

1,182 changes: 1,182 additions & 0 deletions pycoupler/countries.json

Large diffs are not rendered by default.

122 changes: 59 additions & 63 deletions pycoupler/coupler.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import pandas as pd
import xarray as xr

from subprocess import run
from enum import Enum

from pycoupler.config import read_config
Expand All @@ -24,6 +23,7 @@
read_header,
)
from pycoupler.utils import get_countries
from pathlib import Path


# class for testing purposes
Expand Down Expand Up @@ -277,7 +277,7 @@ class LPJmLCoupler:
Port number for socket connection.
"""

def __init__(self, config_file, version=3, host="localhost", port=2224):
def __init__(self, config_file, version=3, host="0.0.0.0", port=2224):
"""Constructor method"""

# read configuration file
Expand All @@ -286,7 +286,7 @@ def __init__(self, config_file, version=3, host="localhost", port=2224):
if hasattr(self._config, "coupled_host") and hasattr(
self._config, "coupled_port"
):
if host != "localhost" or port != 2224:
if host != "0.0.0.0" or port != 2224:
warnings.warn(
"Host and port are set in configuration file. "
"Provided host and port are ignored."
Expand Down Expand Up @@ -444,7 +444,7 @@ def get_historic_years(self, match_period=True):
if match_period and start_year >= end_year:
raise ValueError(
f"No historic years available. Simulated year {start_year} "
f"is greater than coupled year {end_year}."
f"is greater than or equal to coupled year {end_year}."
)
current_year = start_year
while current_year < end_year:
Expand Down Expand Up @@ -504,38 +504,46 @@ def code_to_name(self, to_iso_alpha_3=False):
If True, convert country indices to ISO alpha-3 codes, else return
country names
"""
for static_output in self._static_ids.values():
# Only convert these particular static outputs
outputs_to_change = set(["country", "region"])

if static_output not in ["country", "region"]:
continue
for static_output in outputs_to_change & set(self._static_ids.values()):

getattr(self, static_output).values = getattr(
self, static_output
).values.astype(str)
name_dict = {
str(reg["id"]): reg["name"]
for reg in self._config.to_dict()[f"{static_output}par"]
}
if static_output == "country" and to_iso_alpha_3:
country_dict = get_countries()
name_dict = {
idx: country_dict[reg]["code"] for idx, reg in name_dict.items()
}
getattr(self, f"{static_output}").attrs[
"long_name"
] = f"{static_output} iso alpha-3 code"
else:
getattr(self, f"{static_output}").attrs[
"long_name"
] = f"{static_output} name"
string_values = getattr(self, static_output).values.astype(str)

def replace_values(x):
return name_dict[x] if x in name_dict else x
if static_output == "country":
country_dict = get_countries()
getattr(self, static_output).values = np.apply_along_axis(
lambda values: [
(
country_dict[id]["alpha-3"]
if to_iso_alpha_3
else country_dict[id]["name"] if id in country_dict else id
)
for id in values
],
0,
string_values,
)
elif static_output == "region":
config = self._config.to_dict()
if "regionpar" in config:
name_dict = {
str(reg["id"]): reg["name"] for reg in config["regionpar"]
}
print(string_values)
getattr(self, static_output).values = np.apply_along_axis(
lambda values: [
name_dict[id] if id in name_dict else id for id in values
],
0,
string_values,
)

getattr(self, f"{static_output}").values = np.vectorize(
replace_values
)( # noqa
getattr(self, f"{static_output}").values
getattr(self, static_output).attrs["long_name"] = (
f"{static_output} iso alpha-3 code"
if to_iso_alpha_3 and static_output == "country"
else f"{static_output} name"
)

def read_historic_output(self, to_xarray=True):
Expand Down Expand Up @@ -878,24 +886,17 @@ def _copy_input(self, start_year, end_year):

# iterate over each inputs to be send via sockets (get initial values)
for key in sock_inputs:
# check if working on the cluster (workaround by Ciaron)
# (might be adjusted to the new cluster coming soon ...)
if self.config.inpath and (not sock_inputs[key]["name"].startswith("/")):
sock_inputs[key][
"name"
] = f"{self.config.inpath}/{sock_inputs[key]['name']}"
# get input file name
file_name_clm = sock_inputs[key]["name"].split("/")[-1]
# name tmp file after original name (even though could be random)
file_name_tmp = f"{file_name_clm.split('.')[0]}_tmp.clm"
input_datafile = Path(self.config.get_datafile_from_input(sock_inputs[key]))

if not hasattr(sys, "_called_from_test"):
# read meta data of input file
meta_data = read_header(sock_inputs[key]["name"])
else:
file_name_tmp = f"{input_datafile.stem}_tmp{input_datafile.suffix}"

if sock_inputs[key].get("fmt", "") == "meta":
meta_data = read_meta(
f"{os.environ['TEST_PATH']}/data/input/{key}.nc.json"
self.config.get_input_filepath(sock_inputs[key]["name"])
)
else:
# read meta data of input file
meta_data = read_header(str(input_datafile))

# determine start cut off and end cut off year
if meta_data.firstyear > end_year:
Expand All @@ -920,26 +921,24 @@ def _copy_input(self, start_year, end_year):
cut_start_year = start_year
cut_end = cut_end_year = min(meta_data.lastyear, end_year)

cut_clm_start = [
f"{self._config.model_path}/bin/cutclm",
cut_clm_start_args = [
str(cut_start_year),
sock_inputs[key]["name"],
str(input_datafile),
f"{temp_dir}/1_{file_name_tmp}",
]
if not hasattr(sys, "_called_from_test"):
run(cut_clm_start, stdout=open(os.devnull, "wb"))
self.config.run_model_bin("cutclm", *cut_clm_start_args)

# predefine cut clm command for reusage
# cannot deal with overwriting a temp file with same name
cut_clm_end = [
f"{self._config.model_path}/bin/cutclm",
cut_clm_end_args = [
"-end",
str(cut_end_year),
f"{temp_dir}/1_{file_name_tmp}",
f"{temp_dir}/2_{file_name_tmp}",
]
if not hasattr(sys, "_called_from_test"):
run(cut_clm_end, stdout=open(os.devnull, "wb"))
self.config.run_model_bin("cutclm", *cut_clm_end_args)

# a flag for multi (categorical) band input - if true, set
# "-landuse"
Expand All @@ -955,13 +954,10 @@ def _copy_input(self, start_year, end_year):
is_int = None

# default grid file (only valid for 0.5 degree inputs)
if self.config.input.coord.name.startswith("/"):
grid_file = self.config.input.coord.name
else:
grid_file = f"{self.config.inpath}/{self.config.input.coord.name}"
grid_file = self.config.get_datafile_from_input(self.config.input.coord)

# convert clm input to netcdf files
conversion_cmd = [
f"{self._config.model_path}/bin/clm2cdf",
conversion_cmd_args = [
is_int,
is_multiband,
key,
Expand All @@ -970,11 +966,11 @@ def _copy_input(self, start_year, end_year):
f"{input_path}/{key}.nc",
]

if None in conversion_cmd:
conversion_cmd.remove(None)
if None in conversion_cmd_args:
conversion_cmd_args.remove(None)

if not hasattr(sys, "_called_from_test"):
run(conversion_cmd)
self.config.run_model_bin("clm2cdf", *conversion_cmd_args)
else:
return "tested"
# remove the temporary clm (binary) files, 1_* is not created in
Expand Down
2 changes: 1 addition & 1 deletion pycoupler/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,7 @@ def read_header(filename, to_dict=False, force_version=None, verbose=False):
break

if not headername.startswith("LPJ"):
raise ValueError(f"Invalid header name {headername}")
raise ValueError(f"Invalid header name '{headername}' in file '{filename}'")
if headername == "LPJRESTART":
raise ValueError(
"LPJRESTART header detected. This function does not support"
Expand Down
Loading
Loading