suhaas-code commited on
Commit
ddf8803
·
1 Parent(s): 1ef6948

Fix HF import error by tracking env source package

Browse files
Files changed (3) hide show
  1. .gitignore +3 -0
  2. env/__init__.py +3 -0
  3. env/farm_env.py +191 -0
.gitignore CHANGED
@@ -6,6 +6,9 @@
6
  .venv/
7
  venv/
8
  env/
 
 
 
9
 
10
  # Python cache and build artifacts
11
  __pycache__/
 
6
  .venv/
7
  venv/
8
  env/
9
+ !env/
10
+ !env/__init__.py
11
+ !env/farm_env.py
12
 
13
  # Python cache and build artifacts
14
  __pycache__/
env/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .farm_env import FarmAction, FarmEnv, FarmState, FarmStepResult
2
+
3
+ __all__ = ["FarmAction", "FarmEnv", "FarmState", "FarmStepResult"]
env/farm_env.py ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+ from pydantic import BaseModel, Field
9
+
10
+
11
+ class FarmState(BaseModel):
12
+ soil_moisture: float = Field(ge=0.0, le=100.0)
13
+ soil_ph: float = Field(ge=4.0, le=9.0)
14
+ temperature: float
15
+ rainfall: float = Field(ge=0.0)
16
+ crop_stage: int = Field(ge=0)
17
+ day: int = Field(ge=0)
18
+
19
+
20
+ class FarmAction(BaseModel):
21
+ water: float = Field(ge=0.0, le=50.0)
22
+ fertilizer: float = Field(ge=0.0, le=20.0)
23
+ pesticide: float = Field(ge=0.0, le=10.0)
24
+
25
+
26
+ class FarmStepResult(BaseModel):
27
+ observation: FarmState
28
+ reward: float
29
+ done: bool
30
+ info: dict[str, Any]
31
+
32
+
33
+ class FarmEnv:
34
+ """Minimal deterministic OpenEnv-style farm environment for Phase-1."""
35
+
36
+ REQUIRED_COLUMNS = {
37
+ "Soil_pH",
38
+ "Soil_Moisture",
39
+ "Temperature_C",
40
+ "Rainfall_mm",
41
+ }
42
+
43
+ def __init__(
44
+ self,
45
+ dataset_path: str | Path = "farmer_advisor_dataset.csv",
46
+ seed: int = 42,
47
+ max_days: int = 30,
48
+ ) -> None:
49
+ self.dataset_path = Path(dataset_path)
50
+ self.max_days = max_days
51
+ self._rng = np.random.default_rng(seed)
52
+ self._dataset = self._load_dataset(self.dataset_path)
53
+ self._row_index = 0
54
+ self._state: FarmState | None = None
55
+
56
+ def _load_dataset(self, dataset_path: Path) -> pd.DataFrame:
57
+ if not dataset_path.exists():
58
+ raise FileNotFoundError(f"Dataset not found: {dataset_path}")
59
+
60
+ df = pd.read_csv(dataset_path)
61
+ missing = self.REQUIRED_COLUMNS - set(df.columns)
62
+ if missing:
63
+ raise ValueError(
64
+ f"Dataset is missing required columns: {sorted(missing)}")
65
+ return df.reset_index(drop=True)
66
+
67
+ def _next_weather_row(self) -> pd.Series:
68
+ self._row_index = (self._row_index + 1) % len(self._dataset)
69
+ return self._dataset.iloc[self._row_index]
70
+
71
+ def reset(self, seed: int | None = None) -> FarmState:
72
+ if seed is not None:
73
+ self._rng = np.random.default_rng(seed)
74
+
75
+ self._row_index = int(self._rng.integers(0, len(self._dataset)))
76
+ row = self._dataset.iloc[self._row_index]
77
+
78
+ self._state = FarmState(
79
+ soil_moisture=float(np.clip(row["Soil_Moisture"], 0.0, 100.0)),
80
+ soil_ph=float(np.clip(row["Soil_pH"], 4.5, 8.5)),
81
+ temperature=float(row["Temperature_C"]),
82
+ rainfall=float(np.clip(row["Rainfall_mm"], 0.0, 200.0)),
83
+ crop_stage=0,
84
+ day=0,
85
+ )
86
+ return self._state
87
+
88
+ def state(self) -> FarmState:
89
+ if self._state is None:
90
+ raise RuntimeError(
91
+ "Environment is not initialized. Call reset() first.")
92
+ return self._state
93
+
94
+ @staticmethod
95
+ def _clip(value: float, low: float, high: float) -> float:
96
+ return float(np.clip(value, low, high))
97
+
98
+ @staticmethod
99
+ def _compute_reward(state: FarmState, action: FarmAction, day: int) -> tuple[float, dict[str, float]]:
100
+ moisture_score = np.clip(state.soil_moisture / 100.0, 0.0, 1.0)
101
+ temperature_factor = np.clip(
102
+ 1.0 - abs(state.temperature - 26.0) / 16.0, 0.0, 1.0)
103
+ rainfall_factor = np.clip(
104
+ 1.0 - abs(state.rainfall - 60.0) / 60.0, 0.0, 1.0)
105
+
106
+ yield_score = (
107
+ 0.4 * float(moisture_score)
108
+ + 0.3 * float(temperature_factor)
109
+ + 0.3 * float(rainfall_factor)
110
+ )
111
+
112
+ resource_penalty = 0.03 * \
113
+ (action.fertilizer**1.2) + 0.04 * (action.pesticide**1.3)
114
+ sustainability_bonus = 0.2 * np.exp(-action.fertilizer / 20.0) + 0.2 * np.exp(
115
+ -action.pesticide / 10.0
116
+ )
117
+
118
+ overuse_penalty = 0.0
119
+ if action.fertilizer > 12.0:
120
+ overuse_penalty += 0.02 * (action.fertilizer - 12.0)
121
+ if action.pesticide > 6.0:
122
+ overuse_penalty += 0.03 * (action.pesticide - 6.0)
123
+
124
+ loop_penalty = 0.0
125
+ if day > 20 and action.water == 0.0 and action.fertilizer == 0.0 and action.pesticide == 0.0:
126
+ loop_penalty = 0.1
127
+
128
+ reward = float(yield_score + sustainability_bonus -
129
+ resource_penalty - overuse_penalty - loop_penalty)
130
+ info = {
131
+ "yield_score": float(yield_score),
132
+ "resource_penalty": float(resource_penalty),
133
+ "sustainability_bonus": float(sustainability_bonus),
134
+ "overuse_penalty": float(overuse_penalty),
135
+ "loop_penalty": float(loop_penalty),
136
+ }
137
+ return reward, info
138
+
139
+ def step(self, action: FarmAction | dict[str, float]) -> FarmStepResult:
140
+ if self._state is None:
141
+ raise RuntimeError(
142
+ "Environment is not initialized. Call reset() first.")
143
+
144
+ action_model = action if isinstance(
145
+ action, FarmAction) else FarmAction(**action)
146
+ previous_state = self._state
147
+ weather = self._next_weather_row()
148
+
149
+ day = previous_state.day + 1
150
+ crop_stage = min(5, day // 6)
151
+
152
+ temperature = 0.7 * previous_state.temperature + \
153
+ 0.3 * float(weather["Temperature_C"])
154
+ rainfall = 0.5 * previous_state.rainfall + \
155
+ 0.5 * float(weather["Rainfall_mm"])
156
+ rainfall = self._clip(rainfall, 0.0, 200.0)
157
+
158
+ evaporation = max(temperature - 20.0, 0.0) * 0.35
159
+ moisture_gain = 0.12 * rainfall + 0.65 * action_model.water
160
+ moisture_loss = evaporation + 0.5 * crop_stage
161
+ soil_moisture = self._clip(
162
+ previous_state.soil_moisture + moisture_gain - moisture_loss,
163
+ 0.0,
164
+ 100.0,
165
+ )
166
+
167
+ soil_ph = self._clip(
168
+ previous_state.soil_ph - 0.012 *
169
+ action_model.fertilizer + 0.002 * action_model.water,
170
+ 4.5,
171
+ 8.5,
172
+ )
173
+
174
+ self._state = FarmState(
175
+ soil_moisture=soil_moisture,
176
+ soil_ph=soil_ph,
177
+ temperature=float(temperature),
178
+ rainfall=rainfall,
179
+ crop_stage=crop_stage,
180
+ day=day,
181
+ )
182
+
183
+ reward, reward_info = self._compute_reward(
184
+ self._state, action_model, day=day)
185
+ done = day >= self.max_days
186
+ return FarmStepResult(
187
+ observation=self._state,
188
+ reward=reward,
189
+ done=done,
190
+ info=reward_info,
191
+ )