Cycles-utils 4.0.6__tar.gz → 4.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/Cycles_utils.egg-info/PKG-INFO +33 -9
  2. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/Cycles_utils.egg-info/SOURCES.txt +6 -2
  3. cycles_utils-4.2.0/Cycles_utils.egg-info/requires.txt +44 -0
  4. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/PKG-INFO +33 -9
  5. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/README.md +2 -0
  6. cycles_utils-4.2.0/cycles/__init__.py +67 -0
  7. cycles_utils-4.2.0/cycles/_base_file.py +98 -0
  8. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/cycles.py +5 -40
  9. cycles_utils-4.2.0/cycles/cycles_runner.py +370 -0
  10. cycles_utils-4.2.0/cycles/cycles_tools/__init__.py +72 -0
  11. cycles_utils-4.2.0/cycles/cycles_tools/_geo_file.py +25 -0
  12. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/cycles_tools/control_file.py +18 -13
  13. cycles_utils-4.2.0/cycles/cycles_tools/converter.py +290 -0
  14. cycles_utils-4.2.0/cycles/cycles_tools/crop_file.py +159 -0
  15. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/cycles_tools/nudge_file.py +25 -22
  16. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/cycles_tools/operation_file.py +39 -40
  17. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/cycles_tools/plot_tools.py +6 -6
  18. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/cycles_tools/soil_file.py +18 -19
  19. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/gadm/gadm.py +71 -5
  20. cycles_utils-4.2.0/cycles/rotation_builder.py +679 -0
  21. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/soilgrids/__init__.py +0 -3
  22. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/soilgrids/soilgrids.py +12 -10
  23. cycles_utils-4.2.0/cycles/ssurgo/__init__.py +5 -0
  24. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/ssurgo/ssurgo.py +13 -9
  25. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/weather/__init__.py +0 -1
  26. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/weather/weather.py +9 -6
  27. cycles_utils-4.2.0/setup.py +35 -0
  28. cycles_utils-4.2.0/test/test_runner.py +45 -0
  29. cycles_utils-4.0.6/Cycles_utils.egg-info/requires.txt +0 -19
  30. cycles_utils-4.0.6/cycles/__init__.py +0 -19
  31. cycles_utils-4.0.6/cycles/cycles_runner.py +0 -287
  32. cycles_utils-4.0.6/cycles/cycles_tools/__init__.py +0 -38
  33. cycles_utils-4.0.6/cycles/cycles_tools/_base_file.py +0 -75
  34. cycles_utils-4.0.6/cycles/rotation_builder.py +0 -486
  35. cycles_utils-4.0.6/cycles/ssurgo/__init__.py +0 -8
  36. cycles_utils-4.0.6/setup.py +0 -25
  37. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/Cycles_utils.egg-info/dependency_links.txt +0 -0
  38. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/Cycles_utils.egg-info/top_level.txt +0 -0
  39. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/LICENSE +0 -0
  40. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/MANIFEST.in +0 -0
  41. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/cycles_tools/output_file.py +0 -0
  42. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/cycles_tools/reinit_file.py +0 -0
  43. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/cycles_tools/weather_file.py +0 -0
  44. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/data/GLDASp5_elevation_025d.nc4 +0 -0
  45. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/data/GLDASp5_landmask_025d.nc4 +0 -0
  46. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/data/NLDAS_elevation.nc4 +0 -0
  47. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/data/NLDAS_masks-veg-soil.nc4 +0 -0
  48. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/data/cb_2018_us_state_20m.cpg +0 -0
  49. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/data/cb_2018_us_state_20m.dbf +0 -0
  50. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/data/cb_2018_us_state_20m.prj +0 -0
  51. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/data/cb_2018_us_state_20m.shp +0 -0
  52. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/data/cb_2018_us_state_20m.shx +0 -0
  53. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/data/fips_gid_conversion.csv +0 -0
  54. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/data/gridMET_elevation_mask.nc +0 -0
  55. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/data/metdata_elevationdata.nc +0 -0
  56. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/data/us_states.csv +0 -0
  57. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/cycles/gadm/__init__.py +0 -0
  58. {cycles_utils-4.0.6 → cycles_utils-4.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Cycles-utils
3
- Version: 4.0.6
3
+ Version: 4.2.0
4
4
  Summary: Python scripts to build Cycles input files and post-process Cycles output files
5
5
  Home-page: https://github.com/PSUmodeling/Cycles-utils
6
6
  Author: Yuning Shi
@@ -10,21 +10,43 @@ Requires-Python: >=3.10
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
12
  Requires-Dist: pandas>=1.2.4
13
- Requires-Dist: geopandas>=0.9.0
14
13
  Requires-Dist: numpy>=1.19.5
15
- Requires-Dist: cartopy>=0.18.0
16
14
  Requires-Dist: matplotlib>=3.4.2
15
+ Requires-Dist: tqdm>=4.60.0
16
+ Requires-Dist: IPython>=7.0.0
17
+ Provides-Extra: plot
18
+ Requires-Dist: cartopy>=0.18.0; extra == "plot"
19
+ Requires-Dist: geopandas>=0.9.0; extra == "plot"
20
+ Requires-Dist: fiona>=1.8.20; extra == "plot"
21
+ Requires-Dist: shapely>=1.7.1; extra == "plot"
22
+ Provides-Extra: weather
23
+ Requires-Dist: netCDF4>=1.5.7; extra == "weather"
24
+ Requires-Dist: scipy>=1.6.3; extra == "weather"
17
25
  Provides-Extra: soilgrids
26
+ Requires-Dist: geopandas>=0.9.0; extra == "soilgrids"
18
27
  Requires-Dist: rioxarray>=0.5.0; extra == "soilgrids"
19
28
  Requires-Dist: owslib>=0.24.1; extra == "soilgrids"
20
29
  Requires-Dist: rasterio>=1.2.3; extra == "soilgrids"
21
30
  Requires-Dist: shapely>=1.7.1; extra == "soilgrids"
22
- Provides-Extra: gssurgo
23
- Requires-Dist: shapely>=1.7.1; extra == "gssurgo"
24
- Requires-Dist: fiona>=1.8.20; extra == "gssurgo"
25
- Provides-Extra: weather
26
- Requires-Dist: netCDF4>=1.5.7; extra == "weather"
27
- Requires-Dist: tqdm>=4.60.0; extra == "weather"
31
+ Requires-Dist: pyproj>=3.0.1; extra == "soilgrids"
32
+ Provides-Extra: ssurgo
33
+ Requires-Dist: geopandas>=0.9.0; extra == "ssurgo"
34
+ Requires-Dist: shapely>=1.7.1; extra == "ssurgo"
35
+ Requires-Dist: fiona>=1.8.20; extra == "ssurgo"
36
+ Provides-Extra: gadm
37
+ Requires-Dist: geopandas>=0.9.0; extra == "gadm"
38
+ Provides-Extra: all
39
+ Requires-Dist: cartopy>=0.18.0; extra == "all"
40
+ Requires-Dist: geopandas>=0.9.0; extra == "all"
41
+ Requires-Dist: fiona>=1.8.20; extra == "all"
42
+ Requires-Dist: shapely>=1.7.1; extra == "all"
43
+ Requires-Dist: rioxarray>=0.5.0; extra == "all"
44
+ Requires-Dist: owslib>=0.24.1; extra == "all"
45
+ Requires-Dist: rasterio>=1.2.3; extra == "all"
46
+ Requires-Dist: pyproj>=3.0.1; extra == "all"
47
+ Requires-Dist: netCDF4>=1.5.7; extra == "all"
48
+ Requires-Dist: tqdm>=4.60.0; extra == "all"
49
+ Requires-Dist: scipy>=1.6.3; extra == "all"
28
50
  Dynamic: author
29
51
  Dynamic: author-email
30
52
  Dynamic: description
@@ -61,6 +83,8 @@ pip install Cycles-utils
61
83
 
62
84
  | Cycles-utils version | Cycles version |
63
85
  | -------------------- | -------------- |
86
+ | 4.2.0 | 1.5.20+ |
87
+ | 4.1.0 | 1.5.20+ |
64
88
  | 4.0.6 | 1.5.20 |
65
89
  | 4.0.5 | 1.5.20 |
66
90
  | 4.0.4 | 1.5.20 |
@@ -8,12 +8,15 @@ Cycles_utils.egg-info/dependency_links.txt
8
8
  Cycles_utils.egg-info/requires.txt
9
9
  Cycles_utils.egg-info/top_level.txt
10
10
  cycles/__init__.py
11
+ cycles/_base_file.py
11
12
  cycles/cycles.py
12
13
  cycles/cycles_runner.py
13
14
  cycles/rotation_builder.py
14
15
  cycles/cycles_tools/__init__.py
15
- cycles/cycles_tools/_base_file.py
16
+ cycles/cycles_tools/_geo_file.py
16
17
  cycles/cycles_tools/control_file.py
18
+ cycles/cycles_tools/converter.py
19
+ cycles/cycles_tools/crop_file.py
17
20
  cycles/cycles_tools/nudge_file.py
18
21
  cycles/cycles_tools/operation_file.py
19
22
  cycles/cycles_tools/output_file.py
@@ -41,4 +44,5 @@ cycles/soilgrids/soilgrids.py
41
44
  cycles/ssurgo/__init__.py
42
45
  cycles/ssurgo/ssurgo.py
43
46
  cycles/weather/__init__.py
44
- cycles/weather/weather.py
47
+ cycles/weather/weather.py
48
+ test/test_runner.py
@@ -0,0 +1,44 @@
1
+ pandas>=1.2.4
2
+ numpy>=1.19.5
3
+ matplotlib>=3.4.2
4
+ tqdm>=4.60.0
5
+ IPython>=7.0.0
6
+
7
+ [all]
8
+ cartopy>=0.18.0
9
+ geopandas>=0.9.0
10
+ fiona>=1.8.20
11
+ shapely>=1.7.1
12
+ rioxarray>=0.5.0
13
+ owslib>=0.24.1
14
+ rasterio>=1.2.3
15
+ pyproj>=3.0.1
16
+ netCDF4>=1.5.7
17
+ tqdm>=4.60.0
18
+ scipy>=1.6.3
19
+
20
+ [gadm]
21
+ geopandas>=0.9.0
22
+
23
+ [plot]
24
+ cartopy>=0.18.0
25
+ geopandas>=0.9.0
26
+ fiona>=1.8.20
27
+ shapely>=1.7.1
28
+
29
+ [soilgrids]
30
+ geopandas>=0.9.0
31
+ rioxarray>=0.5.0
32
+ owslib>=0.24.1
33
+ rasterio>=1.2.3
34
+ shapely>=1.7.1
35
+ pyproj>=3.0.1
36
+
37
+ [ssurgo]
38
+ geopandas>=0.9.0
39
+ shapely>=1.7.1
40
+ fiona>=1.8.20
41
+
42
+ [weather]
43
+ netCDF4>=1.5.7
44
+ scipy>=1.6.3
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Cycles-utils
3
- Version: 4.0.6
3
+ Version: 4.2.0
4
4
  Summary: Python scripts to build Cycles input files and post-process Cycles output files
5
5
  Home-page: https://github.com/PSUmodeling/Cycles-utils
6
6
  Author: Yuning Shi
@@ -10,21 +10,43 @@ Requires-Python: >=3.10
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
12
  Requires-Dist: pandas>=1.2.4
13
- Requires-Dist: geopandas>=0.9.0
14
13
  Requires-Dist: numpy>=1.19.5
15
- Requires-Dist: cartopy>=0.18.0
16
14
  Requires-Dist: matplotlib>=3.4.2
15
+ Requires-Dist: tqdm>=4.60.0
16
+ Requires-Dist: IPython>=7.0.0
17
+ Provides-Extra: plot
18
+ Requires-Dist: cartopy>=0.18.0; extra == "plot"
19
+ Requires-Dist: geopandas>=0.9.0; extra == "plot"
20
+ Requires-Dist: fiona>=1.8.20; extra == "plot"
21
+ Requires-Dist: shapely>=1.7.1; extra == "plot"
22
+ Provides-Extra: weather
23
+ Requires-Dist: netCDF4>=1.5.7; extra == "weather"
24
+ Requires-Dist: scipy>=1.6.3; extra == "weather"
17
25
  Provides-Extra: soilgrids
26
+ Requires-Dist: geopandas>=0.9.0; extra == "soilgrids"
18
27
  Requires-Dist: rioxarray>=0.5.0; extra == "soilgrids"
19
28
  Requires-Dist: owslib>=0.24.1; extra == "soilgrids"
20
29
  Requires-Dist: rasterio>=1.2.3; extra == "soilgrids"
21
30
  Requires-Dist: shapely>=1.7.1; extra == "soilgrids"
22
- Provides-Extra: gssurgo
23
- Requires-Dist: shapely>=1.7.1; extra == "gssurgo"
24
- Requires-Dist: fiona>=1.8.20; extra == "gssurgo"
25
- Provides-Extra: weather
26
- Requires-Dist: netCDF4>=1.5.7; extra == "weather"
27
- Requires-Dist: tqdm>=4.60.0; extra == "weather"
31
+ Requires-Dist: pyproj>=3.0.1; extra == "soilgrids"
32
+ Provides-Extra: ssurgo
33
+ Requires-Dist: geopandas>=0.9.0; extra == "ssurgo"
34
+ Requires-Dist: shapely>=1.7.1; extra == "ssurgo"
35
+ Requires-Dist: fiona>=1.8.20; extra == "ssurgo"
36
+ Provides-Extra: gadm
37
+ Requires-Dist: geopandas>=0.9.0; extra == "gadm"
38
+ Provides-Extra: all
39
+ Requires-Dist: cartopy>=0.18.0; extra == "all"
40
+ Requires-Dist: geopandas>=0.9.0; extra == "all"
41
+ Requires-Dist: fiona>=1.8.20; extra == "all"
42
+ Requires-Dist: shapely>=1.7.1; extra == "all"
43
+ Requires-Dist: rioxarray>=0.5.0; extra == "all"
44
+ Requires-Dist: owslib>=0.24.1; extra == "all"
45
+ Requires-Dist: rasterio>=1.2.3; extra == "all"
46
+ Requires-Dist: pyproj>=3.0.1; extra == "all"
47
+ Requires-Dist: netCDF4>=1.5.7; extra == "all"
48
+ Requires-Dist: tqdm>=4.60.0; extra == "all"
49
+ Requires-Dist: scipy>=1.6.3; extra == "all"
28
50
  Dynamic: author
29
51
  Dynamic: author-email
30
52
  Dynamic: description
@@ -61,6 +83,8 @@ pip install Cycles-utils
61
83
 
62
84
  | Cycles-utils version | Cycles version |
63
85
  | -------------------- | -------------- |
86
+ | 4.2.0 | 1.5.20+ |
87
+ | 4.1.0 | 1.5.20+ |
64
88
  | 4.0.6 | 1.5.20 |
65
89
  | 4.0.5 | 1.5.20 |
66
90
  | 4.0.4 | 1.5.20 |
@@ -22,6 +22,8 @@ pip install Cycles-utils
22
22
 
23
23
  | Cycles-utils version | Cycles version |
24
24
  | -------------------- | -------------- |
25
+ | 4.2.0 | 1.5.20+ |
26
+ | 4.1.0 | 1.5.20+ |
25
27
  | 4.0.6 | 1.5.20 |
26
28
  | 4.0.5 | 1.5.20 |
27
29
  | 4.0.4 | 1.5.20 |
@@ -0,0 +1,67 @@
1
+ from .cycles import Cycles
2
+ from .cycles_tools import generate_control_file
3
+ from .cycles_tools import generate_nudge_file
4
+ from .cycles_tools import generate_soil_file
5
+ from .cycles_tools import generate_reinit_file
6
+ from .cycles_tools import read_control_file
7
+ from .cycles_tools import read_crop_file
8
+ from .cycles_tools import generate_crop_file
9
+ from .cycles_tools import read_soil_file
10
+ from .cycles_tools import read_weather_file
11
+ from .cycles_tools import read_output
12
+ from .cycles_tools import read_operation_file
13
+ from .cycles_tools import generate_operation_file
14
+ from .cycles_tools import convert_obsolete_crop_files
15
+ from .cycles_tools import convert_obsolete_operation_files
16
+ from .cycles_tools import convert_obsolete_soil_files
17
+ from .cycles_tools import SoilLayer
18
+ from .cycles_tools import Operation, Planting, Tillage, Harvest, Kill, FixedFertilization, FixedIrrigation, AutoIrrigation
19
+ from .cycles_runner import CyclesRunner
20
+ from .rotation_builder import CyclesRotationBuilder, Crop, CropGroup
21
+
22
+ __all__ = [
23
+ "Cycles",
24
+ "CyclesRunner",
25
+ "CyclesRotationBuilder",
26
+ "Crop",
27
+ "CropGroup",
28
+ "generate_control_file",
29
+ "read_control_file",
30
+ "generate_nudge_file",
31
+ "read_crop_file",
32
+ "generate_crop_file",
33
+ "read_operation_file",
34
+ "generate_operation_file",
35
+ "read_output",
36
+ "generate_soil_file",
37
+ "read_soil_file",
38
+ "read_weather_file",
39
+ "generate_reinit_file",
40
+ "convert_obsolete_crop_files",
41
+ "convert_obsolete_operation_files",
42
+ "convert_obsolete_soil_files",
43
+ "SoilLayer",
44
+ "Operation", "Planting", "Tillage", "Harvest", "Kill",
45
+ "FixedFertilization", "FixedIrrigation", "AutoIrrigation",
46
+ "plot_yield",
47
+ "plot_operations",
48
+ "plot_map",
49
+ "plot_satellite_map",
50
+ ]
51
+
52
+ # Plotting helpers require the optional [plot] extra (cartopy, geopandas,
53
+ # fiona). Import lazily so `import cycles` never requires them.
54
+ _PLOT_ATTRS = ("plot_yield", "plot_operations", "plot_map", "plot_satellite_map")
55
+
56
+
57
+ def __getattr__(name: str):
58
+ if name in _PLOT_ATTRS:
59
+ from . import cycles_tools
60
+ value = getattr(cycles_tools, name)
61
+ globals()[name] = value
62
+ return value
63
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
64
+
65
+
66
+ def __dir__():
67
+ return sorted(list(globals().keys()) + list(_PLOT_ATTRS))
@@ -0,0 +1,98 @@
1
+ from __future__ import annotations
2
+ import os
3
+ import sys
4
+ import types
5
+ from dataclasses import fields
6
+ from pathlib import Path
7
+ from typing import Union, Any
8
+
9
+ def _fmt_float(value: float, digits: int=2) -> str:
10
+ if value == -999.0:
11
+ return '-999'
12
+ for d in range(1, digits):
13
+ if value == round(value, d):
14
+ return f'{value:.{d}f}'
15
+ else:
16
+ return f'{value:.{digits}f}'
17
+
18
+ # Convenience lambdas for metadata
19
+ FMT_1F = lambda v: _fmt_float(v, 1)
20
+ FMT_2F = lambda v: _fmt_float(v, 2)
21
+ FMT_3F = lambda v: _fmt_float(v, 3)
22
+ FMT_4F = lambda v: _fmt_float(v, 4)
23
+
24
+ def _format_field(object, attribute, widths: tuple[int, int], value=None) -> str:
25
+ fmt = attribute.metadata.get('fmt')
26
+ description = attribute.metadata.get('description', '')
27
+
28
+ if value is None:
29
+ value = getattr(object, attribute.name)
30
+
31
+ formatted = fmt(value) if fmt is not None else str(value)
32
+
33
+ return f'{attribute.name.upper():<{widths[0]}}{formatted:<{widths[1]}}# {description}' if description else f'{attribute.name.upper():<{widths[0]}}{formatted}'
34
+
35
+
36
+ def _format_block(label: str, object, *, widths: tuple[int, int]=(28, 8), center: bool=False, all_caps: bool=True) -> str:
37
+ label = label.replace("_", " ").upper() if all_caps else label.replace("_", " ").capitalize()
38
+
39
+ lines = [f' ' * (28 if center else 0) + f'## {label} ##']
40
+ for f in fields(object):
41
+ lines.append(_format_field(object, f, widths))
42
+ lines.append('')
43
+
44
+ return '\n'.join(lines)
45
+
46
+
47
+ def _write_file(fn: Path, config) -> None:
48
+ content = '\n'.join(
49
+ _format_block(f.name, getattr(config, f.name))
50
+ for f in fields(config)
51
+ if getattr(config, f.name) is not None
52
+ )
53
+ fn.write_text(content)
54
+
55
+
56
+ def _resolve_dict_values(user_dict: dict, simulation: dict[str, Any] | None) -> dict:
57
+ return {key: func(simulation) if callable(func) else func for key, func in user_dict.items()}
58
+
59
+
60
+ def _extract(dc_class, resolved: dict) -> dict:
61
+ return {f.name: resolved[f.name] for f in fields(dc_class) if f.name in resolved}
62
+
63
+
64
+ def _parse_value(raw: str, name: str, hint: type) -> int | float | str:
65
+ hint = _unwrap_optional(hint)
66
+ if raw.split()[0].lower() == name.lower():
67
+ if hint is int: return int(raw.split()[1])
68
+ if hint is float: return float(raw.split()[1])
69
+ return raw.split()[1]
70
+ else:
71
+ raise ValueError(f"Expected field name '{name}' not found in line: {raw}")
72
+
73
+
74
+ def _unwrap_optional(t) -> type:
75
+ origin = getattr(t, '__origin__', None)
76
+ if origin is Union or origin is types.UnionType or isinstance(t, types.UnionType):
77
+ return next(arg for arg in t.__args__ if arg is not type(None))
78
+ return t
79
+
80
+
81
+ def _if_ipython() -> bool:
82
+ try:
83
+ from IPython import get_ipython # type: ignore
84
+ shell = get_ipython()
85
+ return shell is not None and shell.__class__.__name__ == 'ZMQInteractiveShell'
86
+ except ImportError:
87
+ return False
88
+
89
+
90
+ def _disable_progress_bar() -> bool:
91
+ if _if_ipython():
92
+ return False
93
+ if os.environ.get('SLURM_JOB_ID') is not None:
94
+ return True
95
+ try:
96
+ return not sys.stderr.isatty()
97
+ except AttributeError:
98
+ return True
@@ -1,8 +1,6 @@
1
1
  from __future__ import annotations
2
2
  import numpy as np
3
- import os
4
3
  import pandas as pd
5
- import subprocess
6
4
  from collections.abc import Collection
7
5
  from dataclasses import dataclass, field
8
6
  from pathlib import Path
@@ -14,8 +12,6 @@ from .cycles_tools import read_weather_file as _read_weather_file
14
12
  from .cycles_tools import read_output as _read_output
15
13
  from .cycles_tools import read_operation_file as _read_operation_file
16
14
  from .cycles_tools import generate_reinit_file as _generate_reinit_file
17
- from .cycles_tools import plot_yield as _plot_yield
18
- from .cycles_tools import plot_operations as _plot_operations
19
15
 
20
16
  @dataclass
21
17
  class Output:
@@ -26,13 +22,12 @@ class Output:
26
22
  class Cycles:
27
23
  """Interface for executing one Cycles simulation and reading its files.
28
24
 
29
- Provides methods to run simulations, read outputs, and inspect soil/weather/operation configurations. Automatically
30
- loads the control file upon initialization.
25
+ Provides methods to read outputs, and inspect soil/weather/operation configurations. Automatically loads the control
26
+ file upon initialization.
31
27
 
32
28
  Args:
33
29
  path: Path to the simulation directory (containing input/ and output/ subdirs).
34
30
  simulation: Name of the simulation (base name of control file without extension).
35
- executable: Optional absolute path to the Cycles executable binary. This is required to run simulations using the `run` method.
36
31
 
37
32
  Attributes:
38
33
  path: Path to the simulation directory (containing input/ and output/ subdirs).
@@ -44,12 +39,10 @@ class Cycles:
44
39
  curve_number: Runoff curve number for hydrologic calculations.
45
40
  slope: Land slope used in erosion and runoff models.
46
41
  weather: DataFrame of weather forcing data (temperature, precipitation, etc.).
47
- executable: Absolute path to the Cycles executable binary.
48
42
  """
49
43
 
50
- path: Path | str
51
44
  simulation: str
52
- executable: Path | str | None = None
45
+ path: Path | str = '.'
53
46
  output: dict[str, Output] = field(init=False, default_factory=dict[str, Output])
54
47
  control: ControlConfig | None = field(init=False, default=None)
55
48
  operations: list | None = field(init=False, default=None)
@@ -63,36 +56,6 @@ class Cycles:
63
56
  self.path = Path(self.path)
64
57
  assert isinstance(self.path, Path)
65
58
  self.control = _read_control_file(self.path / 'input' / f'{self.simulation}.ctrl')
66
- if self.executable is not None:
67
- self.executable = str(Path(self.executable).resolve())
68
-
69
-
70
- def run(self, options: str, silence: bool=False) -> tuple[int, str]:
71
- """Run the Cycles executable for this simulation.
72
-
73
- To use the `run` method, the `executable` attribute must be set to the path of the Cycles executable. The
74
- `options` string is passed directly to the command line when invoking Cycles, allowing you to specify any
75
- command-line options supported by Cycles (e.g., `-s` for spin-up, etc.).
76
-
77
- Args:
78
- options: Command-line options passed to Cycles.
79
- silence: If True, suppress stdout and stderr printing.
80
-
81
- Returns:
82
- A tuple with process return code and stdout text.
83
- """
84
- cmd = [self.executable, *(options.split() if options else []), self.simulation]
85
- result = subprocess.run(
86
- cmd,
87
- shell=os.name == 'nt',
88
- capture_output=True,
89
- text=True,
90
- )
91
- if not silence:
92
- print(result.stdout)
93
- if result.stderr:
94
- print(result.stderr)
95
- return result.returncode, result.stdout
96
59
 
97
60
 
98
61
  def read_output(self, output_types: Collection) -> None:
@@ -167,6 +130,7 @@ class Cycles:
167
130
  if 'harvest' not in self.output:
168
131
  self.read_output('harvest')
169
132
 
133
+ from .cycles_tools import plot_yield as _plot_yield # lazy: optional [plot] extra
170
134
  return _plot_yield(self.output['harvest'].data, ax=ax, crop_colors=crop_colors, fontsize=fontsize)
171
135
 
172
136
 
@@ -188,4 +152,5 @@ class Cycles:
188
152
 
189
153
  rotation_size = self.control.simulation_years.rotation_size
190
154
 
155
+ from .cycles_tools import plot_operations as _plot_operations # lazy: optional [plot] extra
191
156
  return _plot_operations(self.operations, rotation_size, axs=axs, fontsize=fontsize)