simplace module¶
simpest.models.simplace
¶
SimplaceConfig
dataclass
¶
Configuration for Simplace runs.
Attributes:
| Name | Type | Description |
|---|---|---|
install_dir |
str |
Path to Simplace installation directory. |
work_dir |
str |
Path to Simplace working directory. |
output_dir |
str |
Path to Simplace output directory. |
solution_path |
str |
Path to Simplace solution XML file. |
project_path |
str |
Path to Simplace project XML file. |
Source code in simpest/models/simplace.py
@dataclass(frozen=True)
class SimplaceConfig:
"""
Configuration for Simplace runs.
Attributes:
install_dir (str): Path to Simplace installation directory.
work_dir (str): Path to Simplace working directory.
output_dir (str): Path to Simplace output directory.
solution_path (str): Path to Simplace solution XML file.
project_path (str): Path to Simplace project XML file.
"""
install_dir: str = "C:/ParamVC/Research/simplace+/simplace_portable/workspace/"
work_dir: str = "C:/ParamVC/Research/simplace+/simplace_portable/workspace/simplace_run/simulation/"
output_dir: str = "C:/ParamVC/Research/simplace+/simplace_out/"
solution_path: str = "SimulationExperimentTemplate/solution/Lintul5_indiana.sol.xml"
project_path: str = "SimulationExperimentTemplate/project/Lintul5All_indiana.proj.xml"
build_management(output_root, project_row, crop='wheat', variety='generic', yearly_sowing_doy=None)
¶
Build and save a management CSV file for FraNchEstYN.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
output_root |
Path |
Output root directory. |
required |
project_row |
dict |
Project row dictionary. |
required |
crop |
str |
Crop name. Defaults to "wheat". |
'wheat' |
variety |
str |
Variety name. Defaults to "generic". |
'generic' |
yearly_sowing_doy |
dict[int, int] | None |
Mapping of simulation year to sowing DOY. If omitted, a single "All" row is written to preserve backward compatibility. |
None |
Returns:
| Type | Description |
|---|---|
Path |
Path to the management CSV file. |
Source code in simpest/models/simplace.py
def build_management(
output_root: Path,
project_row: dict,
crop: str = "wheat",
variety: str = "generic",
yearly_sowing_doy: Optional[dict[int, int]] = None,
) -> Path:
"""
Build and save a management CSV file for FraNchEstYN.
Args:
output_root (Path): Output root directory.
project_row (dict): Project row dictionary.
crop (str, optional): Crop name. Defaults to "wheat".
variety (str, optional): Variety name. Defaults to "generic".
yearly_sowing_doy (dict[int, int] | None, optional): Mapping of
simulation year to sowing DOY. If omitted, a single "All" row
is written to preserve backward compatibility.
Returns:
Path: Path to the management CSV file.
"""
sowing_doy = max(1, project_row["idem"] - 7)
if yearly_sowing_doy is None:
candidate = project_row.get("yearly_sowing_doy") if isinstance(project_row, dict) else None
if isinstance(candidate, dict) and candidate:
yearly_sowing_doy = {
int(year): int(doy)
for year, doy in candidate.items()
}
management_dst = output_root / "SimulationExperimentTemplate" / "management_franchestyn.csv"
with management_dst.open("w", newline="", encoding="utf-8") as f_out:
writer = csv.DictWriter(
f_out,
fieldnames=["site", "crop", "variety", "year", "sowingDOY"],
delimiter=",",
)
writer.writeheader()
if yearly_sowing_doy:
for year in sorted(yearly_sowing_doy):
writer.writerow(
{
"site": project_row["location"],
"crop": crop,
"variety": variety,
"year": year,
"sowingDOY": max(1, int(yearly_sowing_doy[year])),
}
)
else:
writer.writerow(
{
"site": project_row["location"],
"crop": crop,
"variety": variety,
"year": "All",
"sowingDOY": sowing_doy,
}
)
return management_dst
convert_weather(work_root, output_root, location)
¶
Convert Simplace weather file to FraNchEstYN-compatible CSV format.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
work_root |
Path |
Simplace workspace root directory. |
required |
output_root |
Path |
Output root directory. |
required |
location |
str |
Location name (used for weather file). |
required |
Returns:
| Type | Description |
|---|---|
Path |
Path to the converted weather CSV file. |
Source code in simpest/models/simplace.py
def convert_weather(work_root: Path, output_root: Path, location: str) -> Path:
"""
Convert Simplace weather file to FraNchEstYN-compatible CSV format.
Args:
work_root (Path): Simplace workspace root directory.
output_root (Path): Output root directory.
location (str): Location name (used for weather file).
Returns:
Path: Path to the converted weather CSV file.
"""
weather_src = work_root / "SimulationExperimentTemplate" / "data" / "weather" / f"{location}.txt"
weather_dst = output_root / "SimulationExperimentTemplate" / "weather_franchestyn.csv"
with weather_src.open(newline="", encoding="utf-8-sig") as f_in, weather_dst.open(
"w", newline="", encoding="utf-8"
) as f_out:
reader = csv.DictReader(f_in, delimiter="\t")
reader.fieldnames = [h.lstrip("\ufeff").strip() for h in reader.fieldnames]
writer = csv.DictWriter(
f_out,
fieldnames=["site", "year", "month", "day", "tx", "tn", "p", "rad", "vp", "rhx", "rhn"],
delimiter=",",
)
writer.writeheader()
for row in reader:
d = datetime.strptime(row["CURRENTDAY"], "%d.%m.%Y")
tmax = float(row["Tmax"])
tmin = float(row["Tmin"])
vp = float(row["VapourPressure"])
es_tmax = _saturation_vapor_pressure(tmax)
es_tmin = _saturation_vapor_pressure(tmin)
rhx = min(100.0, max(0.0, 100.0 * vp / es_tmin)) if es_tmin > 0 else 0.0
rhn = min(100.0, max(0.0, 100.0 * vp / es_tmax)) if es_tmax > 0 else 0.0
writer.writerow(
{
"site": location,
"year": d.year,
"month": d.month,
"day": d.day,
"tx": tmax,
"tn": tmin,
"p": row["Precipitation"],
"rad": float(row["Irradiation"]) / 1000.0,
"vp": vp,
"rhx": rhx,
"rhn": rhn,
}
)
return weather_dst
export_crop_model_data(output_root, project_row)
¶
Export crop model data from Simplace daily output to a CSV file for FraNchEstYN.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
output_root |
Path |
Output root directory. |
required |
project_row |
dict |
Project row dictionary. |
required |
Returns:
| Type | Description |
|---|---|
Path |
Path to the exported crop model CSV file. |
Source code in simpest/models/simplace.py
def export_crop_model_data(output_root: Path, project_row: dict) -> Path:
"""
Export crop model data from Simplace daily output to a CSV file for FraNchEstYN.
Args:
output_root (Path): Output root directory.
project_row (dict): Project row dictionary.
Returns:
Path: Path to the exported crop model CSV file.
"""
src = output_root / "SimulationExperimentTemplate" / f"{project_row['location']}{project_row['iopt']}_daily.csv"
dst = output_root / "SimulationExperimentTemplate" / "cropModel_data.csv"
with src.open(newline="", encoding="utf-8") as f_in, dst.open("w", newline="", encoding="utf-8") as f_out:
reader = csv.DictReader(f_in, delimiter=";")
writer = csv.DictWriter(
f_out,
fieldnames=["year", "doy", "agb", "yield", "fint", "lai", "gdd"],
delimiter=",",
)
writer.writeheader()
for row in reader:
if all(float(row[name]) == 0.0 for name in ("TAGB", "WSO", "FINT", "LAI")):
continue
d = datetime.strptime(row["CURRENT.DATE"], "%d.%m.%Y")
writer.writerow(
{
"year": d.year,
"doy": d.timetuple().tm_yday,
"agb": float(row["TAGB"]) * 10,
"yield": float(row["WSO"]) * 10,
"fint": row["FINT"],
"lai": row["LAI"],
"gdd": row["TSUM"],
}
)
return dst
extract_yearly_sowing_doy(work_root, project_row, start_year, end_year)
¶
Extract per-year sowing DOY from projectdata CSV files.
The function prefers explicit ISOW values when present. If no per-year
values are available, it falls back to a constant sowing DOY derived from
IDEM - 7 to preserve current behavior.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
work_root |
Path |
Simplace workspace root. |
required |
project_row |
dict |
Selected project row metadata. |
required |
start_year |
int |
Start year (inclusive). |
required |
end_year |
int |
End year (inclusive). |
required |
Returns:
| Type | Description |
|---|---|
dict[int, int] |
Mapping year -> sowing DOY. |
Source code in simpest/models/simplace.py
def extract_yearly_sowing_doy(
work_root: Path,
project_row: dict,
start_year: int,
end_year: int,
) -> dict[int, int]:
"""
Extract per-year sowing DOY from projectdata CSV files.
The function prefers explicit `ISOW` values when present. If no per-year
values are available, it falls back to a constant sowing DOY derived from
`IDEM - 7` to preserve current behavior.
Args:
work_root (Path): Simplace workspace root.
project_row (dict): Selected project row metadata.
start_year (int): Start year (inclusive).
end_year (int): End year (inclusive).
Returns:
dict[int, int]: Mapping year -> sowing DOY.
"""
projectdata_dir = work_root / "SimulationExperimentTemplate" / "data" / "projectdata"
if not projectdata_dir.exists():
return {}
location = str(project_row.get("location", "")).strip().lower()
iopt = _to_int(project_row.get("iopt"))
idem = _to_int(project_row.get("idem"))
default_sowing_doy = max(1, (idem or 8) - 7)
rows: list[dict] = []
for project_csv in sorted(projectdata_dir.glob("*.csv")):
with project_csv.open(newline="", encoding="utf-8-sig") as f_proj:
reader = csv.DictReader(f_proj, delimiter=";")
if not reader.fieldnames:
continue
name_to_col = {name.strip().lower(): name for name in reader.fieldnames}
col_location = name_to_col.get("location")
col_iopt = name_to_col.get("iopt")
col_start = name_to_col.get("startdate")
col_end = name_to_col.get("enddate")
col_isow = name_to_col.get("isow")
col_idem = name_to_col.get("idem")
for raw in reader:
row_location = str(raw.get(col_location, "")).strip().lower() if col_location else ""
if location and row_location and row_location != location:
continue
row_iopt = _to_int(raw.get(col_iopt)) if col_iopt else None
if iopt is not None and row_iopt is not None and row_iopt != iopt:
continue
start_dt = _parse_project_date(raw.get(col_start, "")) if col_start else None
end_dt = _parse_project_date(raw.get(col_end, "")) if col_end else None
if start_dt is None and end_dt is None:
continue
year = start_dt.year if start_dt is not None else end_dt.year
if year < start_year or year > end_year:
continue
sow_doy = _to_int(raw.get(col_isow)) if col_isow else None
if sow_doy is None:
row_idem = _to_int(raw.get(col_idem)) if col_idem else idem
sow_doy = max(1, (row_idem or 8) - 7)
rows.append(
{
"year": year,
"sowing_doy": sow_doy,
"span_days": ((end_dt - start_dt).days if start_dt and end_dt else 999999),
}
)
# Prefer the most specific row per year (shortest start-end span).
yearly_sowing: dict[int, int] = {}
for row in sorted(rows, key=lambda x: (x["year"], x["span_days"])):
yearly_sowing[row["year"]] = row["sowing_doy"]
if yearly_sowing:
return yearly_sowing
# Backward-compatible fallback: constant sowing day across all years.
return {year: default_sowing_doy for year in range(start_year, end_year + 1)}
get_project_row(work_root, selected_line)
¶
Get a row from the project CSV file by line index.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
work_root |
Path |
Root directory for Simplace workspace. |
required |
selected_line |
int |
Line number (1-based) to select from the project CSV. |
required |
Returns:
| Type | Description |
|---|---|
dict |
Dictionary with project row data. |
Source code in simpest/models/simplace.py
def get_project_row(work_root: Path, selected_line: int) -> dict:
"""
Get a row from the project CSV file by line index.
Args:
work_root (Path): Root directory for Simplace workspace.
selected_line (int): Line number (1-based) to select from the project CSV.
Returns:
dict: Dictionary with project row data.
"""
project_csv = (
work_root
/ "SimulationExperimentTemplate"
/ "data"
/ "projectdata"
/ "lintul5all_indiana.csv"
)
with project_csv.open(newline="", encoding="utf-8-sig") as f_proj:
proj_reader = csv.DictReader(f_proj, delimiter=";")
project_rows = list(proj_reader)
row = project_rows[selected_line - 1]
key_map = {k.strip().lower(): k for k in row.keys()}
def field(*aliases: str, default: str = "") -> str:
for alias in aliases:
key = key_map.get(alias.lower())
if key is not None:
return str(row.get(key, default)).strip()
return default
project_row = {
"projectid": field("projectid"),
"simulationid": field("simulationid"),
"startdate": field("startdate"),
"enddate": field("enddate"),
"location": field("location"),
"iopt": field("iopt"),
"idem": _to_int(field("idem")) or 0,
"irri": field("irri"),
}
start_dt = _parse_project_date(project_row["startdate"])
end_dt = _parse_project_date(project_row["enddate"])
if start_dt and end_dt:
project_row["yearly_sowing_doy"] = extract_yearly_sowing_doy(
work_root,
project_row,
start_dt.year,
end_dt.year,
)
return project_row
get_simplace_directories(shell)
¶
Wrapper for simplace.getSimplaceDirectories to avoid exposing simplace in notebooks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shell |
Simplace shell instance. |
required |
Returns:
| Type | Description |
|---|---|
dict |
Simplace directories information. |
Source code in simpest/models/simplace.py
def get_simplace_directories(shell):
"""
Wrapper for simplace.getSimplaceDirectories to avoid exposing simplace in notebooks.
Args:
shell: Simplace shell instance.
Returns:
dict: Simplace directories information.
"""
return simplace.getSimplaceDirectories(shell)
init_simplace(config)
¶
Initialize and return a Simplace instance using the given configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config |
SimplaceConfig |
Simplace configuration object. |
required |
Returns:
| Type | Description |
|---|---|
Simplace instance. |
Source code in simpest/models/simplace.py
def init_simplace(config: SimplaceConfig):
"""
Initialize and return a Simplace instance using the given configuration.
Args:
config (SimplaceConfig): Simplace configuration object.
Returns:
Simplace instance.
"""
global _SIMPLACE_INSTANCE
if _SIMPLACE_INSTANCE is not None and jpype.isJVMStarted():
return _SIMPLACE_INSTANCE
if jpype.isJVMStarted():
Wrapper = jpype.JClass('net.simplace.sim.wrapper.SimplaceWrapper')
_SIMPLACE_INSTANCE = Wrapper(config.work_dir, config.output_dir, None, None)
return _SIMPLACE_INSTANCE
_SIMPLACE_INSTANCE = simplace.initSimplace(
installDir=config.install_dir,
workDir=config.work_dir,
outputDir=config.output_dir,
)
return _SIMPLACE_INSTANCE
merge_simplace_and_franchestyn(output_root, project_row, franchestyn_df, out_name='merged_simulation_data.csv')
¶
Merge Simplace and FraNchEstYN daily outputs into a single CSV file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
output_root |
Path |
Output root directory. |
required |
project_row |
dict |
Project row dictionary. |
required |
franchestyn_df |
pd.DataFrame |
FraNchEstYN daily output DataFrame. |
required |
out_name |
str |
Output filename. Defaults to "merged_simulation_data.csv". |
'merged_simulation_data.csv' |
Returns:
| Type | Description |
|---|---|
Path |
Path to the merged CSV file. |
Source code in simpest/models/simplace.py
def merge_simplace_and_franchestyn(
output_root: Path,
project_row: dict,
franchestyn_df: pd.DataFrame,
out_name: str = "merged_simulation_data.csv",
) -> Path:
"""
Merge Simplace and FraNchEstYN daily outputs into a single CSV file.
Args:
output_root (Path): Output root directory.
project_row (dict): Project row dictionary.
franchestyn_df (pd.DataFrame): FraNchEstYN daily output DataFrame.
out_name (str, optional): Output filename. Defaults to "merged_simulation_data.csv".
Returns:
Path: Path to the merged CSV file.
"""
simplace_daily_path = output_root / "SimulationExperimentTemplate" / f"{project_row['location']}{project_row['iopt']}_daily.csv"
simplace_df = pd.read_csv(simplace_daily_path, sep=";")
simplace_df.columns = simplace_df.columns.str.strip()
franchestyn_df.columns = franchestyn_df.columns.str.strip()
simplace_df["date"] = pd.to_datetime(simplace_df["CURRENT.DATE"], format="%d.%m.%Y")
franchestyn_df["date"] = pd.to_datetime(franchestyn_df["Date"], dayfirst=True)
sim_df = simplace_df.rename(columns={c: f"{c}_S" for c in simplace_df.columns if c != "date"})
fran_df = franchestyn_df.rename(columns={c: f"{c}_F" for c in franchestyn_df.columns if c != "date"})
merged = pd.merge(sim_df, fran_df, on="date", how="left")
merged = merged.rename(
columns={
"WSO_S": "WSO_S_g_m2",
"YieldAttainable_F": "YieldAttainable_F_kg_ha",
"YieldActual_F": "YieldActual_F_kg_ha",
}
)
out_path = output_root / "SimulationExperimentTemplate" / out_name
merged.to_csv(out_path, index=False)
return out_path
run_simplace(shell, config, project_lines)
¶
Run a Simplace project for the specified project lines.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shell |
Simplace shell instance. |
required | |
config |
SimplaceConfig |
Simplace configuration object. |
required |
project_lines |
list[int] |
List of project line indices to run. |
required |
Source code in simpest/models/simplace.py
def run_simplace(shell, config: SimplaceConfig, project_lines: list[int]):
"""
Run a Simplace project for the specified project lines.
Args:
shell: Simplace shell instance.
config (SimplaceConfig): Simplace configuration object.
project_lines (list[int]): List of project line indices to run.
"""
simplace.openProject(shell, config.solution_path, config.project_path)
simplace.setProjectLines(shell, project_lines)
simplace.runProject(shell)