Cycles class

cycles.Cycles(path, simulation, output=dict[str, Output](), control=None, operations=None, soil_profile=None, curve_number=None, slope=None, weather=None, executable=None) dataclass

Interface for executing one Cycles simulation and reading its files.

Provides methods to run simulations, read outputs, and inspect soil/weather/operation configurations. Automatically loads the control file upon initialization.

Attributes:
  • path (Path | str) –

    Path to the simulation directory (containing input/ and output/ subdirs).

  • simulation (str) –

    Name of the simulation (base name of control file without extension).

  • output (dict[str, Output]) –

    Dictionary mapping output table names to Output objects with data and units.

  • control (ControlConfig | None) –

    Parsed control file configuration from the input directory.

  • operations (list | None) –

    List of parsed operation records from the operation file.

  • soil_profile (list[SoilLayer] | None) –

    List of SoilLayer objects describing the soil profile.

  • curve_number (int | None) –

    Runoff curve number for hydrologic calculations.

  • slope (float | None) –

    Land slope used in erosion and runoff models.

  • weather (DataFrame | None) –

    DataFrame of weather forcing data (temperature, precipitation, etc.).

  • executable (Path | str | None) –

    Absolute path to the Cycles executable binary.

run(options, silence=False)

Run the Cycles executable for this simulation.

Parameters:
  • options (str) –

    Command-line options passed to Cycles.

  • silence (bool, default: False ) –

    If True, suppress stdout and stderr printing.

Returns:
  • tuple[int, str]

    A tuple with process return code and stdout text.

Source code in cycles/cycles.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def run(self, options: str, silence: bool=False) -> tuple[int, str]:
    """Run the Cycles executable for this simulation.

    Args:
        options: Command-line options passed to Cycles.
        silence: If True, suppress stdout and stderr printing.

    Returns:
        A tuple with process return code and stdout text.
    """
    cmd = [self.executable, *(options.split() if options else []), self.simulation]
    result = subprocess.run(
        cmd,
        shell=os.name == 'nt',
        capture_output=True,
        text=True,
    )
    if not silence:
        print(result.stdout)
    if result.stderr:
        print(result.stderr)
    return result.returncode, result.stdout

read_output(output_types)

Read one or more output tables into memory.

Parameters:
  • output_types (Collection) –

    Output table name or collection of names.

Source code in cycles/cycles.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def read_output(self, output_types: Collection) -> None:
    """Read one or more output tables into memory.

    Args:
        output_types: Output table name or collection of names.
    """
    assert isinstance(self.path, Path)
    if isinstance(output_types, str):
        output_types = output_types,

    for output_type in output_types:
        df, units = _read_output(self.path / 'output' / self.simulation, output_type)
        self.output[output_type] = Output(data=df, units=units)

read_operation_file()

Load operation records defined in the control file.

Source code in cycles/cycles.py
104
105
106
107
108
def read_operation_file(self) -> None:
    """Load operation records defined in the control file."""
    assert isinstance(self.path, Path)
    assert self.control is not None
    self.operations = _read_operation_file(self.path / 'input' / self.control.input_files.operation_file)

read_soil_file()

Load soil profile layers and metadata from the configured soil file.

Source code in cycles/cycles.py
111
112
113
114
115
116
117
def read_soil_file(self) -> None:
    """Load soil profile layers and metadata from the configured soil file."""
    assert isinstance(self.path, Path)
    assert self.control is not None
    self.soil_profile, meta = _read_soil_file(self.path / 'input' / self.control.input_files.soil_file)
    self.curve_number = meta['curve_number']
    self.slope = meta['slope']

read_weather_file(*, start_year=-9999, end_year=9999, subdaily=False)

Read weather forcing data for the configured weather file.

Parameters:
  • start_year (int, default: -9999 ) –

    Inclusive first year to keep.

  • end_year (int, default: 9999 ) –

    Inclusive last year to keep.

  • subdaily (bool, default: False ) –

    If True, parse hourly format instead of daily.

Source code in cycles/cycles.py
120
121
122
123
124
125
126
127
128
129
130
def read_weather_file(self, *, start_year: int=-9999, end_year: int=9999, subdaily: bool=False) -> None:
    """Read weather forcing data for the configured weather file.

    Args:
        start_year: Inclusive first year to keep.
        end_year: Inclusive last year to keep.
        subdaily: If True, parse hourly format instead of daily.
    """
    assert isinstance(self.path, Path)
    assert self.control is not None
    self.weather = _read_weather_file(self.path / 'input' / self.control.input_files.weather_file, start_year=start_year, end_year=end_year, subdaily=subdaily)

generate_reinit_file(doy, *, reinit=None)

Generate a reinitialization file from model output.

Parameters:
  • doy (int) –

    Day-of-year to extract from reinit output.

  • reinit (str | None, default: None ) –

    Optional output stem for the reinit file.

Source code in cycles/cycles.py
133
134
135
136
137
138
139
140
141
def generate_reinit_file(self, doy: int, *, reinit: str | None=None) -> None:
    """Generate a reinitialization file from model output.

    Args:
        doy: Day-of-year to extract from reinit output.
        reinit: Optional output stem for the reinit file.
    """
    assert isinstance(self.path, Path)
    _generate_reinit_file(self.path / 'input' / f'{self.simulation if reinit is None else reinit}.reinit', self.path / 'output' / self.simulation, doy)

plot_yield(*, ax=None, fontsize=None)

Plot grain and forage yields from harvest output.

Source code in cycles/cycles.py
144
145
146
147
148
149
def plot_yield(self, *, ax: Axes | None=None, fontsize: int | None=None) -> Axes:
    """Plot grain and forage yields from harvest output."""
    if 'harvest' not in self.output:
        self.read_output('harvest')

    return _plot_yield(self.output['harvest'].data, ax=ax, fontsize=fontsize)

plot_operations(*, axs=None, fontsize=None)

Plot operation timelines grouped by rotation year.

Parameters:
  • rotation_size

    Number of years in the plotted rotation.

  • axs (Axes | ndarray | None, default: None ) –

    Optional axes object(s) to draw on.

  • fontsize (int | None, default: None ) –

    Global matplotlib font size override.

Returns:
  • The axes used for plotting.

Source code in cycles/cycles.py
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def plot_operations(self, *, axs: Axes | np.ndarray | None=None, fontsize: int | None=None):
    """Plot operation timelines grouped by rotation year.

    Args:
        rotation_size: Number of years in the plotted rotation.
        axs: Optional axes object(s) to draw on.
        fontsize: Global matplotlib font size override.

    Returns:
        The axes used for plotting.
    """
    if self.operations is None:
        self.read_operation_file()

    assert self.control is not None
    assert self.operations is not None

    rotation_size = self.control.simulation_years.rotation_size

    return _plot_operations(self.operations, rotation_size, axs=axs, fontsize=fontsize)