Sarikaa-Sridhar commited on
Commit
67ccc5c
·
verified ·
1 Parent(s): 99acee6

Delete pipeline_yield.py

Browse files
Files changed (1) hide show
  1. pipeline_yield.py +0 -278
pipeline_yield.py DELETED
@@ -1,278 +0,0 @@
1
- import sys
2
- from pathlib import Path
3
-
4
- PROJECT_ROOT = Path(__file__).resolve().parents[1]
5
- if str(PROJECT_ROOT) not in sys.path:
6
- sys.path.insert(0, str(PROJECT_ROOT))
7
-
8
- import numpy as np
9
- import torch
10
- from transformers import Pipeline
11
- import math
12
-
13
- DEFAULT_WEATHER_AGG_RULES = {
14
- "dayl": "mean",
15
- "prcp": "sum",
16
- "srad": "mean",
17
- "tmax": "mean",
18
- "tmin": "mean",
19
- "vp": "mean",
20
- "tmean": "mean",
21
- "gdd": "sum",
22
- "precip_3day_avg_perday": "mean",
23
- "precip_7day_avg_perday": "mean",
24
- "precip_14day_avg_perday": "mean",
25
- }
26
-
27
- def daily_to_cumulative_weekly(
28
- daily: np.ndarray,
29
- agg: str,
30
- week_len: int = 7,
31
- ) -> np.ndarray:
32
- daily = daily.astype(np.float32)
33
- T = daily.shape[0]
34
- K = int(math.ceil(T / week_len))
35
-
36
- weekly = np.zeros(K, dtype=np.float32)
37
- cumulative = np.zeros(K, dtype=np.float32)
38
-
39
- for w in range(K):
40
- s = w * week_len
41
- e = min((w + 1) * week_len, T)
42
-
43
- week_chunk = daily[s:e]
44
- cumulative_chunk = daily[:e]
45
-
46
- if agg == "sum":
47
- weekly[w] = np.nansum(week_chunk)
48
- cumulative[w] = np.nansum(cumulative_chunk)
49
- else:
50
- weekly[w] = np.nanmean(week_chunk)
51
- cumulative[w] = np.nanmean(cumulative_chunk)
52
-
53
- return np.stack([weekly, cumulative], axis=1).astype(np.float32)
54
-
55
- class YieldEstimationPipeline(Pipeline):
56
- """
57
- Pipeline API for yield prediction using weather + soil inputs.
58
-
59
- Supports:
60
- - old models: one feature per weather variable, W = len(weather_vars)
61
- - new models: weekly + cumulative per weather variable, W = 2 * len(weather_vars)
62
- """
63
-
64
- def _sanitize_parameters(self, cutoff=None, weather_format=None, **kwargs):
65
- preprocess_kwargs = {}
66
-
67
- if cutoff is not None:
68
- preprocess_kwargs["cutoff"] = cutoff
69
-
70
- if weather_format is not None:
71
- preprocess_kwargs["weather_format"] = weather_format
72
-
73
- return preprocess_kwargs, {}, {}
74
-
75
- def preprocess(self, inputs, cutoff=None, weather_format=None):
76
- cfg = self.model.config
77
-
78
- if not isinstance(inputs, dict):
79
- raise ValueError("Input must be a dictionary.")
80
-
81
- if "weather" not in inputs:
82
- raise ValueError("Input must contain a 'weather' dictionary.")
83
-
84
- if "soil" not in inputs:
85
- raise ValueError("Input must contain a 'soil' dictionary.")
86
-
87
- requested_cutoff = inputs.get("cutoff", cutoff)
88
- weather_format = inputs.get(
89
- "weather_format",
90
- weather_format if weather_format is not None else "weekly_cumulative",
91
- )
92
-
93
- n_weather_vars = len(cfg.weather_vars)
94
- model_w_dim = int(cfg.W)
95
-
96
- use_weekly_and_cumulative = model_w_dim == 2 * n_weather_vars
97
- use_cumulative_only = model_w_dim == n_weather_vars
98
-
99
- if not (use_weekly_and_cumulative or use_cumulative_only):
100
- raise ValueError(
101
- f"Unsupported weather dimension. Model has W={model_w_dim}, "
102
- f"but len(weather_vars)={n_weather_vars}. Expected W={n_weather_vars} "
103
- f"or W={2 * n_weather_vars}."
104
- )
105
-
106
- weather_cols = []
107
-
108
- for v in cfg.weather_vars:
109
- if v not in inputs["weather"]:
110
- raise ValueError(f"Missing weather variable: {v}")
111
-
112
- arr = np.asarray(inputs["weather"][v], dtype=np.float32)
113
-
114
- if weather_format == "daily":
115
- agg = DEFAULT_WEATHER_AGG_RULES.get(v, "mean")
116
- arr = daily_to_cumulative_weekly(arr, agg=agg, week_len=7)
117
-
118
- if arr.ndim != 2 or arr.shape[1] != 2:
119
- raise ValueError(
120
- f"daily_to_cumulative_weekly for '{v}' should return shape [K, 2], "
121
- f"but got {arr.shape}."
122
- )
123
-
124
- if use_weekly_and_cumulative:
125
- weather_cols.append(arr[:, 0]) # weekly
126
- weather_cols.append(arr[:, 1]) # cumulative
127
- else:
128
- weather_cols.append(arr[:, 1]) # cumulative only
129
-
130
- elif weather_format == "weekly_cumulative":
131
- arr = np.asarray(arr, dtype=np.float32)
132
-
133
- if use_cumulative_only:
134
- if arr.ndim != 1:
135
- raise ValueError(
136
- f"For W={model_w_dim}, weather variable '{v}' must be 1D, "
137
- f"but got shape {arr.shape}."
138
- )
139
- weather_cols.append(arr)
140
-
141
- else:
142
- # For W=22 models, weekly_cumulative input must provide either:
143
- # 1. a [K, 2] array for each variable, or
144
- # 2. separate keys: var_weekly and var_cumulative.
145
- if arr.ndim == 2 and arr.shape[1] == 2:
146
- weather_cols.append(arr[:, 0])
147
- weather_cols.append(arr[:, 1])
148
- else:
149
- weekly_key = f"{v}_weekly"
150
- cumulative_key = f"{v}_cumulative"
151
-
152
- if weekly_key not in inputs["weather"] or cumulative_key not in inputs["weather"]:
153
- raise ValueError(
154
- f"Model expects weekly + cumulative features for '{v}'. "
155
- f"For weather_format='weekly_cumulative', provide either "
156
- f"weather['{v}'] as shape [K, 2], or provide "
157
- f"weather['{weekly_key}'] and weather['{cumulative_key}']."
158
- )
159
-
160
- weekly = np.asarray(inputs["weather"][weekly_key], dtype=np.float32)
161
- cumulative = np.asarray(inputs["weather"][cumulative_key], dtype=np.float32)
162
-
163
- if weekly.ndim != 1 or cumulative.ndim != 1:
164
- raise ValueError(
165
- f"{weekly_key} and {cumulative_key} must both be 1D."
166
- )
167
-
168
- weather_cols.append(weekly)
169
- weather_cols.append(cumulative)
170
-
171
- else:
172
- raise ValueError(
173
- "weather_format must be either 'daily' or 'weekly_cumulative'."
174
- )
175
-
176
- lengths = [len(x) for x in weather_cols]
177
- if len(set(lengths)) != 1:
178
- raise ValueError(
179
- f"Weather variable lengths do not match after preprocessing: {lengths}"
180
- )
181
-
182
- weather = np.stack(weather_cols, axis=1).astype(np.float32)
183
-
184
- if requested_cutoff is None:
185
- cutoff = weather.shape[0]
186
- else:
187
- cutoff = int(requested_cutoff)
188
-
189
- t_eff = min(cutoff, weather.shape[0])
190
-
191
- if weather.shape[1] != model_w_dim:
192
- raise ValueError(
193
- f"Processed weather has shape {weather.shape}, but model expects W={model_w_dim}."
194
- )
195
-
196
- soil = []
197
- for v in cfg.soil_vars:
198
- if v not in inputs["soil"]:
199
- raise ValueError(f"Missing soil variable: {v}")
200
- soil.append(float(inputs["soil"][v]))
201
-
202
- soil = np.asarray(soil, dtype=np.float32)
203
-
204
- w_mean = np.asarray(cfg.w_mean, dtype=np.float32)
205
- w_std = np.asarray(cfg.w_std, dtype=np.float32)
206
- s_mean = np.asarray(cfg.s_mean, dtype=np.float32)
207
- s_std = np.asarray(cfg.s_std, dtype=np.float32)
208
-
209
- if len(w_mean) != weather.shape[1]:
210
- raise ValueError(
211
- f"Normalization mismatch: len(w_mean)={len(w_mean)}, "
212
- f"but weather feature dimension={weather.shape[1]}."
213
- )
214
-
215
- weather = np.where(np.isnan(weather), w_mean[None, :], weather)
216
- weather = (weather - w_mean[None, :]) / w_std[None, :]
217
-
218
- soil = np.where(np.isnan(soil), s_mean, soil)
219
- soil = (soil - s_mean) / s_std
220
-
221
- crop_map = {
222
- "corn": 0,
223
- "maize": 0,
224
- "soybean": 1,
225
- "soy": 1,
226
- }
227
-
228
- crop = str(inputs.get("crop", "corn")).strip().lower()
229
- crop_id = crop_map.get(crop, 0)
230
-
231
- return {
232
- "weather": torch.from_numpy(weather[:t_eff]).unsqueeze(0),
233
- "soil": torch.from_numpy(soil).unsqueeze(0),
234
- "crop_id": torch.tensor([crop_id], dtype=torch.long),
235
- "horizon_idx": t_eff,
236
- "cutoff": cutoff,
237
- "effective_cutoff": t_eff,
238
- "crop": crop,
239
- "weather_format": weather_format,
240
- "weather_shape": list(weather[:t_eff].shape),
241
- }
242
-
243
- def _forward(self, model_inputs):
244
- cutoff = model_inputs.pop("cutoff")
245
- effective_cutoff = model_inputs.pop("effective_cutoff")
246
- crop = model_inputs.pop("crop")
247
- weather_format = model_inputs.pop("weather_format")
248
- weather_shape = model_inputs.pop("weather_shape")
249
-
250
- outputs = self.model(
251
- weather=model_inputs["weather"],
252
- soil=model_inputs["soil"],
253
- crop_id=model_inputs["crop_id"],
254
- horizon_idx=model_inputs["horizon_idx"],
255
- causal=True,
256
- return_sequence=False,
257
- )
258
-
259
- return {
260
- "outputs": outputs,
261
- "cutoff": cutoff,
262
- "effective_cutoff": effective_cutoff,
263
- "crop": crop,
264
- "weather_format": weather_format,
265
- "weather_shape": weather_shape,
266
- }
267
-
268
- def postprocess(self, model_outputs):
269
- pred = float(model_outputs["outputs"].predictions.detach().cpu().item())
270
-
271
- return {
272
- "predicted_yield": pred,
273
- "cutoff": int(model_outputs["cutoff"]),
274
- "effective_cutoff": int(model_outputs["effective_cutoff"]),
275
- "crop": model_outputs["crop"],
276
- "weather_format": model_outputs["weather_format"],
277
- "weather_shape": model_outputs["weather_shape"],
278
- }